diff options
Diffstat (limited to 'src/libstrongswan/plugins/af_alg/af_alg_ops.c')
-rw-r--r-- | src/libstrongswan/plugins/af_alg/af_alg_ops.c | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/src/libstrongswan/plugins/af_alg/af_alg_ops.c b/src/libstrongswan/plugins/af_alg/af_alg_ops.c new file mode 100644 index 000000000..37eeab54e --- /dev/null +++ b/src/libstrongswan/plugins/af_alg/af_alg_ops.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2010 Martin Willi + * Copyright (C) 2010 revosec AG + * + * 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. See <http://www.fsf.org/copyleft/gpl.txt>. + * + * 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. + */ + +#include "af_alg_ops.h" + +#include <unistd.h> +#include <errno.h> +#include <linux/socket.h> + +#include <debug.h> + +typedef struct private_af_alg_ops_t private_af_alg_ops_t; + +/** + * Private data of an af_alg_ops_t object. + */ +struct private_af_alg_ops_t { + + /** + * Public af_alg_ops_t interface. + */ + af_alg_ops_t public; + + /** + * Transform FD + */ + int tfm; + + /** + * Operation FD + */ + int op; +}; + +METHOD(af_alg_ops_t, reset, void, + private_af_alg_ops_t *this) +{ + if (this->op != -1) + { + close(this->op); + this->op = -1; + } +} + +METHOD(af_alg_ops_t, hash, void, + private_af_alg_ops_t *this, chunk_t data, char *out, size_t outlen) +{ + ssize_t len; + + while (this->op == -1) + { + this->op = accept(this->tfm, NULL, 0); + if (this->op == -1) + { + DBG1(DBG_LIB, "opening AF_ALG hasher failed: %s", strerror(errno)); + sleep(1); + } + } + do + { + len = send(this->op, data.ptr, data.len, out ? 0 : MSG_MORE); + if (len == -1) + { + DBG1(DBG_LIB, "writing to AF_ALG hasher failed: %s", strerror(errno)); + sleep(1); + } + else + { + data = chunk_skip(data, len); + } + } + while (data.len); + + if (out) + { + while (read(this->op, out, outlen) != outlen) + { + DBG1(DBG_LIB, "reading AF_ALG hasher failed: %s", strerror(errno)); + sleep(1); + } + reset(this); + } +} + +METHOD(af_alg_ops_t, crypt, void, + private_af_alg_ops_t *this, u_int32_t type, chunk_t iv, chunk_t data, + char *out) +{ + struct msghdr msg = {}; + struct cmsghdr *cmsg; + struct af_alg_iv *ivm; + struct iovec iov; + char buf[CMSG_SPACE(sizeof(type)) + + CMSG_SPACE(offsetof(struct af_alg_iv, iv) + iv.len)]; + ssize_t len; + int op; + + while ((op = accept(this->tfm, NULL, 0)) == -1) + { + DBG1(DBG_LIB, "accepting AF_ALG crypter failed: %s", strerror(errno)); + sleep(1); + } + + memset(buf, 0, sizeof(buf)); + + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_ALG; + cmsg->cmsg_type = ALG_SET_OP; + cmsg->cmsg_len = CMSG_LEN(sizeof(type)); + *(u_int32_t*)CMSG_DATA(cmsg) = type; + + cmsg = CMSG_NXTHDR(&msg, cmsg); + cmsg->cmsg_level = SOL_ALG; + cmsg->cmsg_type = ALG_SET_IV; + cmsg->cmsg_len = CMSG_LEN(offsetof(struct af_alg_iv, iv) + iv.len); + ivm = (void*)CMSG_DATA(cmsg); + ivm->ivlen = iv.len; + memcpy(ivm->iv, iv.ptr, iv.len); + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + while (data.len) + { + iov.iov_base = data.ptr; + iov.iov_len = data.len; + + len = sendmsg(op, &msg, 0); + if (len == -1) + { + DBG1(DBG_LIB, "writing to AF_ALG crypter failed: %s", + strerror(errno)); + sleep(1); + continue; + } + if (read(op, out, len) != len) + { + DBG1(DBG_LIB, "reading from AF_ALG crypter failed: %s", + strerror(errno)); + } + data = chunk_skip(data, len); + /* no IV for subsequent data chunks */ + msg.msg_controllen = 0; + } + close(op); +} + +METHOD(af_alg_ops_t, set_key, void, + private_af_alg_ops_t *this, chunk_t key) +{ + if (setsockopt(this->tfm, SOL_ALG, ALG_SET_KEY, key.ptr, key.len) == -1) + { + DBG1(DBG_LIB, "setting AF_ALG key failed: %s", strerror(errno)); + } +} + +METHOD(af_alg_ops_t, destroy, void, + private_af_alg_ops_t *this) +{ + close(this->tfm); + if (this->op != -1) + { + close(this->op); + } + free(this); +} + +/** + * See header + */ +af_alg_ops_t *af_alg_ops_create(char *type, char *alg) +{ + private_af_alg_ops_t *this; + struct sockaddr_alg sa = { + .salg_family = AF_ALG, + }; + + strncpy(sa.salg_type, type, sizeof(sa.salg_type)); + strncpy(sa.salg_name, alg, sizeof(sa.salg_name)); + + INIT(this, + .public = { + .hash = _hash, + .reset = _reset, + .crypt = _crypt, + .set_key = _set_key, + .destroy = _destroy, + }, + .tfm = socket(AF_ALG, SOCK_SEQPACKET, 0), + .op = -1, + ); + if (this->tfm == -1) + { + DBG1(DBG_LIB, "opening AF_ALG socket failed: %s", strerror(errno)); + free(this); + return NULL; + } + if (bind(this->tfm, (struct sockaddr*)&sa, sizeof(sa)) == -1) + { + DBG1(DBG_LIB, "binding AF_ALG socket for '%s' failed: %s", + sa.salg_name, strerror(errno)); + destroy(this); + return NULL; + } + return &this->public; +} |