#include "config.h"
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <System/net/pfkeyv2.h>
#include <netinet/in.h>
#ifndef HAVE_NETINET6_IPSEC
#include <netinet/ipsec.h>
#else
#include <netinet6/ipsec.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <launch.h>
#include "var.h"
#include "misc.h"
#include "vmbuf.h"
#include "plog.h"
#include "sockmisc.h"
#include "debug.h"
#include "schedule.h"
#include "localconf.h"
#include "remoteconf.h"
#include "grabmyaddr.h"
#include "isakmp_var.h"
#include "isakmp.h"
#include "oakley.h"
#include "handler.h"
#include "evt.h"
#include "pfkey.h"
#include "ipsec_doi.h"
#include "vpn_control.h"
#include "vpn_control_var.h"
#include "isakmp_inf.h"
#include "session.h"
#include "gcmalloc.h"
#ifdef ENABLE_VPNCONTROL_PORT
char *vpncontrolsock_path = VPNCONTROLSOCK_PATH;
uid_t vpncontrolsock_owner = 0;
gid_t vpncontrolsock_group = 0;
mode_t vpncontrolsock_mode = 0600;
static struct sockaddr_un sunaddr;
static int vpncontrol_process(struct vpnctl_socket_elem *, char *);
static int vpncontrol_reply(int, char *);
static void vpncontrol_close_comm(struct vpnctl_socket_elem *);
static int checklaunchd();
extern int vpn_get_config __P((struct ph1handle *, struct vpnctl_status_phase_change **, size_t *));
extern int vpn_xauth_reply __P((u_int32_t, void *, size_t));
int
checklaunchd()
{
launch_data_t checkin_response = NULL;
launch_data_t checkin_request = NULL;
launch_data_t sockets_dict, listening_fd_array;
launch_data_t listening_fd;
struct sockaddr_storage fdsockaddr;
socklen_t fdsockaddrlen = sizeof(fdsockaddr);
int socketct;
int i;
int listenerct;
int returnval = 0;
int fd;
if ((checkin_request = launch_data_new_string(LAUNCH_KEY_CHECKIN)) == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to launch_data_new_string.\n");
goto done;
}
if ((checkin_response = launch_msg(checkin_request)) == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to launch_msg.\n");
goto done;
}
if (LAUNCH_DATA_ERRNO == launch_data_get_type(checkin_response)) {
plog(LLV_ERROR, LOCATION, NULL,
"launch_data_get_type error %d\n",
launch_data_get_errno(checkin_response));
goto done;
}
if ( (sockets_dict = launch_data_dict_lookup(checkin_response, LAUNCH_JOBKEY_SOCKETS)) == NULL){
plog(LLV_ERROR, LOCATION, NULL,
"failed to launch_data_dict_lookup.\n");
goto done;
}
if ( !(socketct = launch_data_dict_get_count(sockets_dict))){
plog(LLV_ERROR, LOCATION, NULL,
"launch_data_dict_get_count returns no socket defined.\n");
goto done;
}
if ( (listening_fd_array = launch_data_dict_lookup(sockets_dict, "Listeners")) == NULL ){
plog(LLV_ERROR, LOCATION, NULL,
"failed to launch_data_dict_lookup.\n");
goto done;
}
listenerct = launch_data_array_get_count(listening_fd_array);
for (i = 0; i < listenerct; i++) {
listening_fd = launch_data_array_get_index(listening_fd_array, i);
fd = launch_data_get_fd( listening_fd );
if ( getsockname( fd , (struct sockaddr*)&fdsockaddr, &fdsockaddrlen)){
continue;
}
if ( (((struct sockaddr*)&fdsockaddr)->sa_family) == AF_UNIX &&
(!(strcmp(vpncontrolsock_path, ((struct sockaddr_un *)&fdsockaddr)->sun_path))))
{
plog(LLV_INFO, LOCATION, NULL,
"found launchd socket.\n");
returnval = fd;
break;
}
}
if ( listenerct == i){
plog(LLV_ERROR, LOCATION, NULL,
"failed to find launchd socket\n");
returnval = 0;
}
done:
if (checkin_request)
launch_data_free(checkin_request);
if (checkin_response)
launch_data_free(checkin_response);
return(returnval);
}
int
vpncontrol_handler()
{
struct sockaddr_storage from;
socklen_t fromlen = sizeof(from);
struct vpnctl_socket_elem *sock_elem;
sock_elem = racoon_malloc(sizeof(struct vpnctl_socket_elem));
if (sock_elem == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"memory error: %s\n", strerror(errno));
return -1;
}
LIST_INIT(&sock_elem->bound_addresses);
sock_elem->sock = accept(lcconf->sock_vpncontrol, (struct sockaddr *)&from, &fromlen);
if (sock_elem->sock < 0) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to accept vpn_control command: %s\n", strerror(errno));
racoon_free(sock_elem);
return -1;
}
LIST_INSERT_HEAD(&lcconf->vpnctl_comm_socks, sock_elem, chain);
plog(LLV_NOTIFY, LOCATION, NULL,
"accepted connection on vpn control socket.\n");
check_auto_exit();
return 0;
}
int
vpncontrol_comm_handler(struct vpnctl_socket_elem *elem)
{
struct vpnctl_hdr hdr;
char *combuf = NULL;
int len;
while ((len = recv(elem->sock, (char *)&hdr, sizeof(hdr), MSG_PEEK)) < 0) {
if (errno == EINTR)
continue;
plog(LLV_ERROR, LOCATION, NULL,
"failed to recv vpn_control command: %s\n", strerror(errno));
goto end;
}
if (len == 0) {
plog(LLV_DEBUG, LOCATION, NULL,
"vpn_control socket closed by peer.\n");
vpncontrol_close_comm(elem);
return -1;
}
if (len < sizeof(hdr)) {
plog(LLV_ERROR, LOCATION, NULL,
"invalid header length of vpn_control command - len=%d - expected %d\n", len, sizeof(hdr));
goto end;
}
if ((combuf = racoon_malloc(ntohs(hdr.len) + sizeof(hdr))) == 0) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to alloc buffer for vpn_control command\n");
goto end;
}
while ((len = recv(elem->sock, combuf, ntohs(hdr.len) + sizeof(hdr), 0)) < 0) {
if (errno == EINTR)
continue;
plog(LLV_ERROR, LOCATION, NULL,
"failed to recv vpn_control command: %s\n",
strerror(errno));
goto end;
}
(void)vpncontrol_process(elem, combuf);
end:
if (combuf)
racoon_free(combuf);
return 0; }
static int
vpncontrol_process(struct vpnctl_socket_elem *elem, char *combuf)
{
u_int16_t error = 0;
struct vpnctl_hdr *hdr = (struct vpnctl_hdr *)combuf;
switch (ntohs(hdr->msg_type)) {
case VPNCTL_CMD_BIND:
{
struct vpnctl_cmd_bind *pkt = (struct vpnctl_cmd_bind *)combuf;
struct bound_addr *addr;
plog(LLV_DEBUG, LOCATION, NULL,
"received bind command on vpn control socket.\n");
addr = racoon_calloc(1, sizeof(struct bound_addr));
if (addr == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"memory error: %s\n", strerror(errno));
error = -1;
break;
}
if (ntohs(pkt->vers_len)) {
addr->version = vmalloc(ntohs(pkt->vers_len));
if (addr->version == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"memory error: %s\n", strerror(errno));
error = -1;
break;
}
memcpy(addr->version->v, pkt + 1, ntohs(pkt->vers_len));
}
addr->address = pkt->address;
LIST_INSERT_HEAD(&elem->bound_addresses, addr, chain);
lcconf->auto_exit_state |= LC_AUTOEXITSTATE_CLIENT;
}
break;
case VPNCTL_CMD_UNBIND:
{
struct vpnctl_cmd_unbind *pkt = (struct vpnctl_cmd_unbind *)combuf;
struct bound_addr *addr;
struct bound_addr *t_addr;
plog(LLV_DEBUG, LOCATION, NULL,
"received unbind command on vpn control socket.\n");
LIST_FOREACH_SAFE(addr, &elem->bound_addresses, chain, t_addr) {
if (pkt->address == 0xFFFFFFFF ||
pkt->address == addr->address) {
flushsainfo_dynamic(addr->address);
LIST_REMOVE(addr, chain);
if (addr->version)
vfree(addr->version);
racoon_free(addr);
}
}
}
break;
case VPNCTL_CMD_REDIRECT:
{
struct vpnctl_cmd_redirect *redirect_msg = (struct vpnctl_cmd_redirect *)combuf;
struct redirect *raddr;
struct redirect *t_raddr;
int found = 0;
plog(LLV_DEBUG, LOCATION, NULL,
"received redirect command on vpn control socket - address = %x.\n", ntohl(redirect_msg->redirect_address));
LIST_FOREACH_SAFE(raddr, &lcconf->redirect_addresses, chain, t_raddr) {
if (raddr->cluster_address == redirect_msg->address) {
if (redirect_msg->redirect_address == 0) {
LIST_REMOVE(raddr, chain);
racoon_free(raddr);
} else {
raddr->redirect_address = redirect_msg->redirect_address;
raddr->force = ntohs(redirect_msg->force);
}
found = 1;
break;
}
}
if (!found) {
raddr = racoon_malloc(sizeof(struct redirect));
if (raddr == NULL) {
plog(LLV_DEBUG, LOCATION, NULL,
"cannot allcoate memory for redirect address.\n");
error = -1;
break;
}
raddr->cluster_address = redirect_msg->address;
raddr->redirect_address = redirect_msg->redirect_address;
raddr->force = ntohs(redirect_msg->force);
LIST_INSERT_HEAD(&lcconf->redirect_addresses, raddr, chain);
}
}
break;
case VPNCTL_CMD_PING:
break;
case VPNCTL_CMD_XAUTH_INFO:
{
struct vpnctl_cmd_xauth_info *pkt = (struct vpnctl_cmd_xauth_info *)combuf;
struct bound_addr *addr;
struct bound_addr *t_addr;
void *attr_list;
plog(LLV_DEBUG, LOCATION, NULL,
"received xauth info command vpn control socket.\n");
LIST_FOREACH_SAFE(addr, &elem->bound_addresses, chain, t_addr) {
if (pkt->address == addr->address) {
attr_list = pkt + 1;
error = vpn_xauth_reply(pkt->address, attr_list, ntohs(pkt->hdr.len) - sizeof(u_int32_t));
break;
}
}
}
break;
case VPNCTL_CMD_CONNECT:
{
struct vpnctl_cmd_connect *pkt = (struct vpnctl_cmd_connect *)combuf;
struct bound_addr *addr;
struct bound_addr *t_addr;
plog(LLV_DEBUG, LOCATION, NULL,
"received connect command on vpn control socket.\n");
LIST_FOREACH_SAFE(addr, &elem->bound_addresses, chain, t_addr) {
if (pkt->address == addr->address) {
error = vpn_connect(addr);
break;
}
}
}
break;
case VPNCTL_CMD_DISCONNECT:
{
struct vpnctl_cmd_connect *pkt = (struct vpnctl_cmd_connect *)combuf;
struct bound_addr *addr;
struct bound_addr *t_addr;
plog(LLV_DEBUG, LOCATION, NULL,
"received disconnect command on vpn control socket.\n");
LIST_FOREACH_SAFE(addr, &elem->bound_addresses, chain, t_addr) {
if (pkt->address == addr->address) {
error = vpn_disconnect(addr);
break;
}
}
}
break;
case VPNCTL_CMD_START_PH2:
{
struct vpnctl_cmd_start_ph2 *pkt = (struct vpnctl_cmd_start_ph2 *)combuf;
struct bound_addr *addr;
struct bound_addr *t_addr;
plog(LLV_DEBUG, LOCATION, NULL,
"received start_ph2 command on vpn control socket.\n");
plogdump(LLV_DEBUG2, pkt, ntohs(hdr->len) + sizeof(struct vpnctl_hdr));
LIST_FOREACH_SAFE(addr, &elem->bound_addresses, chain, t_addr) {
if (pkt->address == addr->address) {
error = vpn_start_ph2(addr, pkt);
break;
}
}
}
break;
case VPNCTL_CMD_START_DPD:
{
struct vpnctl_cmd_start_dpd *pkt = (struct vpnctl_cmd_start_dpd *)combuf;
struct bound_addr *srv;
struct bound_addr *t_addr;
plog(LLV_DEBUG, LOCATION, NULL,
"received start_dpd command on vpn control socket.\n");
LIST_FOREACH_SAFE(srv, &elem->bound_addresses, chain, t_addr) {
if (pkt->address == srv->address) {
struct sockaddr_in daddr;
bzero(&daddr, sizeof(daddr));
daddr.sin_len = sizeof(daddr);
daddr.sin_addr.s_addr = srv->address;
daddr.sin_port = 0;
daddr.sin_family = AF_INET;
error = ph1_force_dpd(&daddr);
break;
}
}
}
break;
default:
plog(LLV_ERROR, LOCATION, NULL,
"invalid command: %d\n", ntohs(hdr->msg_type));
error = -1; break;
}
hdr->len = 0;
hdr->result = htons(error);
if (vpncontrol_reply(elem->sock, combuf) < 0)
return -1;
return 0;
}
static int
vpncontrol_reply(int so, char *combuf)
{
size_t tlen;
tlen = send(so, combuf, sizeof(struct vpnctl_hdr), 0);
if (tlen < 0) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to send vpn_control message: %s\n", strerror(errno));
return -1;
}
return 0;
}
int
vpncontrol_notify_need_authinfo(struct ph1handle *iph1, void* attr_list, size_t attr_len)
{
struct vpnctl_status_need_authinfo *msg = NULL;
struct vpnctl_socket_elem *sock_elem;
struct bound_addr *bound_addr;
size_t tlen, msg_size;
u_int32_t address;
void *ptr;
if (!iph1)
goto end;
plog(LLV_DEBUG, LOCATION, NULL,
"sending vpn_control xauth need info status\n");
msg = (struct vpnctl_status_need_authinfo *)racoon_malloc(msg_size = sizeof(struct vpnctl_status_need_authinfo) + attr_len);
if (msg == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"unable to allocate space for vpn control message.\n");
return -1;
}
msg->hdr.flags = 0;
if (iph1->remote->sa_family == AF_INET)
address = ((struct sockaddr_in *)iph1->remote)->sin_addr.s_addr;
else
goto end;
msg->hdr.cookie = msg->hdr.reserved = msg->hdr.result = 0;
msg->hdr.len = htons((msg_size) - sizeof(struct vpnctl_hdr));
if (!ike_session_is_client_ph1_rekey(iph1)) {
msg->hdr.msg_type = htons(VPNCTL_STATUS_NEED_AUTHINFO);
} else {
msg->hdr.msg_type = htons(VPNCTL_STATUS_NEED_REAUTHINFO);
}
msg->address = address;
ptr = msg + 1;
memcpy(ptr, attr_list, attr_len);
LIST_FOREACH(sock_elem, &lcconf->vpnctl_comm_socks, chain) {
LIST_FOREACH(bound_addr, &sock_elem->bound_addresses, chain) {
if (bound_addr->address == 0xFFFFFFFF ||
bound_addr->address == address) {
plog(LLV_DEBUG, LOCATION, NULL,
"vpn control writing %d bytes\n", msg_size);
plogdump(LLV_DEBUG, msg, msg_size);
tlen = send(sock_elem->sock, msg, msg_size, 0);
if (tlen < 0) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to send vpn_control need authinfo status: %s\n", strerror(errno));
}
break;
}
}
}
end:
if (msg)
racoon_free(msg);
return 0;
}
int
vpncontrol_notify_ike_failed(u_int16_t notify_code, u_int16_t from, u_int32_t address, u_int16_t data_len, u_int8_t *data)
{
struct vpnctl_status_failed *msg = NULL;
struct vpnctl_socket_elem *sock_elem;
struct bound_addr *bound_addr;
size_t tlen, len;
len = sizeof(struct vpnctl_status_failed) + data_len;
msg = (struct vpnctl_status_failed *)racoon_malloc(len);
if (msg == NULL) {
plog(LLV_DEBUG, LOCATION, NULL,
"unable to allcate memory for vpn control status message.\n");
return -1;
}
msg->hdr.msg_type = htons(VPNCTL_STATUS_IKE_FAILED);
msg->hdr.flags = msg->hdr.cookie = msg->hdr.reserved = msg->hdr.result = 0;
msg->hdr.len = htons(len - sizeof(struct vpnctl_hdr));
msg->address = address;
msg->ike_code = htons(notify_code);
msg->from = htons(from);
if (data_len > 0)
memcpy(msg->data, data, data_len);
plog(LLV_DEBUG, LOCATION, NULL,
"sending vpn_control ike failed message - code=%d from=%s.\n", notify_code,
(from == FROM_LOCAL ? "local" : "remote"));
LIST_FOREACH(sock_elem, &lcconf->vpnctl_comm_socks, chain) {
LIST_FOREACH(bound_addr, &sock_elem->bound_addresses, chain) {
if (bound_addr->address == 0xFFFFFFFF ||
bound_addr->address == address) {
tlen = send(sock_elem->sock, msg, len, 0);
if (tlen < 0) {
plog(LLV_ERROR, LOCATION, NULL,
"unable to send vpn_control ike notify failed: %s\n", strerror(errno));
}
break;
}
}
}
if (msg)
racoon_free(msg);
return 0;
}
int
vpncontrol_notify_phase_change(int start, u_int16_t from, struct ph1handle *iph1, struct ph2handle *iph2)
{
struct vpnctl_status_phase_change *msg;
struct vpnctl_socket_elem *sock_elem;
struct bound_addr *bound_addr;
size_t tlen, msg_size;
u_int32_t address;
plog(LLV_DEBUG, LOCATION, NULL,
"sending vpn_control phase change status\n");
if (iph1 && !start && iph1->mode_cfg) {
if (vpn_get_config(iph1, &msg, &msg_size) == 1)
return 0;
} else {
msg = racoon_malloc(msg_size = sizeof(struct vpnctl_status_phase_change));
msg->hdr.flags = 0;
}
if (msg == NULL) {
plog(LLV_ERROR, LOCATION, NULL,
"unable to allocate space for vpn control message.\n");
return -1;
}
if (iph1) {
if (iph1->remote->sa_family == AF_INET)
address = ((struct sockaddr_in *)iph1->remote)->sin_addr.s_addr;
else
goto end; msg->hdr.msg_type = htons(start ?
(from == FROM_LOCAL ? VPNCTL_STATUS_PH1_START_US : VPNCTL_STATUS_PH1_START_PEER)
: VPNCTL_STATUS_PH1_ESTABLISHED);
} else {
if (iph2->dst->sa_family == AF_INET)
address = ((struct sockaddr_in *)iph2->dst)->sin_addr.s_addr;
else
goto end; msg->hdr.msg_type = htons(start ? VPNCTL_STATUS_PH2_START : VPNCTL_STATUS_PH2_ESTABLISHED);
}
msg->hdr.cookie = msg->hdr.reserved = msg->hdr.result = 0;
msg->hdr.len = htons((msg_size) - sizeof(struct vpnctl_hdr));
msg->address = address;
LIST_FOREACH(sock_elem, &lcconf->vpnctl_comm_socks, chain) {
LIST_FOREACH(bound_addr, &sock_elem->bound_addresses, chain) {
if (bound_addr->address == 0xFFFFFFFF ||
bound_addr->address == address) {
plog(LLV_DEBUG, LOCATION, NULL,
"vpn control writing %d bytes\n", msg_size);
plogdump(LLV_DEBUG, msg, msg_size);
tlen = send(sock_elem->sock, msg, msg_size, 0);
if (tlen < 0) {
plog(LLV_ERROR, LOCATION, NULL,
"failed to send vpn_control phase change status: %s\n", strerror(errno));
}
break;
}
}
}
end:
if (msg)
racoon_free(msg);
return 0;
}
int
vpncontrol_init()
{
if (vpncontrolsock_path == NULL) {
lcconf->sock_vpncontrol = -1;
return 0;
}
if ( (lcconf->sock_vpncontrol = checklaunchd()) ){
return 0;
}
else {
memset(&sunaddr, 0, sizeof(sunaddr));
sunaddr.sun_family = AF_UNIX;
snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path),
"%s", vpncontrolsock_path);
lcconf->sock_vpncontrol = socket(AF_UNIX, SOCK_STREAM, 0);
if (lcconf->sock_vpncontrol == -1) {
plog(LLV_ERROR, LOCATION, NULL,
"socket: %s\n", strerror(errno));
return -1;
}
unlink(sunaddr.sun_path);
if (bind(lcconf->sock_vpncontrol, (struct sockaddr *)&sunaddr,
sizeof(sunaddr)) != 0) {
plog(LLV_ERROR, LOCATION, NULL,
"bind(sockname:%s): %s\n",
sunaddr.sun_path, strerror(errno));
(void)close(lcconf->sock_vpncontrol);
return -1;
}
if (chown(sunaddr.sun_path, vpncontrolsock_owner, vpncontrolsock_group) != 0) {
plog(LLV_ERROR, LOCATION, NULL,
"chown(%s, %d, %d): %s\n",
sunaddr.sun_path, vpncontrolsock_owner,
vpncontrolsock_group, strerror(errno));
(void)close(lcconf->sock_vpncontrol);
return -1;
}
if (chmod(sunaddr.sun_path, vpncontrolsock_mode) != 0) {
plog(LLV_ERROR, LOCATION, NULL,
"chmod(%s, 0%03o): %s\n",
sunaddr.sun_path, vpncontrolsock_mode, strerror(errno));
(void)close(lcconf->sock_vpncontrol);
return -1;
}
if (listen(lcconf->sock_vpncontrol, 5) != 0) {
plog(LLV_ERROR, LOCATION, NULL,
"listen(sockname:%s): %s\n",
sunaddr.sun_path, strerror(errno));
(void)close(lcconf->sock_vpncontrol);
return -1;
}
plog(LLV_DEBUG, LOCATION, NULL,
"opened %s as racoon management.\n", sunaddr.sun_path);
return 0;
}
}
void
vpncontrol_close()
{
struct vpnctl_socket_elem *elem;
struct vpnctl_socket_elem *t_elem;
plog(LLV_DEBUG, LOCATION, NULL,
"vpncontrol_close.\n");
if (lcconf->sock_vpncontrol != -1) {
close(lcconf->sock_vpncontrol);
lcconf->sock_vpncontrol = -1;
}
LIST_FOREACH_SAFE(elem, &lcconf->vpnctl_comm_socks, chain, t_elem)
vpncontrol_close_comm(elem);
}
static void
vpncontrol_close_comm(struct vpnctl_socket_elem *elem)
{
struct bound_addr *addr;
struct bound_addr *t_addr;
plog(LLV_DEBUG, LOCATION, NULL,
"vpncontrol_close_comm.\n");
LIST_REMOVE(elem, chain);
if (elem->sock != -1)
close(elem->sock);
LIST_FOREACH_SAFE(addr, &elem->bound_addresses, chain, t_addr) {
flushsainfo_dynamic(addr->address);
LIST_REMOVE(addr, chain);
if (addr->version)
vfree(addr->version);
racoon_free(addr);
}
racoon_free(elem);
check_auto_exit();
}
int
vpn_control_connected(void)
{
if (LIST_EMPTY(&lcconf->vpnctl_comm_socks))
return 0;
else
return 1;
}
#endif