static bool sharedsecret_auth_calc_ha1(const struct stun_attr *user, const uint8_t *secret, const size_t secret_length, uint8_t *key) { uint8_t expected[SHA_DIGEST_LENGTH]; char expected_base64[SHA_DIGEST_LENGTH/2*3]; size_t b64len; uint8_t ha1[MD5_SIZE]; int retval; if (!secret_length) { /* restund_warning("auth: calc_ha1 no secret length %s\n", secret); */ return false; } hmac_sha1(secret, secret_length, (uint8_t *) user->v.username, strlen(user->v.username), expected, SHA_DIGEST_LENGTH); b64len = sizeof expected_base64; if ((retval = base64_encode(expected, SHA_DIGEST_LENGTH, expected_base64, &b64len)) != 0) { restund_warning("auth: failed to base64 encode hmac, error %d\n", retval); return false; } expected_base64[b64len] = 0; if ((retval = md5_printf(ha1, "%s:%s:%s", user->v.username, restund_realm(), expected_base64)) != 0) { restund_warning("auth: failed to md5_printf ha1, error %d\n", retval); return false; } memcpy(key, &ha1, MD5_SIZE); return true; }
static int module_init(void) { struct sa laddr_udp, laddr_http; struct pl addr; uint32_t port; int err; /* UDP bind address */ if (conf_get(restund_conf(), "status_udp_addr", &addr)) pl_set_str(&addr, "127.0.0.1"); if (conf_get_u32(restund_conf(), "status_udp_port", &port)) port = 33000; err = sa_set(&laddr_udp, &addr, port); if (err) { restund_error("status: bad udp bind address: %r:%u", &addr, port); goto out; } /* HTTP bind address */ if (conf_get(restund_conf(), "status_http_addr", &addr)) pl_set_str(&addr, "127.0.0.1"); if (conf_get_u32(restund_conf(), "status_http_port", &port)) port = 8080; err = sa_set(&laddr_http, &addr, port); if (err) { restund_error("status: bad http bind address: %r:%u", &addr, port); goto out; } err = udp_listen(&stg.us, &laddr_udp, udp_recv, NULL); if (err) { restund_warning("status: udp_listen: %m\n", err); goto out; } err = httpd_alloc(&stg.httpd, &laddr_http, httpd_handler); if (err) { restund_warning("status: httpd: %m\n", err); goto out; } stg.start = time(NULL); restund_debug("status: module loaded (udp=%J http=%J)\n", &laddr_udp, &laddr_http); out: if (err) { stg.us = mem_deref(stg.us); stg.httpd = mem_deref(stg.httpd); } return err; }
static int listen_handler(const struct pl *addrport, void *arg) { uint32_t sockbuf_size = *(uint32_t *)arg; struct udp_lstnr *ul = NULL; int err = ENOMEM; ul = mem_zalloc(sizeof(*ul), destructor); if (!ul) { restund_warning("udp listen error: %s\n", strerror(err)); goto out; } list_append(&lstnrl, &ul->le, ul); err = sa_decode(&ul->bnd_addr, addrport->p, addrport->l); if (err || sa_is_any(&ul->bnd_addr) || !sa_port(&ul->bnd_addr)) { restund_warning("bad udp_listen directive: '%r'\n", addrport); err = EINVAL; goto out; } err = udp_listen(&ul->us, &ul->bnd_addr, udp_recv, ul); if (err) { restund_warning("udp listen %J: %s\n", &ul->bnd_addr, strerror(err)); goto out; } if (sockbuf_size > 0) (void)udp_sockbuf_set(ul->us, sockbuf_size); restund_debug("udp listen: %J\n", &ul->bnd_addr); out: if (err) mem_deref(ul); return err; }
static int accounts_getall(const char *realm, restund_db_account_h *acch, void *arg) { MYSQL_RES *res; int err = 0; if (!realm || !acch) return EINVAL; switch (my.version) { case 2: err = query(&res, "SELECT auth_username, ha1 " "FROM credentials WHERE realm = '%s';", realm); break; default: err = query(&res, "SELECT username, ha1 " "FROM subscriber where domain = '%s';", realm); break; } if (err) { restund_warning("mysql: unable to select accounts: %s\n", mysql_error(&my.mysql)); return err; } for (;!err;) { MYSQL_ROW row; row = mysql_fetch_row(res); if (!row) break; err = acch(row[0] ? row[0] : "", row[1] ? row[1] : "", arg); } mysql_free_result(res); return err; }
static int accounts_count(const char *realm, uint32_t *n) { MYSQL_RES *res; MYSQL_ROW row; int err = 0; if (!realm || !n) return EINVAL; switch (my.version) { case 2: err = query(&res, "SELECT COUNT(*) " "FROM credentials WHERE realm = '%s';", realm); break; default: err = query(&res, "SELECT COUNT(*) " "FROM subscriber where domain = '%s';", realm); break; } if (err) { restund_warning("mysql: unable to select nr of accounts: %s\n", mysql_error(&my.mysql)); return err; } row = mysql_fetch_row(res); if (row) *n = atoi(row[0]); else err = ENOENT; mysql_free_result(res); return err; }
static int module_handler(const struct pl *val, void *arg) { struct pl *modpath = arg; char filepath[256]; struct mod *mod; int err; if (val->p && val->l && (*val->p == '/')) (void)re_snprintf(filepath, sizeof(filepath), "%r", val); else (void)re_snprintf(filepath, sizeof(filepath), "%r/%r", modpath, val); err = mod_load(&mod, filepath); if (err) { restund_warning("can't load module %s (%m)\n", filepath, err); goto out; } out: return err; }
void chanbind_request(struct allocation *al, struct restund_msgctx *ctx, int proto, void *sock, const struct sa *src, const struct stun_msg *msg) { struct chan *chan = NULL, *ch_numb = NULL, *ch_peer; struct perm *perm = NULL, *permx = NULL; struct stun_attr *chnr, *peer; int err = ENOMEM, rerr; chnr = stun_msg_attr(msg, STUN_ATTR_CHANNEL_NUMBER); peer = stun_msg_attr(msg, STUN_ATTR_XOR_PEER_ADDR); if (!chnr || !chan_numb_valid(chnr->v.channel_number) || !peer) { restund_info("turn: bad chanbind attributes\n"); rerr = stun_ereply(proto, sock, src, 0, msg, 400, "Bad Attributes", ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto out; } if (sa_af(&peer->v.xor_peer_addr) != sa_af(&al->rel_addr)) { restund_info("turn: chanbind peer address family mismatch\n"); rerr = stun_ereply(proto, sock, src, 0, msg, 443, "Peer Address Family Mismatch", ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto out; } ch_numb = chan_numb_find(al->chans, chnr->v.channel_number); ch_peer = chan_peer_find(al->chans, &peer->v.xor_peer_addr); if (ch_numb != ch_peer) { restund_info("turn: channel %p/peer %p already bound\n", ch_numb, ch_peer); rerr = stun_ereply(proto, sock, src, 0, msg, 400, "Channel/Peer Already Bound", ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto out; } if (!ch_numb) { chan = chan_create(al->chans, chnr->v.channel_number, &peer->v.xor_peer_addr, al); if (!chan) { restund_info("turn: unable to create channel\n"); rerr = stun_ereply(proto, sock, src, 0, msg, 500, "Server Error", ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE,restund_software); goto out; } } permx = perm_find(al->perms, &peer->v.xor_peer_addr); if (!permx) { perm = perm_create(al->perms, &peer->v.xor_peer_addr, al); if (!perm) { restund_info("turn: unable to create permission\n"); rerr = stun_ereply(proto, sock, src, 0, msg, 500, "Server Error", ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE,restund_software); goto out; } } err = rerr = stun_reply(proto, sock, src, 0, msg, ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); out: if (rerr) restund_warning("turn: chanbind reply: %m\n", rerr); if (err) { mem_deref(chan); mem_deref(perm); } else { chan_refresh(ch_numb); perm_refresh(permx); } }
static bool request_handler(struct restund_msgctx *ctx, int proto, void *sock, const struct sa *src, const struct sa *dst, const struct stun_msg *msg) { const uint16_t met = stun_msg_method(msg); struct allocation *al; int err = 0; switch (met) { case STUN_METHOD_ALLOCATE: case STUN_METHOD_REFRESH: case STUN_METHOD_CREATEPERM: case STUN_METHOD_CHANBIND: break; default: return false; } if (ctx->ua.typec > 0) { err = stun_ereply(proto, sock, src, 0, msg, 420, "Unknown Attribute", ctx->key, ctx->keylen, ctx->fp, 2, STUN_ATTR_UNKNOWN_ATTR, &ctx->ua, STUN_ATTR_SOFTWARE, restund_software); goto out; } al = allocation_find(proto, src, dst); if (!al && met != STUN_METHOD_ALLOCATE) { restund_debug("turn: allocation does not exist\n"); err = stun_ereply(proto, sock, src, 0, msg, 437, "Allocation Mismatch", ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto out; } if (al && al->username && ctx->key) { struct stun_attr *usr = stun_msg_attr(msg, STUN_ATTR_USERNAME); if (!usr || strcmp(usr->v.username, al->username)) { restund_debug("turn: wrong credetials\n"); err = stun_ereply(proto, sock, src, 0, msg, 441, "Wrong Credentials", ctx->key, ctx->keylen, ctx->fp, 1, STUN_ATTR_SOFTWARE,restund_software); goto out; } } switch (met) { case STUN_METHOD_ALLOCATE: allocate_request(&turnd, al, ctx, proto, sock, src, dst, msg); break; case STUN_METHOD_REFRESH: refresh_request(&turnd, al, ctx, proto, sock, src, msg); break; case STUN_METHOD_CREATEPERM: createperm_request(al, ctx, proto, sock, src, msg); break; case STUN_METHOD_CHANBIND: chanbind_request(al, ctx, proto, sock, src, msg); break; } out: if (err) { restund_warning("turn reply error: %m\n", err); } return true; }
static bool request_handler(struct restund_msgctx *ctx, int proto, void *sock, const struct sa *src, const struct sa *dst, const struct stun_msg *msg) { struct stun_attr *mi, *user, *realm, *nonce; const time_t now = time(NULL); char nstr[NONCE_MAX_SIZE + 1]; int err; (void)dst; if (ctx->key) return false; mi = stun_msg_attr(msg, STUN_ATTR_MSG_INTEGRITY); user = stun_msg_attr(msg, STUN_ATTR_USERNAME); realm = stun_msg_attr(msg, STUN_ATTR_REALM); nonce = stun_msg_attr(msg, STUN_ATTR_NONCE); if (!mi) { err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } if (!user || !realm || !nonce) { err = stun_ereply(proto, sock, src, 0, msg, 400, "Bad Request", NULL, 0, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto unauth; } if (!nonce_validate(nonce->v.nonce, now, src)) { err = stun_ereply(proto, sock, src, 0, msg, 438, "Stale Nonce", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } ctx->key = mem_alloc(MD5_SIZE, NULL); if (!ctx->key) { restund_warning("auth: can't to allocate memory for MI key\n"); err = stun_ereply(proto, sock, src, 0, msg, 500, "Server Error", NULL, 0, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto unauth; } ctx->keylen = MD5_SIZE; if (auth.sharedsecret_length > 0 || auth.sharedsecret2_length > 0) { if (!((sharedsecret_auth_calc_ha1(user, (uint8_t*) auth.sharedsecret, auth.sharedsecret_length, ctx->key) && !stun_msg_chk_mi(msg, ctx->key, ctx->keylen)) || (sharedsecret_auth_calc_ha1(user, (uint8_t*) auth.sharedsecret2, auth.sharedsecret2_length, ctx->key) && !stun_msg_chk_mi(msg, ctx->key, ctx->keylen)))) { restund_info("auth: shared secret auth for user '%s' (%j) failed\n", user->v.username, src); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } else { /* restund_info("auth: shared secret auth for user '%s' (%j) worked\n", user->v.username, src); */ if (STUN_METHOD_ALLOCATE == stun_msg_method(msg) && !sharedsecret_auth_check_timestamp(user, now)) { restund_info("auth: shared secret auth for user '%s' expired)\n", user->v.username); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } } } else if (restund_get_ha1(user->v.username, ctx->key)) { restund_info("auth: unknown user '%s' (%j)\n", user->v.username, src); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } if (stun_msg_chk_mi(msg, ctx->key, ctx->keylen)) { restund_info("auth: bad password for user '%s' (%j)\n", user->v.username, src); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } return false; unauth: if (err) { restund_warning("auth reply error: %m\n", err); } return true; }
int main(int argc, char *argv[]) { bool daemon = true; int err = 0; struct pl opt; (void)sys_coredump_set(true); #ifdef HAVE_GETOPT for (;;) { const int c = getopt(argc, argv, "dhnf:"); if (0 > c) break; switch (c) { case 'd': force_debug = true; restund_log_enable_debug(true); break; case 'f': configfile = optarg; break; case 'n': daemon = false; break; case '?': err = EINVAL; /*@fallthrough@*/ case 'h': usage(); return err; } } #else (void)argc; (void)argv; #endif restund_cmd_subscribe(&cmd_reload); err = fd_setsize(4096); if (err) { restund_warning("fd_setsize error: %m\n", err); goto out; } err = libre_init(); if (err) { restund_error("re init failed: %m\n", err); goto out; } /* configuration file */ err = conf_alloc(&conf, configfile); if (err) { restund_error("error loading configuration: %s: %m\n", configfile, err); goto out; } /* debug config */ if (!conf_get(conf, "debug", &opt) && !pl_strcasecmp(&opt, "yes")) restund_log_enable_debug(true); /* udp */ err = restund_udp_init(); if (err) goto out; /* tcp */ err = restund_tcp_init(); if (err) goto out; /* daemon config */ if (!conf_get(conf, "daemon", &opt) && !pl_strcasecmp(&opt, "no")) daemon = false; /* module config */ if (conf_get(conf, "module_path", &opt)) pl_set_str(&opt, "."); err = conf_apply(conf, "module", module_handler, &opt); if (err) goto out; /* daemon */ if (daemon) { err = sys_daemon(); if (err) { restund_error("daemon error: %m\n", err); goto out; } restund_log_enable_stderr(false); } /* database */ err = restund_db_init(); if (err) { restund_warning("database error: %m\n", err); goto out; } restund_info("stun server ready\n"); /* main loop */ err = re_main(signal_handler); out: restund_db_close(); mod_close(); restund_udp_close(); restund_tcp_close(); conf = mem_deref(conf); libre_close(); restund_cmd_unsubscribe(&cmd_reload); /* check for memory leaks */ tmr_debug(); mem_debug(); return err; }
static bool request_handler(struct restund_msgctx *ctx, int proto, void *sock, const struct sa *src, const struct sa *dst, const struct stun_msg *msg) { struct stun_attr *mi, *user, *realm, *nonce; const uint32_t now = (uint32_t)time(NULL); char nstr[NONCE_SIZE + 1]; int err; (void)dst; if (ctx->key) return false; mi = stun_msg_attr(msg, STUN_ATTR_MSG_INTEGRITY); user = stun_msg_attr(msg, STUN_ATTR_USERNAME); realm = stun_msg_attr(msg, STUN_ATTR_REALM); nonce = stun_msg_attr(msg, STUN_ATTR_NONCE); if (!mi) { err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } if (!user || !realm || !nonce) { err = stun_ereply(proto, sock, src, 0, msg, 400, "Bad Request", NULL, 0, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto unauth; } if (!nonce_validate(nonce->v.nonce, now, src)) { err = stun_ereply(proto, sock, src, 0, msg, 438, "Stale Nonce", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } ctx->key = mem_alloc(MD5_SIZE, NULL); if (!ctx->key) { restund_warning("auth: can't to allocate memory for MI key\n"); err = stun_ereply(proto, sock, src, 0, msg, 500, "Server Error", NULL, 0, ctx->fp, 1, STUN_ATTR_SOFTWARE, restund_software); goto unauth; } ctx->keylen = MD5_SIZE; if (restund_get_ha1(user->v.username, ctx->key)) { restund_info("auth: unknown user '%s'\n", user->v.username); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } if (stun_msg_chk_mi(msg, ctx->key, ctx->keylen)) { restund_info("auth: bad passwd for '%s'\n", user->v.username); err = stun_ereply(proto, sock, src, 0, msg, 401, "Unauthorized", NULL, 0, ctx->fp, 3, STUN_ATTR_REALM, restund_realm(), STUN_ATTR_NONCE, mknonce(nstr, now, src), STUN_ATTR_SOFTWARE, restund_software); goto unauth; } return false; unauth: if (err) { restund_warning("auth reply error: %s\n", strerror(err)); } return true; }