From 31baf6937aee6f449a6675e102d6f0831bfb1fbe Mon Sep 17 00:00:00 2001 From: "Alfred E. Heggestad" Date: Sun, 3 Apr 2016 17:35:05 +0200 Subject: test: added test-case for authenticated register --- test/sip/aor.c | 98 ++++++++++++++++ test/sip/auth.c | 91 +++++++++++++++ test/sip/domain.c | 187 +++++++++++++++++++++++++++++ test/sip/location.c | 188 ++++++++++++++++++++++++++++++ test/sip/sipsrv.c | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test/sip/sipsrv.h | 121 +++++++++++++++++++ test/sip/user.c | 73 ++++++++++++ 7 files changed, 1087 insertions(+) create mode 100644 test/sip/aor.c create mode 100644 test/sip/auth.c create mode 100644 test/sip/domain.c create mode 100644 test/sip/location.c create mode 100644 test/sip/sipsrv.c create mode 100644 test/sip/sipsrv.h create mode 100644 test/sip/user.c (limited to 'test/sip') diff --git a/test/sip/aor.c b/test/sip/aor.c new file mode 100644 index 0000000..0d8a317 --- /dev/null +++ b/test/sip/aor.c @@ -0,0 +1,98 @@ +/** + * @file sip/aor.c Mock SIP server -- SIP Address of Record + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include +#include +#include "sipsrv.h" + + +static void destructor(void *arg) +{ + struct aor *aor = arg; + + list_flush(&aor->locl); + hash_unlink(&aor->he); + mem_deref(aor->uri); +} + + +static int uri_canon(char **curip, const struct uri *uri) +{ + if (pl_isset(&uri->user)) + return re_sdprintf(curip, "%r:%H@%r", + &uri->scheme, + uri_user_unescape, &uri->user, + &uri->host); + else + return re_sdprintf(curip, "%r:%r", + &uri->scheme, + &uri->host); +} + + +int aor_create(struct sip_server *srv, struct aor **aorp, + const struct uri *uri) +{ + struct aor *aor; + int err; + + if (!aorp || !uri) + return EINVAL; + + aor = mem_zalloc(sizeof(*aor), destructor); + if (!aor) + return ENOMEM; + + err = uri_canon(&aor->uri, uri); + if (err) + goto out; + + hash_append(srv->ht_aor, hash_joaat_str_ci(aor->uri), &aor->he, aor); + + out: + if (err) + mem_deref(aor); + else + *aorp = aor; + + return err; +} + + +int aor_find(struct sip_server *srv, struct aor **aorp, const struct uri *uri) +{ + struct list *lst; + struct aor *aor; + struct le *le; + char *curi; + int err; + + if (!uri) + return EINVAL; + + err = uri_canon(&curi, uri); + if (err) + return err; + + lst = hash_list(srv->ht_aor, hash_joaat_str_ci(curi)); + + for (le=list_head(lst); le; le=le->next) { + + aor = le->data; + + if (!str_casecmp(curi, aor->uri)) + break; + } + + mem_deref(curi); + + if (!le) + return ENOENT; + + if (aorp) + *aorp = aor; + + return 0; +} diff --git a/test/sip/auth.c b/test/sip/auth.c new file mode 100644 index 0000000..5d3e224 --- /dev/null +++ b/test/sip/auth.c @@ -0,0 +1,91 @@ +/** + * @file sip/auth.c Mock SIP server -- authentication + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include +#include +#include +#include "sipsrv.h" + + +enum { + NONCE_MIN_SIZE = 33, +}; + + +int auth_print(struct re_printf *pf, const struct auth *auth) +{ + uint8_t key[MD5_SIZE]; + uint64_t nv[2]; + + if (!auth) + return EINVAL; + + nv[0] = time(NULL); + nv[1] = auth->srv->secret; + + md5((uint8_t *)nv, sizeof(nv), key); + + return re_hprintf(pf, + "Digest realm=\"%s\", nonce=\"%w%llx\", " + "qop=\"auth\"%s", + auth->realm, + key, sizeof(key), nv[0], + auth->stale ? ", stale=true" : ""); +} + + +int auth_chk_nonce(struct sip_server *srv, + const struct pl *nonce, uint32_t expires) +{ + uint8_t nkey[MD5_SIZE], ckey[MD5_SIZE]; + uint64_t nv[2]; + struct pl pl; + int64_t age; + unsigned i; + + if (!nonce || !nonce->p || nonce->l < NONCE_MIN_SIZE) + return EINVAL; + + pl = *nonce; + + for (i=0; isecret; + + md5((uint8_t *)nv, sizeof(nv), ckey); + + if (memcmp(nkey, ckey, MD5_SIZE)) + return EAUTH; + + age = time(NULL) - nv[0]; + + if (age < 0 || age > expires) + return ETIMEDOUT; + + return 0; +} + + +int auth_set_realm(struct auth *auth, const char *realm) +{ + size_t len; + + if (!auth || !realm) + return EINVAL; + + len = strlen(realm); + if (len >= sizeof(auth->realm)) + return ENOMEM; + + memcpy(auth->realm, realm, len); + auth->realm[len] = '\0'; + + return 0; +} diff --git a/test/sip/domain.c b/test/sip/domain.c new file mode 100644 index 0000000..063e3b9 --- /dev/null +++ b/test/sip/domain.c @@ -0,0 +1,187 @@ +/** + * @file sip/domain.c Mock SIP server -- domain handling + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include +#include "sipsrv.h" + + +#define DEBUG_MODULE "mock/sipsrv" +#define DEBUG_LEVEL 5 +#include + + +enum { + NONCE_EXPIRES = 300, +}; + + +static void destructor(void *arg) +{ + struct domain *dom = arg; + + hash_unlink(&dom->he); + hash_flush(dom->ht_usr); + mem_deref(dom->ht_usr); + mem_deref(dom->name); +} + + +static struct domain *lookup(struct sip_server *srv, const struct pl *name) +{ + struct list *lst; + struct le *le; + + lst = hash_list(srv->ht_dom, hash_joaat_ci(name->p, name->l)); + + for (le=list_head(lst); le; le=le->next) { + + struct domain *dom = le->data; + + if (pl_strcasecmp(name, dom->name)) + continue; + + return dom; + } + + return NULL; +} + + +int domain_add(struct sip_server *srv, const char *name) +{ + struct domain *dom; + int err; + + dom = mem_zalloc(sizeof(*dom), destructor); + if (!dom) + return ENOMEM; + + err = str_dup(&dom->name, name); + if (err) + goto out; + + err = hash_alloc(&dom->ht_usr, 32); + if (err) + return err; + + hash_append(srv->ht_dom, hash_joaat_str_ci(name), &dom->he, dom); + + out: + if (err) + mem_deref(dom); + + return err; +} + + +int domain_find(struct sip_server *srv, const struct uri *uri) +{ + int err = ENOENT; + struct sa addr; + + if (!uri) + return EINVAL; + + if (!sa_set(&addr, &uri->host, uri->port)) { + + if (!uri->port) { + + uint16_t port = SIP_PORT; + + if (!pl_strcasecmp(&uri->scheme, "sips")) + port = SIP_PORT_TLS; + + sa_set_port(&addr, port); + } + + if (sip_transp_isladdr(srv->sip, SIP_TRANSP_NONE, &addr)) + return 0; + + return ENOENT; + } + + err = lookup(srv, &uri->host) ? 0 : ENOENT; + + return err; +} + + +int domain_auth(struct sip_server *srv, + const struct uri *uri, bool user_match, + const struct sip_msg *msg, enum sip_hdrid hdrid, + struct auth *auth) +{ + struct domain *dom; + struct list *lst; + struct le *le; + int err = ENOENT; + + if (!uri || !msg || !auth) + return EINVAL; + + dom = lookup(srv, &uri->host); + if (!dom) { + DEBUG_WARNING("domain not found (%r)\n", &uri->host); + return ENOENT; + } + + err = auth_set_realm(auth, dom->name); + if (err) + return err; + + auth->stale = false; + + lst = hash_list(msg->hdrht, hdrid); + + for (le=list_head(lst); le; le=le->next) { + + const struct sip_hdr *hdr = le->data; + struct httpauth_digest_resp resp; + const struct user *usr; + + if (hdr->id != hdrid) + continue; + + if (httpauth_digest_response_decode(&resp, &hdr->val)) + continue; + + if (pl_strcasecmp(&resp.realm, dom->name)) + continue; + + if (auth_chk_nonce(srv, &resp.nonce, NONCE_EXPIRES)) { + auth->stale = true; + continue; + } + + auth->stale = false; + + usr = user_find(dom->ht_usr, &resp.username); + if (!usr) { + DEBUG_WARNING("user not found (%r)\n", &resp.username); + break; + } + + err = httpauth_digest_response_auth(&resp, &msg->met,usr->ha1); + if (err) + return err; + + if (user_match && pl_cmp(&resp.username, &uri->user)) + return EPERM; + + return 0; + } + + return EAUTH; +} + + +struct domain *domain_lookup(struct sip_server *srv, const char *name) +{ + struct pl pl; + + pl_set_str(&pl, name); + + return lookup(srv, &pl); +} diff --git a/test/sip/location.c b/test/sip/location.c new file mode 100644 index 0000000..be2bd66 --- /dev/null +++ b/test/sip/location.c @@ -0,0 +1,188 @@ +/** + * @file sip/location.c Mock SIP server -- location handling + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include +#include +#include "sipsrv.h" + + +struct loctmp { + struct sa src; + struct uri duri; + char *uri; + char *callid; + uint32_t expires; + uint32_t cseq; + double q; +}; + + +static void destructor_loctmp(void *arg) +{ + struct loctmp *tmp = arg; + + mem_deref(tmp->uri); + mem_deref(tmp->callid); +} + + +static void destructor_location(void *arg) +{ + struct location *loc = arg; + + list_unlink(&loc->le); + mem_deref(loc->uri); + mem_deref(loc->callid); + mem_deref(loc->tmp); +} + + +static bool cmp_handler(struct le *le, void *arg) +{ + struct location *loc = le->data; + + return uri_cmp(&loc->duri, arg); +} + + +int location_update(struct list *locl, const struct sip_msg *msg, + const struct sip_addr *contact, uint32_t expires) +{ + struct location *loc, *loc_new = NULL; + struct loctmp *tmp; + struct pl pl; + int err; + + if (!locl || !msg || !contact) + return EINVAL; + + loc = list_ledata(list_apply(locl, true, cmp_handler, + (void *)&contact->uri)); + if (!loc) { + if (expires == 0) + return 0; + + loc = loc_new = mem_zalloc(sizeof(*loc), destructor_location); + if (!loc) + return ENOMEM; + + list_append(locl, &loc->le, loc); + } + else { + if (!pl_strcmp(&msg->callid, loc->callid) && + msg->cseq.num <= loc->cseq) + return EPROTO; + + if (expires == 0) { + loc->rm = true; + return 0; + } + } + + tmp = mem_zalloc(sizeof(*tmp), destructor_loctmp); + if (!tmp) { + err = ENOMEM; + goto out; + } + + err = pl_strdup(&tmp->uri, &contact->auri); + if (err) + goto out; + + pl_set_str(&pl, tmp->uri); + + if (uri_decode(&tmp->duri, &pl)) { + err = EBADMSG; + goto out; + } + + err = pl_strdup(&tmp->callid, &msg->callid); + if (err) + goto out; + + + if (!msg_param_decode(&contact->params, "q", &pl)) + tmp->q = pl_float(&pl); + else + tmp->q = 1; + + tmp->cseq = msg->cseq.num; + tmp->expires = expires; + tmp->src = msg->src; + + out: + if (err) { + mem_deref(loc_new); + mem_deref(tmp); + } + else { + mem_deref(loc->tmp); + loc->tmp = tmp; + } + + return err; +} + + +void location_commit(struct list *locl) +{ + time_t now = time(NULL); + struct le *le; + + if (!locl) + return; + + for (le=locl->head; le; ) { + + struct location *loc = le->data; + + le = le->next; + + if (loc->rm) { + list_unlink(&loc->le); + mem_deref(loc); + } + else if (loc->tmp) { + + mem_deref(loc->uri); + mem_deref(loc->callid); + + loc->uri = mem_ref(loc->tmp->uri); + loc->callid = mem_ref(loc->tmp->callid); + loc->duri = loc->tmp->duri; + loc->cseq = loc->tmp->cseq; + loc->expires = loc->tmp->expires + now; + loc->src = loc->tmp->src; + loc->q = loc->tmp->q; + + loc->tmp = mem_deref(loc->tmp); + } + } +} + + +void location_rollback(struct list *locl) +{ + struct le *le; + + if (!locl) + return; + + for (le=locl->head; le; ) { + + struct location *loc = le->data; + + le = le->next; + + if (!loc->uri) { + list_unlink(&loc->le); + mem_deref(loc); + } + else { + loc->tmp = mem_deref(loc->tmp); + loc->rm = false; + } + } +} diff --git a/test/sip/sipsrv.c b/test/sip/sipsrv.c new file mode 100644 index 0000000..8d9bd8e --- /dev/null +++ b/test/sip/sipsrv.c @@ -0,0 +1,329 @@ +/** + * @file mock/sipsrv.c Mock SIP server + * + * Copyright (C) 2010 - 2015 Creytiv.com + */ +#include +#include +#include +#include +#include "../test.h" +#include "sipsrv.h" + + +#define DEBUG_MODULE "mock/sipsrv" +#define DEBUG_LEVEL 5 +#include + + +#define LOCAL_PORT 0 +#define LOCAL_SECURE_PORT 0 +#define EXPIRES_MIN 60 +#define EXPIRES_MAX 3600 + + +static int print_contact(struct re_printf *pf, const struct aor *aor) +{ + const uint64_t now = (uint64_t)time(NULL); + struct le *le; + int err = 0; + + for (le=aor->locl.head; le; le=le->next) { + + const struct location *loc = le->data; + + if (loc->expires < now) + continue; + + err |= re_hprintf(pf, "Contact: <%s>;expires=%lli\r\n", + loc->uri, loc->expires - now); + } + + return err; +} + + +static bool handle_register(struct sip_server *srv, const struct sip_msg *msg) +{ + struct auth auth = { srv, "", false }; + struct sip *sip = srv->sip; + struct list *lst; + struct aor *aor; + struct le *le; + int err; + + /* Request URI */ + err = domain_find(srv, &msg->uri); + if (err) { + if (err == ENOENT) { + warning("domain not found\n"); + return false; + } + + sip_reply(sip, msg, 500, strerror(err)); + error("domain find error: %s\n", strerror(err)); + return true; + } + + /* Authorize */ + if (srv->auth_enabled) + err = domain_auth(srv, &msg->to.uri, true, + msg, SIP_HDR_AUTHORIZATION, &auth); + else + err = domain_find(srv, &msg->to.uri); + + if (err && err != EAUTH) { + DEBUG_NOTICE("domain auth/find error: %m\n", err); + } + + switch (err) { + + case 0: + break; + + case EAUTH: + sip_replyf(sip, msg, 401, "Unauthorized", + "WWW-Authenticate: %H\r\n" + "Content-Length: 0\r\n\r\n", + auth_print, &auth); + return true; + + case EPERM: + sip_reply(sip, msg, 403, "Forbidden"); + return true; + + case ENOENT: + sip_reply(sip, msg, 404, "Not Found"); + return true; + + default: + sip_reply(sip, msg, 500, strerror(err)); + error("domain error: %s\n", strerror(err)); + return true; + } + + /* Find AoR */ + err = aor_find(srv, &aor, &msg->to.uri); + if (err) { + if (err != ENOENT) { + sip_reply(sip, msg, 500, strerror(err)); + error("aor find error: %s\n", strerror(err)); + return true; + } + + err = aor_create(srv, &aor, &msg->to.uri); + if (err) { + sip_reply(sip, msg, 500, strerror(err)); + error("aor create error: %s\n", strerror(err)); + return true; + } + } + + /* Process Contacts */ + lst = hash_list(msg->hdrht, SIP_HDR_CONTACT); + + for (le=list_head(lst); le; le=le->next) { + + const struct sip_hdr *hdr = le->data; + struct sip_addr contact; + uint32_t expires; + struct pl pl; + + if (hdr->id != SIP_HDR_CONTACT) + continue; + + err = sip_addr_decode(&contact, &hdr->val); + if (err) { + sip_reply(sip, msg, 400, "Bad Contact"); + goto fail; + } + + if (!msg_param_decode(&contact.params, "expires", &pl)) + expires = pl_u32(&pl); + else if (pl_isset(&msg->expires)) + expires = pl_u32(&msg->expires); + else + expires = 3600; + + if (expires > 0 && expires < EXPIRES_MIN) { + sip_replyf(sip, msg, 423, "Interval Too Brief", + "Min-Expires: %u\r\n" + "Content-Length: 0\r\n\r\n", + EXPIRES_MIN); + goto fail; + } + + expires = min(expires, EXPIRES_MAX); + + err = location_update(&aor->locl, msg, &contact, expires); + if (err) { + sip_reply(sip, msg, 500, strerror(err)); + if (err != EPROTO) + error("location update error: %s\n", + strerror(err)); + goto fail; + } + } + + location_commit(&aor->locl); + + sip_treplyf(NULL, NULL, sip, msg, false, 200, "OK", + "%H" + "Date: %H\r\n" + "Content-Length: 0\r\n\r\n", + print_contact, aor, + fmt_gmtime, NULL); + + return true; + + fail: + location_rollback(&aor->locl); + + return true; +} + + +static bool sip_msg_handler(const struct sip_msg *msg, void *arg) +{ + struct sip_server *srv = arg; + int err = 0; + +#if 0 + DEBUG_NOTICE("recv %r via %s\n", &msg->met, sip_transp_name(msg->tp)); +#endif + + srv->tp_last = msg->tp; + + if (0 == pl_strcmp(&msg->met, "REGISTER")) { + ++srv->n_register_req; + if (handle_register(srv, msg)) + goto out; + + sip_reply(srv->sip, msg, 503, "Server Error"); + } + else { + DEBUG_NOTICE("method not handled (%r)\n", &msg->met); + return false; + } + + if (srv->terminate) + err = sip_reply(srv->sip, msg, 503, "Server Error"); + + if (err) { + DEBUG_WARNING("could not reply: %m\n", err); + } + + out: + if (srv->terminate) + re_cancel(); + + return true; +} + + +static void destructor(void *arg) +{ + struct sip_server *srv = arg; + + srv->terminate = true; + + sip_close(srv->sip, false); + mem_deref(srv->sip); + + hash_flush(srv->ht_aor); + mem_deref(srv->ht_aor); + + hash_flush(srv->ht_dom); + mem_deref(srv->ht_dom); +} + + +int sip_server_alloc(struct sip_server **srvp) +{ + struct sip_server *srv; + struct sa laddr, laddrs; + struct tls *tls = NULL; + int err; + + if (!srvp) + return EINVAL; + + srv = mem_zalloc(sizeof *srv, destructor); + if (!srv) + return ENOMEM; + + err = sa_set_str(&laddr, "127.0.0.1", LOCAL_PORT); + err |= sa_set_str(&laddrs, "127.0.0.1", LOCAL_SECURE_PORT); + if (err) + goto out; + + err = sip_alloc(&srv->sip, NULL, 16, 16, 16, + "mock SIP server", NULL, NULL); + if (err) + goto out; + + err |= sip_transp_add(srv->sip, SIP_TRANSP_UDP, &laddr); + err |= sip_transp_add(srv->sip, SIP_TRANSP_TCP, &laddr); + if (err) + goto out; + +#ifdef USE_TLS + err = tls_alloc(&tls, TLS_METHOD_SSLV23, NULL, NULL); + if (err) + goto out; + + err = tls_set_certificate(tls, test_certificate, + strlen(test_certificate)); + if (err) + goto out; + + err |= sip_transp_add(srv->sip, SIP_TRANSP_TLS, &laddrs, tls); +#endif + if (err) + goto out; + + err = sip_listen(&srv->lsnr, srv->sip, true, sip_msg_handler, srv); + if (err) + goto out; + + srv->secret = rand_u64(); + + err = hash_alloc(&srv->ht_dom, 32); + if (err) + goto out; + + err = hash_alloc(&srv->ht_aor, 32); + if (err) + goto out; + + out: + mem_deref(tls); + if (err) + mem_deref(srv); + else + *srvp = srv; + + return err; +} + + +int sip_server_uri(struct sip_server *srv, char *uri, size_t sz, + enum sip_transp tp) +{ + struct sa laddr; + int err; + + if (!srv || !uri || !sz) + return EINVAL; + + err = sip_transp_laddr(srv->sip, &laddr, tp, NULL); + if (err) + return err; + + /* NOTE: angel brackets needed to parse ;transport parameter */ + if (re_snprintf(uri, sz, "", + &laddr, sip_transp_param(tp)) < 0) + return ENOMEM; + + return 0; +} diff --git a/test/sip/sipsrv.h b/test/sip/sipsrv.h new file mode 100644 index 0000000..4690ffe --- /dev/null +++ b/test/sip/sipsrv.h @@ -0,0 +1,121 @@ +/** + * @file mock/sipsrv.h Mock SIP server -- interface + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ + + +struct auth; + + +/* + * SIP Server + */ + +struct sip_server { + struct sip *sip; + struct sip_lsnr *lsnr; + bool auth_enabled; + bool terminate; + + unsigned n_register_req; + enum sip_transp tp_last; + + uint64_t secret; + struct hash *ht_dom; + struct hash *ht_aor; +}; + +int sip_server_alloc(struct sip_server **srvp); +int sip_server_uri(struct sip_server *srv, char *uri, size_t sz, + enum sip_transp tp); + + +/* + * AoR + */ + +struct aor { + struct le he; + struct list locl; + char *uri; +}; + +int aor_create(struct sip_server *srv, struct aor **aorp, + const struct uri *uri); +int aor_find(struct sip_server *srv, struct aor **aorp, + const struct uri *uri); + + +/* + * Auth + */ + +struct auth { + const struct sip_server *srv; + char realm[256]; + bool stale; +}; + +int auth_print(struct re_printf *pf, const struct auth *auth); +int auth_chk_nonce(struct sip_server *srv, + const struct pl *nonce, uint32_t expires); +int auth_set_realm(struct auth *auth, const char *realm); + + +/* + * Domain + */ + +struct domain { + struct le he; + struct hash *ht_usr; + char *name; +}; + + +int domain_add(struct sip_server *srv, const char *name); +int domain_find(struct sip_server *srv, const struct uri *uri); +int domain_auth(struct sip_server *srv, + const struct uri *uri, bool user_match, + const struct sip_msg *msg, enum sip_hdrid hdrid, + struct auth *auth); +struct domain *domain_lookup(struct sip_server *srv, const char *name); + + +/* + * Location + */ + +struct location { + struct le le; + struct sa src; + struct uri duri; + char *uri; + char *callid; + struct loctmp *tmp; + uint64_t expires; + uint32_t cseq; + double q; + bool rm; +}; + +int location_update(struct list *locl, const struct sip_msg *msg, + const struct sip_addr *contact, uint32_t expires); +void location_commit(struct list *locl); +void location_rollback(struct list *locl); + + +/* + * User + */ + +struct user { + struct le he; + uint8_t ha1[MD5_SIZE]; + char *name; +}; + +int user_add(struct hash *ht, const char *username, const char *password, + const char *realm); +struct user *user_find(struct hash *ht, const struct pl *name); diff --git a/test/sip/user.c b/test/sip/user.c new file mode 100644 index 0000000..99fb47d --- /dev/null +++ b/test/sip/user.c @@ -0,0 +1,73 @@ +/** + * @file sip/user.c Mock SIP server -- user handling + * + * Copyright (C) 2010 - 2016 Creytiv.com + */ +#include +#include "sipsrv.h" + + +static void destructor(void *arg) +{ + struct user *usr = arg; + + hash_unlink(&usr->he); + mem_deref(usr->name); +} + + +int user_add(struct hash *ht, const char *username, const char *password, + const char *realm) +{ + struct user *usr; + int err; + + usr = mem_zalloc(sizeof(*usr), destructor); + if (!usr) { + err = ENOMEM; + goto out; + } + + err = str_dup(&usr->name, username); + if (err) { + goto out; + } + + err = md5_printf(usr->ha1, "%s:%s:%s", username, realm, password); + if (err) { + goto out; + } + + hash_append(ht, hash_joaat_str(username), &usr->he, usr); + + out: + if (err) { + mem_deref(usr); + } + + return err; +} + + +struct user *user_find(struct hash *ht, const struct pl *name) +{ + struct list *lst; + struct le *le; + + if (!ht || !name) + return NULL; + + lst = hash_list(ht, hash_joaat((uint8_t *)name->p, name->l)); + + for (le=list_head(lst); le; le=le->next) { + + struct user *usr = le->data; + + if (pl_strcmp(name, usr->name)) + continue; + + return usr; + } + + return NULL; +} -- cgit v1.2.3