summaryrefslogtreecommitdiff
path: root/test/sip
diff options
context:
space:
mode:
authorAlfred E. Heggestad <aeh@db.org>2016-04-03 17:35:05 +0200
committerAlfred E. Heggestad <aeh@db.org>2016-04-03 17:35:05 +0200
commit31baf6937aee6f449a6675e102d6f0831bfb1fbe (patch)
treeb31e792b04b746497cb42df90dfb07ce8d0a7035 /test/sip
parent059b6a9f597419adc331ca7e1e2b40b090d907a1 (diff)
test: added test-case for authenticated register
Diffstat (limited to 'test/sip')
-rw-r--r--test/sip/aor.c98
-rw-r--r--test/sip/auth.c91
-rw-r--r--test/sip/domain.c187
-rw-r--r--test/sip/location.c188
-rw-r--r--test/sip/sipsrv.c329
-rw-r--r--test/sip/sipsrv.h121
-rw-r--r--test/sip/user.c73
7 files changed, 1087 insertions, 0 deletions
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 <string.h>
+#include <re.h>
+#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 <string.h>
+#include <time.h>
+#include <re.h>
+#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; i<sizeof(nkey); i++) {
+ nkey[i] = ch_hex(*pl.p++) << 4;
+ nkey[i] += ch_hex(*pl.p++);
+ pl.l -= 2;
+ }
+
+ nv[0] = pl_x64(&pl);
+ nv[1] = srv->secret;
+
+ 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 <re.h>
+#include "sipsrv.h"
+
+
+#define DEBUG_MODULE "mock/sipsrv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+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 <time.h>
+#include <re.h>
+#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 <string.h>
+#include <time.h>
+#include <re.h>
+#include <baresip.h>
+#include "../test.h"
+#include "sipsrv.h"
+
+
+#define DEBUG_MODULE "mock/sipsrv"
+#define DEBUG_LEVEL 5
+#include <re_dbg.h>
+
+
+#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, "<sip:x:x@%J%s>",
+ &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 <re.h>
+#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;
+}