/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * sckmd - Starcat Key Management Daemon * * The sckmd is a daemon that runs on a domain and is responsible for * establishing security associations (SAs) for secure communication * with the System Controller (SC). All SAs are created on the SC * and propogated to the sckmd through the sckm driver running on * the domain. The sckmd then passes the SA to the key engine via the * PF_KEY interface. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SCKMD_DEBUG #define OPT_STR "ds" #else /* SCKMD_DEBUG */ #define OPT_STR "" #endif /* SCKMD_DEBUG */ #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 /* 3 seconds */ 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, ...); /* * main: * * Initialize sckmd and enter an infinite loop. The loop waits for * sckm messages from the sckm driver and dispatches each message * to be processed synchronously. */ int main(int argc, char **argv) { int opt; int fd; sckm_ioctl_getreq_t msg; /* * Set defaults */ standalone = 0; debug = 0; mypid = getpid(); openlog("sckmd", LOG_CONS | LOG_NDELAY, LOG_DAEMON); /* * Check command line options */ opterr = 0; /* disable getopt error messages */ 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"); /* must be root */ 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(); /* reinitialize syslog after closing all fds */ openlog("sckmd", LOG_CONS | LOG_NDELAY, LOG_DAEMON); } /* open driver */ if ((fd = open(KM_DEV, O_RDONLY)) == -1) { sckmd_log(LOG_ERR, "error initializing km driver: %s\n", strerror(errno)); exit(1); } /* * Main processing loop */ for (;;) { /* initialize the ioctl request */ (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; /* wait for the next message */ if (ioctl(fd, SCKM_IOCTL_GETREQ, &msg) == -1) { sckmd_log(LOG_ERR, "failed to receive sckm message: " "%s\n", strerror(errno)); continue; } /* pass the message to pf_key */ if (process_sckm_req(fd, &msg) == -1) { sckmd_log(LOG_DEBUG, "error processing sckm message\n"); continue; } } /*NOTREACHED*/ return (0); } /* * process_sckm_req: * * Process a sckm request message. If the message is valid, pass the * included SADB message to PF_KEY and return status to the sckm driver. * The function only fails if it is unable to return a status message * to the driver. */ 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); } /* initialize a reply message */ (void) memset(&reply, 0, sizeof (sckm_ioctl_status_t)); reply.transid = msg->transid; /* currently, we only support sadb messages */ 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)); } /* check that we have at least the sadb header */ 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)); } /* LINTED Pointer Cast Alignment Warning */ 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; /* check for an unsupported PF_KEY version */ 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)); } /* convert the PF_KEY message if necessary */ 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)); } } /* * Process the PF_KEY message */ pfkey_msg->sadb_msg_seq = ++seq; pfkey_msg->sadb_msg_pid = mypid; switch (msg_type) { case SADB_UPDATE: case SADB_ADD: case SADB_DELETE: /* * Only update, add, and delete are supported. Pass the * message directly to PF_KEY. */ 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)); } /* initialize global key socket */ 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)); } /* send the PF_KEY message */ 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)); } /* wait for key engine 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)); } /* * send_sckm_status: * * Send a sckm status message to the sckm driver */ 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); } /* * get_pfkey_reply: * * Wait for a reply from PF_KEY. Get the reply from the socket using * the global file desciptor 'keysock'. If PF_KEY returns an error, * the global errno is set to the error returned in the reply message. * If an error occurs, the parameter 'err' is set to one of the error * codes prefixed by SCKM_IOCTL_STAT_ERR to indicate the overall status * of the operation. */ 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); /* check for a timeout */ if (pollstatus == 0) { sckmd_log(LOG_NOTICE, "timed out waiting for PF_KEY " "reply\n"); *err = SCKM_IOCTL_STAT_ERR_TIMEOUT; return (-1); } /* read in the next PF_KEY message */ msg = read_pfkey_msg(); if (msg == NULL) { *err = SCKM_IOCTL_STAT_ERR_OTHER; return (-1); } /* check if the message is intended for us */ 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); } /* check for a timeout */ if (timeout <= 0) { sckmd_log(LOG_NOTICE, "timed out waiting for PF_KEY " "reply\n"); *err = SCKM_IOCTL_STAT_ERR_TIMEOUT; return (-1); } /* did we get what we were expecting? */ 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); } /* check for errors in SADB message */ if (msg->sadb_msg_errno != 0) { 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); /* generate unknown type string, if necessary */ 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; } /* use libipsecutil to lookup the SADB diagnostic string */ 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); } /* * read_pfkey_msg: * * Get a PF_KEY message from the socket using the global file descriptor * 'keysock'. Data is stored in the global buffer 'msg_buf'. The function * returns a pointer to the next PF_KEY message. Note that this is not * necessarily at the start of 'msg_buf'. NULL is returned for errors. */ static struct sadb_msg * read_pfkey_msg(void) { static uint64_t *offset; static int len; struct sadb_msg *retval; /* Assume offset and len are initialized to NULL and 0 */ if ((offset == NULL) || (offset - len == msg_buf)) { /* read a new block from the socket. */ 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); } /* * convert_pfkey_msg: * * Convert a lower version PF_KEY message to the current version * being used by sckmd. * * Currently, there is only one implemented version of PF_KEY (v2). * If future versions are added to the PF_KEY specification (RFC 2367), * this function should be updated to provide backwards compatibility * with version 2 and above. */ 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: /* * Current supported version: * No conversion required */ break; default: sckmd_log(LOG_ERR, "No conversion possible for " "PF_KEY version %d\n", msg->sadb_msg_version); return (-1); } return (0); } /* * sckmd_log: * * Log a message using the syslog facility. If sckmd is running in * standalone mode (global flag 'standalone' set), messages are also * sent to stderr. */ static void sckmd_log(int priority, char *fmt, ...) { va_list vap; char err[SCKMD_ERR_MSG_SIZE]; /* if this is a debug message, check if debugging is enabled */ if ((priority == LOG_DEBUG) && (debug == 0)) { return; } va_start(vap, fmt); vsnprintf(err, SCKMD_ERR_MSG_SIZE, fmt, vap); va_end(vap); /* send message to stderr if in standalone mode */ if (standalone != 0) { fprintf(stderr, err); } /* always log the message */ syslog(priority, err); }