/* Copyright (c) 2010, Dirk Krause All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above opyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Dirk Krause nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file useraud.c Main module of the useraud daemon. */ /** Inside the useraud module. */ #define USERAUD_C 1 #include "useraudi.h" $(trace-include) /** VOLATILE is volatile or an empty string. */ #if DK_HAVE_VOLATILE #define VOLATILE volatile #else #define VOLATILE /* nix */ #endif /** Program name. */ static char appname[] = { "useraud" }; /** @defgroup exit Exit code. */ /*@{*/ /** Exit code returned by the main function. */ static VOLATILE int exval = 0; /** Set exit code returned by the main function. @param i New exit code. */ void ua_set_exit_code DK_P1(int,i) { if(i > exval) exval = i; } /*@}*/ /** @defgroup buffers Buffers for request and response. */ /*@{*/ /** Input line. */ static char il[USERAUD_LINESIZE]; /** Outut line. */ static char ol[USERAUD_LINESIZE]; /** Temporary buffer 1. */ static char t1[USERAUD_LINESIZE]; /** Temporary buffer 2. */ static char t2[USERAUD_LINESIZE]; /** Temporary buffer 3. */ static char t3[USERAUD_LINESIZE]; /** Return code. */ static int rc; /*@}*/ /** @defgroup debugmode Debug mode. */ /*@{*/ /** Flag: Debug mode on. */ static int f_debug = 0; /** Check debug mode flag. @return Flag: Debug mode. */ int ua_get_debug DK_P0() { return f_debug; } /*@}*/ /** Name of configuration file. */ static char configFileName[ MAXPATHLEN ]; /** @defgroup loops Loop conditions. */ /*@{*/ /** Flag: Can continue in inner loop. */ static VOLATILE int ccInnerLoop = 1; /** Flag: Can continue in outer loop. */ static VOLATILE int ccOuterLoop = 1; /*@}*/ int useraud_get_outer_loop DK_P0() { return ccOuterLoop; } /** @defgroup signal Signal handling. */ /*@{*/ /** Flag: SIGPIPE found. */ static VOLATILE int haveSigpipe = 0; /** Signal handler for SIGTERM. @param signo Signal number. */ static dk_signal_ret_t handler_sigterm DK_P1(int,signo) { dksignal_refresh(signo, handler_sigterm); ccOuterLoop = ccInnerLoop = 0; $? "! SIGTERM" dksignal_return(0); } /** Signal handler for SIGINT. @param signo Signal number. */ static dk_signal_ret_t handler_sigint DK_P1(int,signo) { dksignal_refresh(signo, handler_sigint); ccOuterLoop = ccInnerLoop = 0; $? "! SIGINT" dksignal_return(0); } /** Signal handler for SIGPIPE. @param signo Signal number. */ static dk_signal_ret_t handler_sigpipe DK_P1(int,signo) { dksignal_refresh(signo, handler_sigpipe); haveSigpipe = 1; $? "! SIGPIPE" dksignal_return(0); } /** Signal handler for SIGHUP. @param signo Signal number. */ static dk_signal_ret_t handler_sighup DK_P1(int,signo) { dksignal_refresh(signo, handler_sighup); ccInnerLoop = 0; $? "! SIGHUP" dksignal_return(0); } /*@}*/ /** @defgroup protosession Protocol session. */ /*@{*/ /** Save cookie sitting in t1. @param uac UAC structure. @param username Name of users for cookie. @param ip IP address as text. @return 1 on success, 0 on error. */ static int save_cookie DK_P3(UAC *,uac, char *,username, char *,ip) { int back = 0; char buffer[64]; time_t tim1, tim2; time(&tim1); tim2 = (time_t)0UL; if(uac->ttl_cookie) { tim2 = tim1 + (time_t)(uac->ttl_cookie); } sprintf(buffer, "%lu", (unsigned long)tim2); if((strlen(buffer) + strlen(username) + 1) < sizeof(t2)) { sprintf(ol, "w:%s:%s", ip, t1); sprintf(t2, "%s:%s", username, buffer); if(dksdbi_string_store(uac->sdbi, ol, t2, 0)) { back = 1; } } return back; } /** Find user. @param uac UAC structure. @param n User logname. @param det Flag: Detailed information wanted. */ static UAU * find_user DK_P3(UAC *,uac, char *,n, int,det) { UAU *back = NULL; /* Function result. */ UAB *uab = NULL; /* Backend. */ UAB_API a; UAP *uap; UAP *uap2; int cc = 1; /* Flag: Can continue. */ int ex = 0; /* Flag: User excluded. */ int he = 0; /* Flag: Have error. */ $? "+ find_user \"%s\"", TR_STR(n) if((uac->s_be) && (uac->i_be)) { dksto_it_reset(uac->i_be); while((back == NULL) && (cc) && ccOuterLoop) { cc = 0; uab = (UAB *)dksto_it_next(uac->i_be); if(uab) { cc = 1; if((uab->s_ex) && (uab->i_ex)) { if(dksto_it_find_like(uab->i_ex, (void *)n, 0)) { ex = 1; } } if(!ex) { uabapi_init(&a); a.a.c = UA_API_GET_USER; a.a.v = n; a.a.f = (det ? 1 : 0); if(uab->f) { (*(uab->f))(uac, (void *)uab, &a); if(a.r.s) { back = a.r.u; he = 0; if((uab->s_a) && (uab->i_a)) { if(!(back->s_p)) { back->s_p = dksto_open(0); if(back->s_p) { back->i_p = dksto_it_open(back->s_p); if(back->i_p) { dksto_it_reset(uab->i_a); while((uap = (UAP *)dksto_it_next(uab->i_a)) != NULL) { uap2 = uau_property_new(uap->major, uap->minor, uap->value); if(uap2) { if(!dksto_add(back->s_p, (void *)uap2)) { he = 1; uau_property_delete(uap2); } } else { he = 1; } } } else { he = 1; } } else { he = 1; } } } if(he) { /* uau_delete(back); back = NULL; */ ualog_1(uac, DK_LOG_LEVEL_ERROR, 14); } } else { if(a.r.u) { uau_delete(a.r.u); } } } } } } } $? "- find_user %s", TR_PTR(back) return back; } /** Write one output line (prepared in ol). @param uac UAC structure. */ static void write_output_line DK_P1(UAC *,uac) { size_t lgt; ssize_t res; $? "+ write_output_line %s", ol if(!haveSigpipe) { lgt = 1 + strlen(ol); ol[lgt - 1] = '\n'; res = send(uac->sock, ol, lgt, 0); } $? "- write_output_line" } /** Remove cookie. @param uac UAC structure. @param args IP address, cookie. */ static void remove_cookie DK_P2(UAC *,uac, char *,args) { char *p1; char *p2; char *p3; p1 = args; p2 = dkstr_next(p1, NULL); if(p2) { p3 = dkstr_next(p2, NULL); if(p3 == NULL) { if((strlen(p1) + strlen(p2) + 3) < sizeof(ol)) { sprintf(ol, "w:%s:%s", p1, p2); dksdbi_string_delete(uac->sdbi, ol); rc = UA_RC_SUCCESS; sprintf(ol, "%03d", rc); write_output_line(uac); } } } } /** Send details about a user as part of the response. @param uac UAC structure. @param uau User to handle. @param cook Flag: Create and send a cookie. */ static void report_user_details DK_P3(UAC *,uac, UAU *,uau, int,cook) { char buffer[64]; char b2[64]; UAG *g; UAP *uap; /* UA_RC_ADDITIONAL_DATA */ if(uau->user_name) { /* 500 0 0 */ if((8 + strlen(uau->user_name)) < sizeof(ol)) { sprintf(ol, "%d 0 0 %s", UA_RC_ADDITIONAL_DATA, uau->user_name); write_output_line(uac); } } if(uau->gecos) { /* 500 0 1 */ if((8 + strlen(uau->gecos)) < sizeof(ol)) { sprintf(ol, "%d 0 1 %s", UA_RC_ADDITIONAL_DATA, uau->gecos); write_output_line(uac); } } if(uau->pg_name) { /* 500 0 2 */ sprintf(buffer, "%lu", (unsigned long)(uau->primary_group)); if((9 + strlen(uau->pg_name) + strlen(buffer)) < sizeof(ol)) { sprintf(ol, "%d 0 2 %s %s", UA_RC_ADDITIONAL_DATA, uau->pg_name, buffer); write_output_line(uac); } } if((uau->s_g) && (uau->i_g)) { dksto_it_reset(uau->i_g); while(((g = (UAG *)dksto_it_next(uau->i_g)) != NULL) && ccOuterLoop) { if(g->n) { /* 500 0 3 */ sprintf(buffer, "%lu", (unsigned long)(g->g)); if((9 + strlen(g->n) + strlen(buffer)) < sizeof(ol)) { sprintf(ol, "%d 0 3 %s %s", UA_RC_ADDITIONAL_DATA, g->n, buffer); write_output_line(uac); } } } } if((uau->s_p) && (uau->i_p)) { dksto_it_reset(uau->i_p); while((uap = (UAP *)dksto_it_next(uau->i_p)) != NULL) { if(uap->value) { sprintf(buffer, "%d", uap->major); sprintf(b2, "%d", uap->minor); if((strlen(buffer) + strlen(b2) + strlen(uap->value) + 6) < sizeof(ol)) { sprintf( ol, "%d %s %s %s", UA_RC_ADDITIONAL_DATA, buffer, b2, uap->value ); write_output_line(uac); } } } } if(cook) { /* 500 0 4 */ sprintf(ol, "%d 0 4 %s", UA_RC_ADDITIONAL_DATA, t1); write_output_line(uac); } sprintf(ol, "%d", UA_RC_FINAL_LINE); write_output_line(uac); } /** Validate user name and password. @param uac UAC structure. @param args Logname, password and IP address (optional). @param det Flag: Detailed response on success. @param cook Flag: Create and send cookie. */ static void logname_password DK_P4(UAC *,uac, char *,args, int,det, int,cook) { char *p1; /* Logname. */ char *p2; /* Password. */ char *p3; /* IP address. */ char *p4; /* Nothing. */ UAU *uau; /* User data. */ int hc; /* Flag: Have cookie. */ size_t cl; /* Cookie length. */ $? "+ logname_password args" p1 = args; p2 = p3 = p4 = NULL; p2 = dkstr_next(p1, NULL); if(p2) { p3 = dkstr_next(p2, NULL); if(p3) { p4 = dkstr_next(p3, NULL); } } if((p1) && (p2) && (cook ? ((p3) && (p4 == NULL)) : (p3 == NULL))) { rc = UA_RC_ERR_NO_SUCH_USER; $? ". p1, p2, p3 ok" uau = find_user(uac, p1, det); if(uau) { $? ". user found" if(uau->pw_hash) { $? ". pw hash \"%s\"", TR_STR(uau->pw_hash) rc = UA_RC_ERR_WRONG_CREDENTIALS; if(strlen(uau->pw_hash) > uau->sl) { $? ". pw hash length ok" if(uau->sl < sizeof(t1)) { $? ". salt length ok" strcpy(t1, uau->pw_hash); t1[uau->sl] = '\0'; if(uatcs_one_hash(uac, ol, sizeof(ol), p2, t1, uau->ht)) { $? ". hash calculation ok \"%s\"", ol if(strcmp(uau->pw_hash, ol) == 0) { $? ". auth ok" if(cook) { hc = 0; cl = sizeof(ol) - 9 - strlen(p3); if(cl > (sizeof(ol) - 5)) { cl = sizeof(ol) - 5; } if(uac->lgt_cookie) { if(cl > uac->lgt_cookie) { cl = uac->lgt_cookie; } } cl = 4 * cl / 5 - 1; if(cl > 0) { if(RAND_bytes((unsigned char *)ol, cl) == 1) { hc = 1; dkenc_bin_to_ra85(t1, sizeof(t1), ol, cl); if(!save_cookie(uac, p1, p3)) { hc = 0; } } } if(det) { rc = UA_RC_SUCCESS_FULL; if(hc) rc = UA_RC_SUCCESS_FULL_WITH_COOKIE; sprintf(ol, "%03d", rc); write_output_line(uac); report_user_details(uac, uau, hc); } else { if(hc) { rc = UA_RC_SUCCESS_COOKIE; sprintf(ol, "%03d %s", rc, t1); } else { rc = UA_RC_SUCCESS; sprintf(ol, "%03d", rc); } write_output_line(uac); } } else { if(det) { rc = UA_RC_SUCCESS_FULL; sprintf(ol, "%03d", rc); write_output_line(uac); report_user_details(uac, uau, 0); } else { rc = UA_RC_SUCCESS; sprintf(ol, "%03d", rc); write_output_line(uac); } } } } else { } } } } uau_delete(uau); uau = NULL; } else { $? "! user not found" } } else { $? "! request wrong" } if(rc == UA_RC_ERR_NO_SUCH_USER) { if(!(uac->f_no_such)) { rc = UA_RC_ERR_WRONG_CREDENTIALS; } } $? "- logname_password rc=%d", rc } /** Handle IP address and cookie. @param uac UAC structure. @param args IP address and cookie. @param det Flag: Send detailed response. */ static void ip_cookie DK_P3(UAC *,uac, char *,args, int,det) { char *p1; /* IP address. */ char *p2; /* Cookie. */ char *p3; /* Time entry in cookie db value. */ unsigned long ul; /* Time entry in cookie db value. */ time_t ct; /* Current time. */ UAU *u; /* User information. */ size_t sl; /* String length of user name. */ char buffer[64]; /* Buffer for new expiration timestamp. */ $? "+ ip_cookie" p1 = args; p2 = dkstr_next(p1, NULL); if(p2) { $? ". cookie found" p3 = dkstr_next(p2, NULL); if(p3 == NULL) { $? ". no extra string" if((strlen(p1) + strlen(p2) + 3) < sizeof(ol)) { rc = UA_RC_ERR_NO_SUCH_COOKIE; $? ". string lengths ok" sprintf(ol, "w:%s:%s", p1, p2); if(dksdbi_string_fetch(uac->sdbi, ol, t1, sizeof(t1))) { p3 = dkstr_chr(t1, ':'); $? ". db entry found" if(p3) { $? ". db entry format ok" *(p3++) = '\0'; sl = strlen(t1); if(((sl + 4) < sizeof(ol)) && (sl > 0)) { $? ". length ok" if(sscanf(p3, "%lu", &ul) == 1) { $? ". expiration found" time(&ct); if((ul == 0UL) || ((unsigned long)ct <= ul)) { u = find_user(uac, t1, det); $? ". expiration ok" if(u) { $? ". user found" if(uac->ttl_cookie) { ul = (unsigned long)ct + uac->ttl_cookie; } else { ul = 0UL; } $? ". new expiration timestamp %lu", ul sprintf(buffer, "%lu", ul); if(u->user_name) { if((strlen(u->user_name)+strlen(buffer)+2) < sizeof(t1)) { $? ". cookie update for new expiration" sprintf(t1, "%s:%s", u->user_name, buffer); dksdbi_string_store(uac->sdbi, ol, t1, 0); strcpy(t1, u->user_name); } } rc = UA_RC_SUCCESS_LOGNAME; if(det) { rc = UA_RC_SUCCESS_COOKIE_FULL; } sprintf(ol, "%03d %s", rc, t1); write_output_line(uac); if(det) { $? ". details needed" report_user_details(uac, u, 0); } uau_delete(u); } } else { rc = UA_RC_ERR_INACTIVE_TOO_LONG; } } } } } if(rc >= 400) { $? "! problem, remove db entry for cookie" dksdbi_string_delete(uac->sdbi, ol); } } } } $? "- ip_cookie %d", rc } /** Create a hash for a faked user and save it in the database. @param uac UAC structure. @param be Backend. @return 1 on success, 0 on error. */ static int create_faked_hash DK_P2(UAC *,uac, UAB *,be) { int back = 0; /* Function result. */ int i; char unb[16]; unsigned x; $? "+ create_faked_hash" if(uatcs_create_salt(uac, t3, sizeof(t3), be->ht, be->st)) { back = strlen(t3); $? ". salt = \"%s\"", t3 for(i = 0; i < 8; i++) { if(RAND_bytes((unsigned char *)(&x), sizeof(x)) == 1) { unb[i] = uatcs_get_alnum(x); } else { back = 0; } } unb[i] = '\0'; $? ". faked password = \"%s\"", unb if(back) { if(!uatcs_one_hash(uac, t1, sizeof(t1), unb, t3, be->ht)) { back = 0; } else { $? ". password hash = \"%s\"", t1 } } } $? "- create_faked_hash %d", back return back; } /** Handle logname and algorithms list. @param uac UAC structure. @param args Logname and algorithms list. */ static void logname_algorithms DK_P2(UAC *,uac, char *,args) { char slb[64]; /* Buffer for salt length 1. */ char sl2[64]; /* Buffer for salt length / timestamp. */ char *p1; char *p2; char *p3; char *pst1; /* Salt type 1. */ char *pst2; /* Salt type 2. */ UAB *be; UAU *u; int ht; /* Hash types supported by client. */ size_t ll; /* Line length needed, */ size_t lgt; /* Hash length. */ int ht2, ht2c, ht2t, i, htori; time_t ct; unsigned long ul; time(&ct); htori = 0; p1 = args; p2 = dkstr_next(p1, NULL); if(p2) { $? ". algorithms list found" p3 = dkstr_next(p2, NULL); if(p3 == NULL) { $? ". ok, no extra data" htori = ht = uatcs_all_hash_types(p2); if(ht) { $? ". algorithms: %d", ht rc = UA_RC_ERR_NO_SUCH_USER; u = find_user(uac, p1, 0); if(u) { $? ". user found" if(u->user_name) { $? ". user name %s", u->user_name ht &= (uac->hash_types | u->ht); rc = UA_RC_ERR_INTERNAL; ht2t = USERAUD_HASH_MAXVAL; ht2c = ht2 = 0; while(ht2t > 0) { if(ht & ht2t) { if(ht2t != u->ht) { ht2 = ht2t; ht2t = 0; } else { ht2c = ht2t; } } ht2t = ht2t / 2; } if(ht2 == 0) { ht2 = ht2c; } if(ht2) { $? ". second hash type: %d", ht2 if(uatcs_create_salt(uac, t2, sizeof(t2), ht2, u->st)) { $? ". salt=\"%s\"", t2 pst1 = uatcs_get_hash_name(u->ht); pst2 = uatcs_get_hash_name(ht2); if((pst1) && (pst2)) { $? ". hash types = %s/%s", pst1, pst2 sprintf(slb, "%d", u->sl); ll = 7 + strlen(pst1) + strlen(slb) + strlen(pst2) + u->sl + strlen(t2); if(ll < sizeof(ol)) { $? ". Both lengths ok" ll = 6 + strlen(u->user_name) +strlen(pst1) + strlen(slb) + strlen(pst2) + u->sl + strlen(t2); if(ll < sizeof(t3)) { for(i = 0; i < u->sl; i++) { t1[i] = (u->pw_hash)[i]; } t1[i] = '\0'; sprintf(ol, "203 %s:%s,%s %s%s", pst1, slb,pst2, t1, t2); $? ". ol = \"%s\"", ol sprintf( t3, "c:%s:%s:%s,%s %s%s", u->user_name, pst1, slb, pst2, t1, t2 ); $? ". t3 = \"%s\"", t3 sprintf( t1, "r:%lu", ((uac->ttl_challenge) ? ((unsigned long)ct + uac->ttl_challenge) : 0UL) ); $? ". t1 = \"%s\"", t1 if(dksdbi_string_store(uac->sdbi, t3, t1, 0)) { rc = 203; $? ". success 203" write_output_line(uac); } } } } } } else { rc = UA_RC_ERR_ALGORITHM_OTHERS; } } else { ht &= uac->hash_types; } uau_delete(u); } else { ht &= uac->hash_types; } } else { ht &= uac->hash_types; rc = UA_RC_ERR_ALGORITHM_OTHERS; } } } if(rc == UA_RC_ERR_NO_SUCH_USER) { if(!(uac->f_no_such)) { $? ". must send faked challenge" rc = UA_RC_ERR_INTERNAL; t1[0] = t2[0] = t3[0] = '\0'; lgt = 0; dksto_it_reset(uac->i_be); be = (UAB *)dksto_it_next(uac->i_be); if(be) { $? ". backend found" ht2t = USERAUD_HASH_MAXVAL; ht2c = ht2 = 0; while(ht2t > 0) { if(htori & ht2t) { if(ht2t != be->ht) { ht2 = ht2t; ht2t = 0; } else { ht2c = ht2t; } } ht2t = ht2t / 2; } if(ht2 == 0) { ht2 = ht2c; } if(ht2) { $? ". second hash type found" /* create_faked_hash: t2: DB key for faked hash - get faked hash if possible, expand time ... or ... - build faked hash ... and ... t3: faked salt t1: faked hash - save faked hash - find method for second hash step - create second salt in t2 t2: second salt - create entire salt in t3 t3: challenge (entire salt) - create challenge type in t2 t2: challenge type - create DB key in t1 - create DB value in ol t1: DB key ol: DB value - save DB entry - create output in ol ol: Output line */ if((strlen(p1) + 2) < sizeof(t2)) { $? ". user name length ok" sprintf(t2, "f:%s", p1); $? ". db key = \"%s\"", t2 if(dksdbi_string_fetch(uac->sdbi, t2, t1, sizeof(t1))) { p2 = dkstr_chr(t1, ' '); $? ". value retrieved" if(p2) { *(p2++) = '\0'; if(sscanf(p2, "%lu", &ul) == 1) { $? ". timestamp found" if((ul == 0UL) || (ul >= (unsigned long)ct)) { $? ". time ok" sprintf( slb, " %lu", ((uac->ttl_salt) ? ((unsigned long)ct + uac->ttl_salt) : 0UL) ); if((strlen(t1) + strlen(slb)) < sizeof(t1)) { strcat(t1, slb); $? ". buffer long enough for new time" if(dksdbi_string_store(uac->sdbi, t2, t1, 0)) { p2--; *p2 = '\0'; $? ". new timestamp saved" p2 = dkstr_chr(t1, ':'); if(p2) { $? ". colon found" *(p2++) = '\0'; if(sscanf(t1, "%lu", &ul) == 1) { lgt = (size_t)ul; $? ". salt length %lu", ul p3 = t1; while(*p2) { *(p3++) = *(p2++); } *p3 = '\0'; $? ". t1 now \"%s\" %lu", t1, (unsigned long)lgt } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } if((t1[0] == '\0') || (lgt == 0)) { $? ". attempt to create faked hash" lgt = create_faked_hash(uac, be); if(lgt > 0) { $? ". faked hash created" sprintf(slb, "%lu", (unsigned long)lgt); sprintf( sl2, "%lu", ((uac->ttl_salt) ? ((unsigned long)ct + uac->ttl_salt) : 0UL) ); ll = strlen(slb) + strlen(t1) + strlen(sl2) + 2; if(ll < sizeof(t3)) { $? ". buffer length ok" sprintf(t3, "%s:%s %s", slb, t1, sl2); if(!dksdbi_string_store(uac->sdbi, t2, t3, 0)) { t1[0] = '\0'; $? ". new faked hash saved" } } else { t1[0] = '\0'; } } else { t1[0] = '\0'; } } if((t1[0] != '\0') && (lgt > 0)) { $? ". faked hash in t1, length in lgt" if(uatcs_create_salt(uac, t2, sizeof(t2), ht2, be->st)) { $? ". secondary salt ok" if((lgt + strlen(t2)) < sizeof(t3)) { $? ". length ok" for(i = 0; i < lgt; i++) { t3[i] = t1[i]; } t3[i] = '\0'; strcat(t3, t2); $? ". challenge=\"%s\"", t3 pst1 = uatcs_get_hash_name(be->ht); pst2 = uatcs_get_hash_name(ht2); if((pst1) && (pst2)) { $? ". hashes %s/%s", pst1, pst2 sprintf(slb, "%lu", (unsigned long)lgt); ll = strlen(pst1) + strlen(pst2) + strlen(slb) + 2; if(ll < sizeof(t2)) { $? ". buffer length ok for challenge type" sprintf(t2, "%s:%s,%s", pst1, slb, pst2); $? ". ch t = \"%s\"", t2 ll = strlen(p1) + strlen(t2) + strlen(t3) + 4; if(ll < sizeof(t1)) { $? ". buffer length ok for db key" ll = strlen(t2) + strlen(t3) + 5; if(ll < sizeof(ol)) { $? ". buffer length ok for output line" sprintf(t1, "c:%s:%s %s", p1, t2, t3); $? ". db key \"%s\"", t1 sprintf( ol, "f:%lu", ((uac->ttl_challenge) ? ((unsigned long)ct + uac->ttl_challenge) : 0UL) ); $? ". db val \"%s\"", ol if(dksdbi_string_store(uac->sdbi, t1, ol, 0)) { $? ". db entry saved" rc = UA_RC_SUCCESS_CHALLENGE; sprintf(ol, "203 %s %s", t2, t3); $? ". response \"%s\"", ol write_output_line(uac); $? ". response sent" } } } } } } } } } } } } } } /** Handle response. @param uac UAC structure. @param args Logname, challenge type, challenge and response. @param det Flag: Send detailed information. */ static void response DK_P3(UAC *,uac, char *,args, int,det) { char *p1; /* Logname. */ char *p2; /* Challenge type. */ char *p3; /* challenge. */ char *p4; /* Response. */ char *p5; /* Must be NULL. */ size_t ll; /* Line length. */ unsigned long ul; /* Timestamp from DB entry. */ time_t ct; /* Current time. */ UAU *u; /* User data. */ $? "+ response" p1 = args; p2 = p3 = p4 = p5 = NULL; p2 = dkstr_next(p1, NULL); if(p2) { p3 = dkstr_next(p2, NULL); if(p3) { p4 = dkstr_next(p3, NULL); if(p4) { p5 = dkstr_next(p4, NULL); } } } if((p1) && (p2) && (p3) && (p4) && (!(p5))) { rc = UA_RC_ERR_NO_SUCH_CHALLENGE; ll = strlen(p1) + strlen(p2) + strlen(p3) + 4; if(ll < sizeof(t1)) { /* NEVER CHANGE t1 IN THIS FUNCTION */ sprintf(t1, "c:%s:%s %s", p1, p2, p3); if(dksdbi_string_fetch(uac->sdbi, t1, t2, sizeof(t2))) { if(t2[0] == 'f') { /* Faked challenge for non-existing user. */ rc = UA_RC_ERR_WRONG_CREDENTIALS; } else { /* Real user. */ p5 = dkstr_chr(t2, ':'); if(p5) { *(p5++) = '\0'; time(&ct); if(sscanf(p5, "%lu", &ul) == 1) { if((ul == 0UL) || ((unsigned long)ct <= ul)) { rc = UA_RC_ERR_WRONG_CREDENTIALS; u = find_user(uac, p1, det); if(u) { if(u->sl > 0) { if(u->pw_hash) { if(strlen(u->pw_hash) > u->sl) { if(strlen(p3) > u->sl) { if(strncmp(u->pw_hash, p3, u->sl) == 0) { $? ". s ok" if(uatcs_apply_challenge(uac, p2, p3, u->pw_hash, t3, sizeof(t3), 0)) { if(strcmp(t3, p4) == 0) { if(det) { rc = UA_RC_SUCCESS_FULL; sprintf(ol, "%03d", rc); write_output_line(uac); report_user_details(uac, u, 0); } else { rc = UA_RC_SUCCESS; sprintf(ol, "%03d", rc); write_output_line(uac); } } } } } } } } uau_delete(u); } } else { rc = UA_RC_ERR_CHALLENGE_TIMEOUT; } } } } dksdbi_string_delete(uac->sdbi, t1); } } } $? "- response %d", rc } /** Handle 110/111 requests to verify a user name. @param uac UAC structure. @param un User name to verify. @param det Flag: Send details. */ static void username_test DK_P3(UAC *,uac, char *,un, int,det) { UAU *u; rc = UA_RC_ERR_NO_SUCH_USER; if(uac->username_test) { u = find_user(uac, un, det); if(u) { if(det) { rc = UA_RC_SUCCESS_FULL; sprintf(ol, "%d", rc); write_output_line(uac); report_user_details(uac, u, 0); } else { rc = UA_RC_SUCCESS; sprintf(ol, "%d", rc); write_output_line(uac); } } } } /** Do one session (read one request line, send answer). @param uac UAC structure. */ static void run_session DK_P1(UAC *,uac) { ssize_t szrd; int opcode = 0; char *p1; char *p2; $? "+ run_session" haveSigpipe = 0; rc = UA_RC_ERR_PROTOCOL; szrd = recv(uac->sock, il, sizeof(il), 0); if(szrd > 0) { $? ". have input line" il[sizeof(il) - 1] = '\0'; il[(szrd >= sizeof(il)) ? sizeof(il) - 1 : szrd] = '\0'; p1 = dkstr_start(il, NULL); $? ". input line \"%s\"", il if(p1) { $? ". real input \"%s\"", p1 p2 = dkstr_next(p1, NULL); if(p2) { $? ". arguments \"%s\"", p2 if(sscanf(p1, "%d", &opcode) == 1) { $? ". opcode=%d", opcode switch(opcode) { case UA_RQ_LOGNAME_PASSWORD: { logname_password(uac, p2, 0, 0); } break; case UA_RQ_LOGNAME_PASSWORD_LONG: { logname_password(uac, p2, 1, 0); } break; case UA_RQ_LOGNAME_PASSWORD_IP: { logname_password(uac, p2, 0, 1); } break; case UA_RQ_LOGNAME_PASSWORD_IP_LONG: { logname_password(uac, p2, 1, 1); } break; case UA_RQ_IP_COOKIE: { ip_cookie(uac, p2, 0); } break; case UA_RQ_IP_COOKIE_LONG: { ip_cookie(uac, p2, 1); } break; case UA_RQ_GET_CHALLENGE: { logname_algorithms(uac, p2); } break; case UA_RQ_RESPONSE: { response(uac, p2, 0); } break; case UA_RQ_RESPONSE_LONG: { response(uac, p2, 1); } break; case UA_RQ_LOGOUT: { remove_cookie(uac, p2); } break; case UA_RQ_VERIFY_LOGNAME: { username_test(uac, p2, 0); } break; case UA_RQ_VERIFY_DETAILED: { username_test(uac, p2, 1); } break; } } } } if((rc >= 400) && (rc < 500)) { if(rc == UA_RC_ERR_NO_SUCH_USER) { if(!(uac->f_no_such)) { if((opcode != 110) && (opcode != 111)) { rc = UA_RC_ERR_WRONG_CREDENTIALS; } } } sprintf(ol, "%03d", rc); write_output_line(uac); } } else { /* No input. */ } $? "- run_session" } /*@}*/ /** @defgroup initialize Initialize and set up. */ /*@{*/ /** Setup when running the first time. @param uac UAC structure. @return Flag to indicate success. */ static int run_first DK_P1(UAC *,uac) { int back = 1; uid_t nu = 0; gid_t ng = 0; int have_nu = 0; int have_ng = 0; struct passwd *pw; struct group * gr; char *p1; $? "+ run_first" /* Find user and group. */ nu = geteuid(); ng = getegid(); if(uac->run_as_user) { pw = uat_getpwnam(uac->run_as_user); if(pw) { nu = pw->pw_uid; have_nu = 1; } else { back = 0; $? "! user not found" ualog_3(uac, DK_LOG_LEVEL_ERROR, 34, 35, uac->run_as_user); } } if(uac->run_as_group) { gr = uat_getgrnam(uac->run_as_group); if(gr) { ng = gr->gr_gid; have_ng = 1; } else { back = 0; $? "! group not found" ualog_3(uac, DK_LOG_LEVEL_ERROR, 36, 35, uac->run_as_group); } } /* Create directory structure for log file, socket and database. */ if(uac->dbname) { p1 = dkstr_chr(uac->dbname, ':'); if(p1) { p1++; if(!uat_create_parent(uac, p1, nu, ng)) { back = 0; ualog_3(uac, DK_LOG_LEVEL_ERROR, 37, 38, p1); } } else { if(!uat_create_parent(uac, uac->dbname, nu, ng)) { back = 0; ualog_3(uac, DK_LOG_LEVEL_ERROR, 37, 38, uac->dbname); } } } else { back = 0; } if(!uat_create_parent(uac, uac->logname, nu, ng)) { back = 0; ualog_3(uac, DK_LOG_LEVEL_ERROR, 37, 38, uac->logname); } if(!uat_create_parent(uac, uac->sockname, nu, ng)) { back = 0; ualog_3(uac, DK_LOG_LEVEL_ERROR, 37, 38, uac->sockname); } /* Seed PRNG. */ if(!uat_get_seed(uac)) { back = 0; ualog_1(uac, DK_LOG_LEVEL_ERROR, 39); } /* Change user and group. */ if(have_ng) { setgid(ng); } if(have_nu) { setuid(nu); } $? "- run_first" return back; } /** Traverse data used when cleaning up the database. */ typedef struct { dk_storage_t *s; /**< Storage for key names to delete. */ time_t *t; /**< Current time. */ } TD; /** Traversal function used when cleaning up the database. @param o Traversal object (a TD here). @param kp Pointer to DB entry key. @param kl Key length. @param vp Pointer to DB entry value. @param vl Value length. @return 0 on success, 1 on warnings (can continue), -1 on errors (exit). */ int traverse_fct DK_P5(void *,o, void *,kp, size_t,kl, void *,vp, size_t,vl) { int back = 0; TD *td; char *pe; char *t; unsigned long ul; $? "+ traverse_fct" if((kp) && (kl) && (vp) && (vl) && (o)) { td = (TD *)o; if((kl < sizeof(il)) && (vl < sizeof(ol))) { DK_MEMCPY(il, kp, kl); DK_MEMCPY(ol, vp, vl); il[sizeof(il) - 1] = '\0'; ol[sizeof(ol) - 1] = '\0'; il[kl] = '\0'; ol[vl] = '\0'; $? "\"%s\"=\"%s\"", il, ol pe = dkstr_chr(ol, ((*il != 'f') ? ':' : ' ')); if(pe) { pe++; if(sscanf(pe, "%lu", &ul) == 1) { if(ul > 0UL) { $? "ct=%lu to=%lu", ul, (unsigned long *)(*(td->t)) if(ul < (unsigned long)(*(td->t))) { t = dkstr_dup(il); $? ". entry \"%s\" expired", il if(t) { if(!dksto_add(td->s, (void *)t)) { dk_delete(t); t = NULL; back = 1; } } else { back = 1; } if(!t) { $? "! memory" } } } } } } } if(!ccOuterLoop) { back = -1; } $? "- traverse_fct %d", back return back; } /** Check cleanup timestamp, do cleanup if necessary. @param uac UAC structure. */ static void cleanup_if_necessary DK_P1(UAC *,uac) { TD td; time_t ct; dk_storage_iterator_t *i_k; char *k; char buffer[64]; unsigned long ndel = 0UL; time(&ct); $? "+ cleanup_if_necessary" if(((uac->last_cleanup + (time_t)(uac->sec_cleanup)) < ct) || (uac->last_cleanup == (time_t)0)) { ualog_1(uac, DK_LOG_LEVEL_PROGRESS, 26); td.t = &ct; td.s = dksto_open(0); if(td.s) { $? ". storage ok" i_k = dksto_it_open(td.s); if(i_k) { $? ". iterator ok" ndel = 0UL; ualog_1(uac, DK_LOG_LEVEL_PROGRESS, 28); if(dksdbi_traverse(uac->sdbi, (void *)(&td), traverse_fct)) { $? ". db traverse success" } else { $? "! failed to traverse db" } ualog_1(uac, DK_LOG_LEVEL_PROGRESS, 29); ualog_1(uac, DK_LOG_LEVEL_PROGRESS, 30); dksto_it_reset(i_k); while(((k = (char *)dksto_it_next(i_k)) != NULL) && (ccOuterLoop)) { dksdbi_string_delete(uac->sdbi, k); ndel++; $? ". db del \"%s\"", k } ualog_1(uac, DK_LOG_LEVEL_PROGRESS, 31); dksto_it_reset(i_k); while((k = (char *)dksto_it_next(i_k)) != NULL) { dk_delete(k); } dksto_it_close(i_k); } dksto_close(td.s); td.s = NULL; } sprintf(buffer, "%lu", ndel); ualog_3(uac, DK_LOG_LEVEL_PROGRESS, 32, 33, buffer); ualog_1(uac, DK_LOG_LEVEL_PROGRESS, 27); time(&ct); uac->last_cleanup = ct; } $? "- cleanup_if_necessary" } /** Run loops. The outer loop is left when receiving an INT or TERM signal or if an error occures. The inner loop is left when receiving a HUP signal, so we can reconfigure. */ static void run_loops DK_P0() { UAC *uac; char *cfgFileName; dk_signal_disp_t disp_term = NULL; dk_signal_disp_t disp_int = NULL; dk_signal_disp_t disp_pipe = NULL; time_t confread; time_t confmod; int is_first = 1; int have_seed = 0; int sockListen; struct sockaddr_un soun; $(trace-init /tmp/useraud.deb) $? "+ run_loops" disp_term = dksignal_set(SIGTERM,handler_sigterm); disp_int = dksignal_set(SIGINT,handler_sigint); disp_pipe = dksignal_set(SIGPIPE,handler_sigpipe); ccOuterLoop = 1; while(ccOuterLoop) { cfgFileName = configFileName; if(*cfgFileName == '\0') { cfgFileName = uatcs_get_default_config_file_name(); } confread = uat_modtime(cfgFileName); uac = uac_open(cfgFileName, 1); if(uac) { $? ". configuration ok" ccInnerLoop = 1; if(is_first) { $? ". running the first time" is_first = 0; if(run_first(uac)) { have_seed = 1; } else { ccOuterLoop = 0; ccInnerLoop = 0; } } if((uac->dbname) && (uac->sockname)) { if(strlen(uac->sockname) < 108) { uac->sdbi = dksdbi_open(uac->dbname,DK_SDBI_TYPE_AUTO,DK_SDBI_MODE_RDWR,0600,1024); if(uac->sdbi) { $? ". DB opened successfuly" cleanup_if_necessary(uac); dksf_remove_file(uac->sockname); unlink(uac->sockname); sockListen = socket(PF_UNIX, SOCK_STREAM, 0); if(sockListen > -1) { soun.sun_family = AF_UNIX; strcpy(soun.sun_path, uac->sockname); if(bind(sockListen, (struct sockaddr *)(&soun), SZSOUN) == 0) { if(listen(sockListen, 5) == 0) { ualog_1(uac, DK_LOG_LEVEL_INFO, 56); while(ccInnerLoop && ccOuterLoop) { uac->sock = accept(sockListen, NULL, 0); if(uac->sock > -1) { if(ccOuterLoop) { run_session(uac); } close(uac->sock); uac->sock = -1; } else { ccOuterLoop = 0; if(errno != EINTR) { ualog_1(uac, DK_LOG_LEVEL_ERROR, 48); } } confmod = uat_modtime(cfgFileName); if(confmod > confread) { $? ". must re-read conf" ccInnerLoop = 0; ualog_1(uac, DK_LOG_LEVEL_INFO, 58); } if(ccOuterLoop) { cleanup_if_necessary(uac); } } ualog_1(uac, DK_LOG_LEVEL_INFO, 57); } else { ccOuterLoop = 0; ualog_1(uac, DK_LOG_LEVEL_ERROR, 47); } } else { ccOuterLoop = 0; ualog_3(uac, DK_LOG_LEVEL_ERROR, 46, 43, uac->sockname); } close(sockListen); dksf_remove_file(uac->sockname); unlink(uac->sockname); } else { ccOuterLoop = 0; ualog_1(uac, DK_LOG_LEVEL_ERROR, 45); } dksdbi_close(uac->sdbi); } else { $? "! failed to open DB" ccOuterLoop = 0; ualog_3(uac, DK_LOG_LEVEL_ERROR, 44, 43, uac->dbname); } } else { ccOuterLoop = 0; ualog_3(uac, DK_LOG_LEVEL_ERROR, 42, 43, uac->sockname); } } else { ccOuterLoop = 0; if(!(uac->dbname)) { ualog_1(uac, DK_LOG_LEVEL_ERROR, 41); } if(!(uac->sockname)) { ualog_1(uac, DK_LOG_LEVEL_ERROR, 40); } } if(!ccOuterLoop) { $? ". save seed before exiting" if(have_seed) { uat_save_seed(uac); } } uac_close(uac); } else { $? "! no configuration" ccOuterLoop = 0; } } if(disp_pipe) { dksignal_set(SIGPIPE,disp_pipe); } if(disp_int) { dksignal_set(SIGPIPE,disp_int); } if(disp_term) { dksignal_set(SIGPIPE,disp_term); } $? "- run_loops" $(trace-end) } /** Run loops in foreground (debug mode). */ static void run_loops_in_foreground DK_P0() { int maxfiles; int i; (void)chdir("/"); umask(077); maxfiles = (int)dksf_get_maxfiles(); for(i = 3; i < maxfiles; i++) { (void)close(i); } run_loops(); } /** Run loops as daemon. */ static void run_loops_as_daemon DK_P0() { int maxfiles; int i; dk_signal_disp_t disp_hup = NULL; pid_t newpid; (void)chdir("/"); umask(077); maxfiles = (int)dksf_get_maxfiles(); for(i = 0; i < maxfiles; i++) { (void)close(i); } newpid = fork(); if(newpid == 0) { #if DK_HAVE_SETSID setsid(); #else #if DK_HAVE_SETPGRP setpgrp(); #endif #endif disp_hup = dksignal_set(SIGHUP,handler_sighup); newpid = fork(); if(newpid == 0) { dksf_write_pid_file(appname, 1); run_loops(); dksf_write_pid_file(appname, 0); } else { if(newpid == ((pid_t)(-1))) { ualog_1(NULL, DK_LOG_LEVEL_ERROR, 15); } } if(disp_hup) { dksignal_set(SIGHUP,disp_hup); } } else { if(newpid == ((pid_t)(-1))) { fprintf(stderr, "ERROR: fork() failed!\n"); fflush(stderr); } } } /*@}*/ /** @defgroup main Command line arguments checking. */ /*@{*/ /** Check command line arguments. @param argc Number of command line arguments. @param argv Command line arguments array. */ static void check_arguments DK_P2(int,argc, char **,argv) { int i; /* Index of current command line arg. */ int f_filename; /* Flag: Have file name. */ char **lfdptr; /* Traverse command line arguments. */ char *ptr; /* Current command line arg processed. */ char *optr; /* Original ptr. */ lfdptr = argv; lfdptr++; i = 1; f_filename = 0; while(i < argc) { ptr = *lfdptr; optr = ptr; switch(*ptr) { case '-': { ptr++; switch(*ptr) { case 'd': { f_debug = 1; ualog_set_debug(1); } break; default: { exval = 1; fprintf(stderr, "ERROR: Unknown option \"%s\"!\n", optr); fflush(stderr); } break; } } break; default: { if(f_filename) { exval = 1; fprintf(stderr, "ERROR: Too many file names, only one allowed\n"); fflush(stderr); } else { if(strlen(ptr) < sizeof(configFileName)) { strcpy(configFileName, ptr); f_filename = 1; } else { exval = 1; fprintf(stderr, "ERROR: Configuration file name too long!\n"); fflush(stderr); } } } break; } lfdptr++; i++; } } /** The main() function. @param argc Number of command line arguments. @param argv Command line arguments array. @return 0 on success, any other value on error. */ #if DK_HAVE_PROTOTYPES int main(int argc, char *argv[]) #else int main(argc, argv) int argc; char *argv[]; #endif { configFileName[0] = '\0'; check_arguments(argc, argv); if(exval == 0) { if(f_debug) { run_loops_in_foreground(); } else { run_loops_as_daemon(); } } exit(exval); return exval; } /*@}*/