#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <net/pfkeyv2.h>
#include <netinet/in.h>
#include <ipsec_util.h>
#include <sys/sckm_io.h>
#ifdef SCKMD_DEBUG
#define OPT_STR "ds"
#else
#define OPT_STR ""
#endif
#define KM_DEV "/dev/kmdrv"
#define SCKMD_MAX_MSG_SIZE 1024
#define SCKMD_ERR_MSG_SIZE 512
#define SCKMD_MSG_HDR_SIZE sizeof (struct sadb_msg)
#define SCKMD_CURR_PFKEY_VER PF_KEY_V2
#define SCKMD_PFKEY_TIMEOUT 3000
static pid_t mypid;
static int standalone;
static int debug;
static int keysock;
static uint32_t seq = 0;
static uint64_t msg_buf[SCKMD_MAX_MSG_SIZE];
static int process_sckm_req(int fd, sckm_ioctl_getreq_t *msg);
static int send_sckm_status(int fd, sckm_ioctl_status_t *msg);
static int get_pfkey_reply(uint32_t req_seq, uint8_t req_type, int *err);
static struct sadb_msg *read_pfkey_msg(void);
static int convert_pfkey_msg(struct sadb_msg *msg);
static void sckmd_log(int priority, char *fmt, ...);
int
main(int argc, char **argv)
{
int opt;
int fd;
sckm_ioctl_getreq_t msg;
standalone = 0;
debug = 0;
mypid = getpid();
openlog("sckmd", LOG_CONS | LOG_NDELAY, LOG_DAEMON);
opterr = 0;
while ((opt = getopt(argc, argv, OPT_STR)) != EOF) {
switch (opt) {
case 'd':
debug++;
break;
case 's':
standalone++;
break;
default:
sckmd_log(LOG_ERR, "unknown command line option\n");
exit(1);
}
}
sckmd_log(LOG_DEBUG, "starting sckmd...\n");
if ((keysock = socket(PF_KEY, SOCK_RAW, SCKMD_CURR_PFKEY_VER)) == -1) {
sckmd_log(LOG_DEBUG, "PF_KEY open for IPsec load failed: %s\n",
strerror(errno));
exit(1);
}
(void) close(keysock);
sckmd_log(LOG_ERR, "PF_KEY socket for IPsec load succeeded.\n");
if (geteuid() != 0) {
sckmd_log(LOG_ERR, "must run as root\n");
exit(1);
}
if (standalone == 0) {
int i;
for (i = 0; i < NOFILE; i++) {
(void) close(i);
}
(void) chdir("/");
(void) umask(0);
if (fork() != 0) {
exit(0);
}
(void) setpgrp();
openlog("sckmd", LOG_CONS | LOG_NDELAY, LOG_DAEMON);
}
if ((fd = open(KM_DEV, O_RDONLY)) == -1) {
sckmd_log(LOG_ERR, "error initializing km driver: %s\n",
strerror(errno));
exit(1);
}
for (;;) {
(void) memset(&msg, 0, sizeof (sckm_ioctl_getreq_t));
msg.buf = (caddr_t)msg_buf;
(void) memset(&msg_buf, 0, SCKMD_MAX_MSG_SIZE);
msg.buf_len = SCKMD_MAX_MSG_SIZE;
if (ioctl(fd, SCKM_IOCTL_GETREQ, &msg) == -1) {
sckmd_log(LOG_ERR, "failed to receive sckm message: "
"%s\n", strerror(errno));
continue;
}
if (process_sckm_req(fd, &msg) == -1) {
sckmd_log(LOG_DEBUG, "error processing sckm message\n");
continue;
}
}
return (0);
}
static int
process_sckm_req(int fd, sckm_ioctl_getreq_t *msg)
{
sckm_ioctl_status_t reply;
struct sadb_msg *pfkey_msg;
unsigned int msg_ver;
unsigned int msg_type;
unsigned int msg_len;
int err;
if (msg == NULL) {
sckmd_log(LOG_ERR, "invalid message\n");
return (-1);
}
(void) memset(&reply, 0, sizeof (sckm_ioctl_status_t));
reply.transid = msg->transid;
if (msg->type != SCKM_IOCTL_REQ_SADB) {
sckmd_log(LOG_ERR, "unsupported message type (%d)\n",
msg->type);
reply.status = SCKM_IOCTL_STAT_ERR_REQ;
return (send_sckm_status(fd, &reply));
}
if (msg->buf_len < sizeof (struct sadb_msg)) {
sckmd_log(LOG_ERR, "incomplete sadb message received\n");
reply.status = SCKM_IOCTL_STAT_ERR_REQ;
return (send_sckm_status(fd, &reply));
}
pfkey_msg = (struct sadb_msg *)msg->buf;
msg_ver = pfkey_msg->sadb_msg_version;
msg_len = SADB_64TO8(pfkey_msg->sadb_msg_len);
msg_type = pfkey_msg->sadb_msg_type;
if ((msg_ver > SCKMD_CURR_PFKEY_VER) || (msg_ver < PF_KEY_V2)) {
sckmd_log(LOG_ERR, "unsupported PF_KEY version (%d)\n",
msg_ver);
reply.status = SCKM_IOCTL_STAT_ERR_VERSION;
reply.sadb_msg_version = SCKMD_CURR_PFKEY_VER;
return (send_sckm_status(fd, &reply));
}
if (msg_ver != SCKMD_CURR_PFKEY_VER) {
if (convert_pfkey_msg(pfkey_msg) == -1) {
reply.status = SCKM_IOCTL_STAT_ERR_VERSION;
reply.sadb_msg_version = SCKMD_CURR_PFKEY_VER;
return (send_sckm_status(fd, &reply));
}
}
pfkey_msg->sadb_msg_seq = ++seq;
pfkey_msg->sadb_msg_pid = mypid;
switch (msg_type) {
case SADB_UPDATE:
case SADB_ADD:
case SADB_DELETE:
break;
default:
sckmd_log(LOG_ERR, "received unsupported operation "
"from client (%d)\n", msg_type);
reply.status = SCKM_IOCTL_STAT_ERR_SADB_TYPE;
return (send_sckm_status(fd, &reply));
}
if ((keysock = socket(PF_KEY, SOCK_RAW, SCKMD_CURR_PFKEY_VER)) == -1) {
sckmd_log(LOG_ERR, "error initializing PF_KEY socket: %s\n",
strerror(errno));
reply.status = SCKM_IOCTL_STAT_ERR_OTHER;
return (send_sckm_status(fd, &reply));
}
if (write(keysock, pfkey_msg, msg_len) != msg_len) {
sckmd_log(LOG_ERR, "PF_KEY write failed\n");
reply.status = SCKM_IOCTL_STAT_ERR_OTHER;
close(keysock);
return (send_sckm_status(fd, &reply));
}
if (get_pfkey_reply(pfkey_msg->sadb_msg_seq, msg_type, &err) == -1) {
reply.status = err;
if (err == SCKM_IOCTL_STAT_ERR_PFKEY) {
reply.sadb_msg_errno = errno;
}
} else {
sckmd_log(LOG_DEBUG, "PF_KEY operation succeeded\n");
reply.status = SCKM_IOCTL_STAT_SUCCESS;
}
close(keysock);
return (send_sckm_status(fd, &reply));
}
static int
send_sckm_status(int fd, sckm_ioctl_status_t *msg)
{
if (ioctl(fd, SCKM_IOCTL_STATUS, msg) == -1) {
sckmd_log(LOG_ERR, "error sending sckm status message: %s\n",
strerror(errno));
return (-1);
}
return (0);
}
static int
get_pfkey_reply(uint32_t req_seq, uint8_t req_type, int *err)
{
int timeout;
int pollstatus;
clock_t before;
clock_t after;
double diff;
struct tms unused;
struct pollfd pfd;
struct sadb_msg *msg;
static char *pfkey_msg_type[] = {
"RESERVED",
"GETSPI",
"UPDATE",
"ADD",
"DELETE",
"GET",
"ACQUIRE",
"REGISTER",
"EXPIRE",
"FLUSH",
"DUMP",
"X_PROMISC",
"X_INVERSE_ACQUIRE",
};
sckmd_log(LOG_DEBUG, "waiting for key engine reply\n");
timeout = SCKMD_PFKEY_TIMEOUT;
pfd.fd = keysock;
pfd.events = POLLIN;
while (timeout > 0) {
before = times(&unused);
pfd.revents = 0;
pollstatus = poll(&pfd, 1, timeout);
if (pollstatus == 0) {
sckmd_log(LOG_NOTICE, "timed out waiting for PF_KEY "
"reply\n");
*err = SCKM_IOCTL_STAT_ERR_TIMEOUT;
return (-1);
}
msg = read_pfkey_msg();
if (msg == NULL) {
*err = SCKM_IOCTL_STAT_ERR_OTHER;
return (-1);
}
if (msg->sadb_msg_seq == req_seq &&
msg->sadb_msg_pid == mypid) {
break;
}
after = times(&unused);
diff = (double)(after - before)/(double)CLK_TCK;
timeout -= (int)(diff * 1000);
}
if (timeout <= 0) {
sckmd_log(LOG_NOTICE, "timed out waiting for PF_KEY "
"reply\n");
*err = SCKM_IOCTL_STAT_ERR_TIMEOUT;
return (-1);
}
if (msg->sadb_msg_type != req_type) {
sckmd_log(LOG_ERR, "unexpected message type from PF_KEY: %d\n",
msg->sadb_msg_type);
*err = SCKM_IOCTL_STAT_ERR_OTHER;
return (-1);
}
if ((msg->sadb_msg_errno != 0) && !((msg->sadb_msg_errno == ESRCH) &&
(msg->sadb_msg_type == SADB_DELETE))) {
char unknown_type_str[16];
int unknown_type = 0;
int arr_sz;
const char *diagnostic_str;
arr_sz = sizeof (pfkey_msg_type) / sizeof (*pfkey_msg_type);
if (msg->sadb_msg_type >= arr_sz) {
(void) snprintf(unknown_type_str,
sizeof (unknown_type_str), "UNKNOWN-%d",
msg->sadb_msg_type);
unknown_type = 1;
}
diagnostic_str = keysock_diag(msg->sadb_x_msg_diagnostic);
sckmd_log(LOG_ERR, "PF_KEY error: type=%s, errno=%d: %s, "
"diagnostic code=%d: %s\n",
(unknown_type) ? unknown_type_str :
pfkey_msg_type[msg->sadb_msg_type],
msg->sadb_msg_errno, strerror(msg->sadb_msg_errno),
msg->sadb_x_msg_diagnostic, diagnostic_str);
*err = SCKM_IOCTL_STAT_ERR_PFKEY;
errno = msg->sadb_msg_errno;
return (-1);
}
return (0);
}
static struct sadb_msg *
read_pfkey_msg(void)
{
static uint64_t *offset;
static int len;
struct sadb_msg *retval;
if ((offset == NULL) || (offset - len == msg_buf)) {
len = read(keysock, &msg_buf, sizeof (msg_buf));
if (len == -1) {
sckmd_log(LOG_ERR, "PF_KEY read: %s\n",
strerror(errno));
offset = NULL;
return (NULL);
}
offset = msg_buf;
len = SADB_8TO64(len);
}
retval = (struct sadb_msg *)offset;
offset += retval->sadb_msg_len;
if (offset > msg_buf + len) {
sckmd_log(LOG_ERR, "PF_KEY read: message corruption, "
"message length %d exceeds boundary %d\n",
SADB_64TO8(retval->sadb_msg_len),
SADB_64TO8((msg_buf + len) - (uint64_t *)retval));
offset = NULL;
return (NULL);
}
return (retval);
}
static int
convert_pfkey_msg(struct sadb_msg *msg)
{
sckmd_log(LOG_DEBUG, "PF_KEY conversion necessary...\n");
switch (msg->sadb_msg_version) {
case PF_KEY_V2:
break;
default:
sckmd_log(LOG_ERR, "No conversion possible for "
"PF_KEY version %d\n", msg->sadb_msg_version);
return (-1);
}
return (0);
}
static void
sckmd_log(int priority, char *fmt, ...)
{
va_list vap;
char err[SCKMD_ERR_MSG_SIZE];
if ((priority == LOG_DEBUG) && (debug == 0)) {
return;
}
va_start(vap, fmt);
vsnprintf(err, SCKMD_ERR_MSG_SIZE, fmt, vap);
va_end(vap);
if (standalone != 0) {
fprintf(stderr, err);
}
syslog(priority, err);
}