/*
* Copyright (C) 2014 Red Hat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct method_ctx {
main_server_st *s;
void *pool;
} method_ctx;
static void method_status(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size);
static void method_list_users(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size);
static void method_disconnect_user_name(method_ctx *ctx, int cfd,
uint8_t * msg, unsigned msg_size);
static void method_disconnect_user_id(method_ctx *ctx, int cfd,
uint8_t * msg, unsigned msg_size);
static void method_stop(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size);
static void method_reload(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size);
static void method_user_info(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size);
static void method_id_info(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size);
typedef void (*method_func) (method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size);
typedef struct {
char *name;
unsigned cmd;
method_func func;
} ctl_method_st;
#define ENTRY(cmd, func) \
{#cmd, cmd, func}
static const ctl_method_st methods[] = {
ENTRY(CTL_CMD_STATUS, method_status),
ENTRY(CTL_CMD_RELOAD, method_reload),
ENTRY(CTL_CMD_STOP, method_stop),
ENTRY(CTL_CMD_LIST, method_list_users),
ENTRY(CTL_CMD_USER_INFO, method_user_info),
ENTRY(CTL_CMD_ID_INFO, method_id_info),
ENTRY(CTL_CMD_DISCONNECT_NAME, method_disconnect_user_name),
ENTRY(CTL_CMD_DISCONNECT_ID, method_disconnect_user_id),
{NULL, 0, NULL}
};
void ctl_handler_deinit(main_server_st * s)
{
if (s->config->use_occtl == 0)
return;
if (s->ctl_fd >= 0) {
/*mslog(s, NULL, LOG_DEBUG, "closing unix socket connection");*/
close(s->ctl_fd);
/*remove(OCSERV_UNIX_NAME); */
}
}
/* Initializes unix socket and stores the fd.
*/
int ctl_handler_init(main_server_st * s)
{
int ret;
struct sockaddr_un sa;
int sd, e;
if (s->config->use_occtl == 0 || s->config->occtl_socket_file == NULL)
return 0;
mslog(s, NULL, LOG_DEBUG, "initializing control unix socket: %s", s->config->occtl_socket_file);
memset(&sa, 0, sizeof(sa));
sa.sun_family = AF_UNIX;
strlcpy(sa.sun_path, s->config->occtl_socket_file, sizeof(sa.sun_path));
remove(s->config->occtl_socket_file);
sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sd == -1) {
e = errno;
mslog(s, NULL, LOG_ERR, "could not create socket '%s': %s",
s->config->occtl_socket_file, strerror(e));
return -1;
}
umask(066);
ret = bind(sd, (struct sockaddr *)&sa, SUN_LEN(&sa));
if (ret == -1) {
e = errno;
mslog(s, NULL, LOG_ERR, "could not bind socket '%s': %s",
s->config->occtl_socket_file, strerror(e));
return -1;
}
ret = chown(s->config->occtl_socket_file, s->config->uid, s->config->gid);
if (ret == -1) {
e = errno;
mslog(s, NULL, LOG_ERR, "could not chown socket '%s': %s",
s->config->occtl_socket_file, strerror(e));
}
ret = listen(sd, 1024);
if (ret == -1) {
e = errno;
mslog(s, NULL, LOG_ERR, "could not listen to socket '%s': %s",
s->config->occtl_socket_file, strerror(e));
return -1;
}
s->ctl_fd = sd;
return sd;
}
static void method_status(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size)
{
StatusRep rep = STATUS_REP__INIT;
int ret;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: status");
rep.status = 1;
rep.pid = getpid();
rep.start_time = ctx->s->start_time;
rep.sec_mod_pid = ctx->s->sec_mod_pid;
rep.active_clients = ctx->s->active_clients;
rep.stored_cookies = ctx->s->cookies.total;
rep.stored_tls_sessions = ctx->s->tls_db.entries;
ret = send_msg(ctx->pool, cfd, CTL_CMD_STATUS_REP, &rep,
(pack_size_func) status_rep__get_packed_size,
(pack_func) status_rep__pack);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR, "error sending ctl reply");
}
return;
}
static void method_reload(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size)
{
BoolMsg rep = BOOL_MSG__INIT;
int ret;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: reload");
request_reload(0);
rep.status = 1;
ret = send_msg(ctx->pool, cfd, CTL_CMD_RELOAD_REP, &rep,
(pack_size_func) bool_msg__get_packed_size,
(pack_func) bool_msg__pack);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR, "error sending ctl reply");
}
return;
}
static void method_stop(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size)
{
BoolMsg rep = BOOL_MSG__INIT;
int ret;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: stop");
request_stop(0);
rep.status = 1;
ret = send_msg(ctx->pool, cfd, CTL_CMD_STOP_REP, &rep,
(pack_size_func) bool_msg__get_packed_size,
(pack_func) bool_msg__pack);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR, "error sending ctl reply");
}
return;
}
#define IPBUF_SIZE 64
static int append_user_info(method_ctx *ctx,
UserListRep * list,
struct proc_st *ctmp, unsigned single)
{
uint32_t tmp;
char *ipbuf;
char *strtmp;
UserInfoRep *rep;
list->user =
talloc_realloc(ctx->pool, list->user, UserInfoRep *, (1 + list->n_user));
if (list->user == NULL)
return -1;
rep = list->user[list->n_user] = talloc(ctx->pool, UserInfoRep);
if (rep == NULL)
return -1;
list->n_user++;
user_info_rep__init(rep);
/* ID: pid */
rep->id = ctmp->pid;
rep->username = ctmp->username;
rep->groupname = ctmp->groupname;
ipbuf = talloc_size(ctx->pool, IPBUF_SIZE);
if (ipbuf == NULL)
return -1;
strtmp =
human_addr2((struct sockaddr *)&ctmp->remote_addr,
ctmp->remote_addr_len, ipbuf, IPBUF_SIZE, 0);
if (strtmp == NULL)
strtmp = "";
rep->ip = strtmp;
rep->tun = ctmp->tun_lease.name;
ipbuf = talloc_size(ctx->pool, IPBUF_SIZE);
if (ipbuf == NULL)
return -1;
strtmp = NULL;
if (ctmp->ipv4 != NULL)
strtmp =
human_addr2((struct sockaddr *)&ctmp->ipv4->rip,
ctmp->ipv4->rip_len, ipbuf, IPBUF_SIZE, 0);
if (strtmp == NULL)
strtmp = "";
rep->local_ip = strtmp;
ipbuf = talloc_size(ctx->pool, IPBUF_SIZE);
if (ipbuf == NULL)
return -1;
strtmp = NULL;
if (ctmp->ipv4 != NULL)
strtmp =
human_addr2((struct sockaddr *)&ctmp->ipv4->lip,
ctmp->ipv4->lip_len, ipbuf, IPBUF_SIZE, 0);
if (strtmp == NULL)
strtmp = "";
rep->remote_ip = strtmp;
/* IPv6 */
ipbuf = talloc_size(ctx->pool, IPBUF_SIZE);
if (ipbuf == NULL)
return -1;
strtmp = NULL;
if (ctmp->ipv6 != NULL)
strtmp =
human_addr2((struct sockaddr *)&ctmp->ipv6->rip,
ctmp->ipv6->rip_len, ipbuf, IPBUF_SIZE, 0);
if (strtmp == NULL)
strtmp = "";
rep->local_ip6 = strtmp;
ipbuf = talloc_size(ctx->pool, IPBUF_SIZE);
if (ipbuf == NULL)
return -1;
strtmp = NULL;
if (ctmp->ipv6 != NULL)
strtmp =
human_addr2((struct sockaddr *)&ctmp->ipv6->lip,
ctmp->ipv6->lip_len, ipbuf, IPBUF_SIZE, 0);
if (strtmp == NULL)
strtmp = "";
rep->remote_ip6 = strtmp;
rep->conn_time = ctmp->conn_time;
rep->hostname = ctmp->hostname;
rep->user_agent = ctmp->user_agent;
if (ctmp->status == PS_AUTH_COMPLETED)
strtmp = "connected";
else if (ctmp->status == PS_AUTH_INIT)
strtmp = "auth";
else if (ctmp->status == PS_AUTH_INACTIVE)
strtmp = "pre-auth";
else if (ctmp->status == PS_AUTH_FAILED)
strtmp = "auth failed";
else
strtmp = "unknown";
rep->status = strtmp;
rep->tls_ciphersuite = ctmp->tls_ciphersuite;
rep->dtls_ciphersuite = ctmp->dtls_ciphersuite;
rep->cstp_compr = ctmp->cstp_compr;
rep->dtls_compr = ctmp->dtls_compr;
if (ctmp->mtu > 0) {
rep->mtu = ctmp->mtu;
rep->has_mtu = 1;
}
if (single > 0) {
if (ctmp->config.rx_per_sec > 0)
tmp = ctmp->config.rx_per_sec;
else
tmp = ctx->s->config->rx_per_sec;
tmp *= 1000;
rep->rx_per_sec = tmp;
if (ctmp->config.tx_per_sec > 0)
tmp = ctmp->config.tx_per_sec;
else
tmp = ctx->s->config->tx_per_sec;
tmp *= 1000;
rep->tx_per_sec = tmp;
if (ctmp->config.dns_size > 0) {
rep->dns = ctmp->config.dns;
rep->n_dns = ctmp->config.dns_size;
} else {
rep->dns = ctx->s->config->network.dns;
rep->n_dns = ctx->s->config->network.dns_size;
}
if (ctmp->config.nbns_size > 0) {
rep->nbns = ctmp->config.nbns;
rep->n_nbns = ctmp->config.nbns_size;
} else {
rep->nbns = ctx->s->config->network.nbns;
rep->n_nbns = ctx->s->config->network.nbns_size;
}
if (ctmp->config.routes_size > 0) {
rep->routes = ctmp->config.routes;
rep->n_routes = ctmp->config.routes_size;
} else {
rep->routes = ctx->s->config->network.routes;
rep->n_routes = ctx->s->config->network.routes_size;
}
if (ctmp->config.iroutes_size > 0) {
rep->iroutes = ctmp->config.iroutes;
rep->n_iroutes = ctmp->config.iroutes_size;
}
}
return 0;
}
static void method_list_users(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size)
{
UserListRep rep = USER_LIST_REP__INIT;
struct proc_st *ctmp = NULL;
int ret;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: list-users");
list_for_each(&ctx->s->proc_list.head, ctmp, list) {
ret = append_user_info(ctx, &rep, ctmp, 0);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR,
"error appending user info to reply");
goto error;
}
}
ret = send_msg(ctx->pool, cfd, CTL_CMD_LIST_REP, &rep,
(pack_size_func) user_list_rep__get_packed_size,
(pack_func) user_list_rep__pack);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR, "error sending ctl reply");
}
error:
return;
}
static void single_info_common(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size, const char *user, unsigned id)
{
UserListRep rep = USER_LIST_REP__INIT;
int ret;
unsigned found_user = 0;
struct proc_st *ctmp = NULL;
if (user != NULL)
mslog(ctx->s, NULL, LOG_INFO, "providing info for user '%s'", user);
else
mslog(ctx->s, NULL, LOG_INFO, "providing info for ID '%u'", id);
list_for_each(&ctx->s->proc_list.head, ctmp, list) {
if (user == NULL) { /* id */
if (id == 0 || id == -1 || id != ctmp->pid) {
continue;
}
} else { /* username */
if (strcmp(ctmp->username, user) != 0) {
continue;
}
}
ret = append_user_info(ctx, &rep, ctmp, 1);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR,
"error appending user info to reply");
goto error;
}
found_user = 1;
if (id != 0) /* id -> one a single element */
break;
}
if (found_user == 0) {
if (user != NULL)
mslog(ctx->s, NULL, LOG_INFO, "could not find user '%s'",
user);
else
mslog(ctx->s, NULL, LOG_INFO, "could not find ID '%u'", id);
}
ret = send_msg(ctx->pool, cfd, CTL_CMD_LIST_REP, &rep,
(pack_size_func) user_list_rep__get_packed_size,
(pack_func) user_list_rep__pack);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR, "error sending ctl reply");
}
error:
return;
}
static void method_user_info(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size)
{
UsernameReq *req;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: user_info (name)");
req = username_req__unpack(NULL, msg_size, msg);
if (req == NULL) {
mslog(ctx->s, NULL, LOG_ERR, "error parsing user_info request");
return;
}
single_info_common(ctx, cfd, msg, msg_size, req->username, 0);
username_req__free_unpacked(req, NULL);
return;
}
static void method_id_info(method_ctx *ctx, int cfd, uint8_t * msg,
unsigned msg_size)
{
IdReq *req;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: user_info (id)");
req = id_req__unpack(NULL, msg_size, msg);
if (req == NULL) {
mslog(ctx->s, NULL, LOG_ERR, "error parsing id_info request");
return;
}
single_info_common(ctx, cfd, msg, msg_size, NULL, req->id);
id_req__free_unpacked(req, NULL);
return;
}
static void method_disconnect_user_name(method_ctx *ctx,
int cfd, uint8_t * msg,
unsigned msg_size)
{
UsernameReq *req;
BoolMsg rep = BOOL_MSG__INIT;
struct proc_st *cpos;
struct proc_st *ctmp = NULL;
int ret;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: disconnect_name");
req = username_req__unpack(NULL, msg_size, msg);
if (req == NULL) {
mslog(ctx->s, NULL, LOG_ERR,
"error parsing disconnect_name request");
return;
}
/* got the name. Try to disconnect */
list_for_each_safe(&ctx->s->proc_list.head, ctmp, cpos, list) {
if (strcmp(ctmp->username, req->username) == 0) {
remove_proc(ctx->s, ctmp, 1);
rep.status = 1;
}
}
username_req__free_unpacked(req, NULL);
ret = send_msg(ctx->pool, cfd, CTL_CMD_DISCONNECT_NAME_REP, &rep,
(pack_size_func) bool_msg__get_packed_size,
(pack_func) bool_msg__pack);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR, "error sending ctl reply");
}
return;
}
static void method_disconnect_user_id(method_ctx *ctx, int cfd,
uint8_t * msg, unsigned msg_size)
{
IdReq *req;
BoolMsg rep = BOOL_MSG__INIT;
struct proc_st *cpos;
struct proc_st *ctmp = NULL;
int ret;
mslog(ctx->s, NULL, LOG_DEBUG, "ctl: disconnect_id");
req = id_req__unpack(NULL, msg_size, msg);
if (req == NULL) {
mslog(ctx->s, NULL, LOG_ERR, "error parsing disconnect_id request");
return;
}
/* got the ID. Try to disconnect */
list_for_each_safe(&ctx->s->proc_list.head, ctmp, cpos, list) {
if (ctmp->pid == req->id) {
remove_proc(ctx->s, ctmp, 1);
rep.status = 1;
if (req->id != -1)
break;
}
}
/* reply */
id_req__free_unpacked(req, NULL);
ret = send_msg(ctx->pool, cfd, CTL_CMD_DISCONNECT_ID_REP, &rep,
(pack_size_func) bool_msg__get_packed_size,
(pack_func) bool_msg__pack);
if (ret < 0) {
mslog(ctx->s, NULL, LOG_ERR, "error sending ctl reply");
}
return;
}
static void ctl_handle_commands(main_server_st * s)
{
int cfd = -1, e, ret;
unsigned i;
struct sockaddr_un sa;
socklen_t sa_len;
uint16_t length;
uint8_t buffer[256];
unsigned buffer_size;
method_ctx ctx;
void *pool = talloc_new(s);
if (pool == NULL) {
mslog(s, NULL, LOG_ERR, "memory allocation error");
return;
}
ctx.s = s;
ctx.pool = pool;
sa_len = sizeof(sa);
cfd = accept(s->ctl_fd, (struct sockaddr *)&sa, &sa_len);
if (cfd == -1) {
e = errno;
mslog(s, NULL, LOG_ERR,
"error accepting control connection: %s", strerror(e));
goto cleanup;
}
ret = check_upeer_id("ctl", s->config->debug, cfd, 0, 0, NULL);
if (ret < 0) {
mslog(s, NULL, LOG_ERR, "ctl: unauthorized connection");
goto cleanup;
}
/* read request */
ret = recv(cfd, buffer, sizeof(buffer), 0);
if (ret < 3) {
if (ret == -1) {
e = errno;
mslog(s, NULL, LOG_ERR, "error receiving ctl data: %s",
strerror(e));
} else {
mslog(s, NULL, LOG_ERR, "received ctl data: %d bytes",
ret);
}
goto cleanup;
}
memcpy(&length, &buffer[1], 2);
buffer_size = ret - 3;
if (length != buffer_size) {
mslog(s, NULL, LOG_ERR,
"received data length doesn't match received data (%d/%d)",
buffer_size, (int)length);
goto cleanup;
}
for (i = 0;; i++) {
if (methods[i].cmd == 0) {
mslog(s, NULL, LOG_INFO,
"unknown unix ctl message: 0x%.1x",
(unsigned)buffer[0]);
break;
} else if (methods[i].cmd == buffer[0]) {
methods[i].func(&ctx, cfd, buffer + 3, buffer_size);
break;
}
}
cleanup:
talloc_free(pool);
if (cfd != -1)
close(cfd);
}
int ctl_handler_set_fds(main_server_st * s, fd_set * rd_set, fd_set * wr_set)
{
if (s->config->use_occtl == 0)
return -1;
FD_SET(s->ctl_fd, rd_set);
return s->ctl_fd;
}
void ctl_handler_run_pending(main_server_st* s, fd_set *rd_set, fd_set *wr_set)
{
if (s->config->use_occtl == 0)
return;
if (FD_ISSET(s->ctl_fd, rd_set)) {
ctl_handle_commands(s);
}
}