https now working!

Still extremely ugly:
- I've inlined all the namespaces under ssl, so you need to know that
  context and config are related to ssl.
- luasec comes with its own copy of luasocket. I haven't deduped that
  yet.
This commit is contained in:
Kartik K. Agaram 2021-11-21 15:55:52 -08:00
parent 3b44b9827d
commit 5a484efe8c
35 changed files with 5641 additions and 10 deletions

View File

@ -14,7 +14,7 @@
An extremely naïve, [brutalist](https://en.wikipedia.org/wiki/Brutalist_architecture)
attempt at packaging up simple [Lua](http://www.lua.org) ([5.1](https://www.lua.org/manual/5.1))
apps with almost all the stuff needed to edit and build them. You will need
some Unix-like platform[1] with a C compiler and the ncurses library.
some Unix-like platform[1] with a C compiler and the ncurses and openssl libraries.
Here's how you run one of the example apps (the [Tower of Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)):

View File

@ -12,7 +12,7 @@ CFLAGS= -g -O2 -Wall $(MYCFLAGS)
AR= ar rc
RANLIB= ranlib
RM= rm -f
LIBS= -lm $(MYLIBS)
LIBS= -lm -lssl -lcrypto $(MYLIBS)
MYCFLAGS=
MYLDFLAGS=
@ -53,8 +53,8 @@ $(LUA_A): $(CORE_O) $(LIB_O)
$(AR) $@ $(CORE_O) $(LIB_O)
$(RANLIB) $@
$(LUA_T): $(LUA_O) $(LUA_A) luasocket/socket.a luasocket/mime.a
$(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) luasocket/socket.a luasocket/mime.a $(LIBS)
$(LUA_T): $(LUA_O) $(LUA_A) luasocket/socket.a luasocket/mime.a luasec/ssl.a
$(CC) -o $@ $(MYLDFLAGS) $(LUA_O) $(LUA_A) luasocket/socket.a luasocket/mime.a luasec/ssl.a $(LIBS)
$(LUAC_T): $(LUAC_O) $(LUA_A)
$(CC) -o $@ $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS)
@ -62,8 +62,12 @@ $(LUAC_T): $(LUAC_O) $(LUA_A)
luasocket/socket.a luasocket/mime.a:
make -C luasocket linux
luasec/ssl.a:
make -C luasec linux
clean:
make -C luasocket clean
make -C luasec clean
$(RM) $(ALL_T) $(ALL_O)
depend:
@ -122,7 +126,7 @@ solaris:
$(MAKE) all MYCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-ldl"
# list targets that do not create files (but not all makes understand .PHONY)
.PHONY: all $(PLATS) default o a clean depend echo none luasocket/socket.a luasocket/mime.a
.PHONY: all $(PLATS) default o a clean depend echo none luasocket/socket.a luasocket/mime.a luasec/ssl.a
# DO NOT DELETE

View File

@ -25,6 +25,10 @@ static const luaL_Reg lualibs[] = {
{LUA_CURSESLIBNAME, luaopen_curses},
{LUA_SOCKETCORELIBNAME, luaopen_socket_core},
{LUA_MIMECORELIBNAME, luaopen_mime_core},
{LUA_SSLLIBNAME, luaopen_ssl_core},
{LUA_SSLCONTEXTLIBNAME, luaopen_ssl_context},
{LUA_SSLX509LIBNAME, luaopen_ssl_x509},
{LUA_SSLCONFIGLIBNAME, luaopen_ssl_config},
{LUA_DBLIBNAME, luaopen_debug},
{NULL, NULL}
};

View File

@ -686,6 +686,10 @@ static int pmain (lua_State *L) {
if (status != 0) return 0;
status = dorequire(L, "src/luasocket/http.lua", "http");
if (status != 0) return 0;
status = dorequire(L, "src/luasec/ssl.lua", "ssl");
if (status != 0) return 0;
status = dorequire(L, "src/luasec/https.lua", "https");
if (status != 0) return 0;
status = dorequire(L, "src/json.lua", "json");
if (status != 0) return 0;
lua_gc(L, LUA_GCRESTART, 0);

View File

@ -42,6 +42,18 @@ LUALIB_API int (luaopen_socket_core) (lua_State *L);
#define LUA_MIMECORELIBNAME "mime"
LUALIB_API int (luaopen_mime_core) (lua_State *L);
#define LUA_SSLLIBNAME "ssl"
LUALIB_API int (luaopen_ssl_core) (lua_State *L);
#define LUA_SSLCONTEXTLIBNAME "context"
LUALIB_API int (luaopen_ssl_context) (lua_State *L);
#define LUA_SSLX509LIBNAME "x509"
LUALIB_API int (luaopen_ssl_x509) (lua_State *L);
#define LUA_SSLCONFIGLIBNAME "config"
LUALIB_API int (luaopen_ssl_config) (lua_State *L);
#define LUA_DBLIBNAME "debug"
LUALIB_API int (luaopen_debug) (lua_State *L);

67
src/luasec/Makefile Normal file
View File

@ -0,0 +1,67 @@
CMOD=ssl.so
LMOD=ssl.lua
OBJS= \
options.o \
x509.o \
context.o \
ssl.o \
config.o \
ec.o
WARN=-Wall -pedantic
BSD_CFLAGS=-O2 -fPIC $(WARN) $(INCDIR) $(DEFS)
BSD_LDFLAGS=-O -fPIC -shared $(LIBDIR)
LNX_CFLAGS=-O2 $(WARN) $(INCDIR) $(DEFS)
LNX_LDFLAGS=-O
MAC_ENV=env MACOSX_DEPLOYMENT_TARGET='$(MACVER)'
MAC_CFLAGS=-O2 -fno-common $(WARN) $(INCDIR) $(DEFS)
MAC_LDFLAGS=-bundle -undefined dynamic_lookup $(LIBDIR)
INSTALL = install
CC ?= cc
CCLD ?= $(MYENV) $(CC)
CFLAGS += $(MYCFLAGS)
LDFLAGS += $(MYLDFLAGS)
AR= ar rc
RANLIB= ranlib
.PHONY: all clean install none linux bsd macosx luasocket
all: luasocket
install: $(CMOD) $(LMOD)
$(INSTALL) -d $(DESTDIR)$(LUAPATH)/ssl $(DESTDIR)$(LUACPATH)
$(INSTALL) $(CMOD) $(DESTDIR)$(LUACPATH)
$(INSTALL) -m644 $(LMOD) $(DESTDIR)$(LUAPATH)
$(INSTALL) -m644 https.lua $(DESTDIR)$(LUAPATH)/ssl
linux: $(OBJS)
$(AR) ssl.a $(OBJS)
$(RANLIB) ssl.a
bsd:
@$(MAKE) $(CMOD) MYCFLAGS="$(BSD_CFLAGS)" MYLDFLAGS="$(BSD_LDFLAGS)" EXTRA="$(EXTRA)"
macosx:
@$(MAKE) $(CMOD) MYCFLAGS="$(MAC_CFLAGS)" MYLDFLAGS="$(MAC_LDFLAGS)" MYENV="$(MAC_ENV)" EXTRA="$(EXTRA)"
luasocket:
@cd luasocket && $(MAKE)
$(CMOD): $(EXTRA) $(OBJS)
$(CCLD) $(LDFLAGS) -o $@ $(OBJS) luasocket/libluasocket.a -lssl -lcrypto
clean:
cd luasocket && $(MAKE) clean
rm -f $(OBJS) $(CMOD)
options.o: options.h options.c
ec.o: ec.c ec.h
x509.o: x509.c x509.h compat.h
context.o: context.c context.h ec.h compat.h options.h
ssl.o: ssl.c ssl.h context.h x509.h compat.h
config.o: config.c ec.h options.h compat.h

57
src/luasec/compat.h Normal file
View File

@ -0,0 +1,57 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2006-2021 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#ifndef LSEC_COMPAT_H
#define LSEC_COMPAT_H
#include <openssl/ssl.h>
//------------------------------------------------------------------------------
#if defined(_WIN32)
#define LSEC_API __declspec(dllexport)
#else
#define LSEC_API extern
#endif
//------------------------------------------------------------------------------
#if (LUA_VERSION_NUM == 501)
#define luaL_testudata(L, ud, tname) lsec_testudata(L, ud, tname)
#define setfuncs(L, R) luaL_register(L, NULL, R)
#define lua_rawlen(L, i) lua_objlen(L, i)
#ifndef luaL_newlib
#define luaL_newlib(L, R) do { lua_newtable(L); luaL_register(L, NULL, R); } while(0)
#endif
#else
#define setfuncs(L, R) luaL_setfuncs(L, R, 0)
#endif
//------------------------------------------------------------------------------
#if (!defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x1010000fL))
#define LSEC_ENABLE_DANE
#endif
//------------------------------------------------------------------------------
#if !((defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2070000fL)) || (OPENSSL_VERSION_NUMBER < 0x1010000fL))
#define LSEC_API_OPENSSL_1_1_0
#endif
//------------------------------------------------------------------------------
#if !defined(LIBRESSL_VERSION_NUMBER) && ((OPENSSL_VERSION_NUMBER & 0xFFFFF000L) == 0x10101000L)
#define LSEC_OPENSSL_1_1_1
#endif
//------------------------------------------------------------------------------
#endif

97
src/luasec/config.c Normal file
View File

@ -0,0 +1,97 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2006-2021 Bruno Silvestre.
*
*--------------------------------------------------------------------------*/
#include "compat.h"
#include "options.h"
#include "ec.h"
/**
* Registre the module.
*/
LSEC_API int luaopen_ssl_config(lua_State *L)
{
lsec_ssl_option_t *opt;
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "config");
// Options
lua_pushstring(L, "options");
lua_newtable(L);
for (opt = lsec_get_ssl_options(); opt->name; opt++) {
lua_pushstring(L, opt->name);
lua_pushboolean(L, 1);
lua_rawset(L, -3);
}
lua_rawset(L, -3);
// Protocols
lua_pushstring(L, "protocols");
lua_newtable(L);
lua_pushstring(L, "tlsv1");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
lua_pushstring(L, "tlsv1_1");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
lua_pushstring(L, "tlsv1_2");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
#ifdef TLS1_3_VERSION
lua_pushstring(L, "tlsv1_3");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
#endif
lua_rawset(L, -3);
// Algorithms
lua_pushstring(L, "algorithms");
lua_newtable(L);
#ifndef OPENSSL_NO_EC
lua_pushstring(L, "ec");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
#endif
lua_rawset(L, -3);
// Curves
lua_pushstring(L, "curves");
lsec_get_curves(L);
lua_rawset(L, -3);
// Capabilities
lua_pushstring(L, "capabilities");
lua_newtable(L);
// ALPN
lua_pushstring(L, "alpn");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
#ifdef LSEC_ENABLE_DANE
// DANE
lua_pushstring(L, "dane");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
#endif
#ifndef OPENSSL_NO_EC
lua_pushstring(L, "curves_list");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
lua_pushstring(L, "ecdh_auto");
lua_pushboolean(L, 1);
lua_rawset(L, -3);
#endif
lua_rawset(L, -3);
return 1;
}

934
src/luasec/context.c Normal file
View File

@ -0,0 +1,934 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann,
* Matthew Wild.
* Copyright (C) 2006-2021 Bruno Silvestre.
*
*--------------------------------------------------------------------------*/
#include <string.h>
#if defined(WIN32)
#include <windows.h>
#endif
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/dh.h>
#include "../lua.h"
#include "../lauxlib.h"
#include "compat.h"
#include "context.h"
#include "options.h"
#ifndef OPENSSL_NO_EC
#include <openssl/ec.h>
#include "ec.h"
#endif
/*--------------------------- Auxiliary Functions ----------------------------*/
/**
* Return the context.
*/
static p_context checkctx(lua_State *L, int idx)
{
return (p_context)luaL_checkudata(L, idx, "SSL:Context");
}
static p_context testctx(lua_State *L, int idx)
{
return (p_context)luaL_testudata(L, idx, "SSL:Context");
}
/**
* Prepare the SSL options flag.
*/
static int set_option_flag(const char *opt, unsigned long *flag)
{
lsec_ssl_option_t *p;
for (p = lsec_get_ssl_options(); p->name; p++) {
if (!strcmp(opt, p->name)) {
*flag |= p->code;
return 1;
}
}
return 0;
}
#ifndef LSEC_API_OPENSSL_1_1_0
/**
* Find the protocol.
*/
static const SSL_METHOD* str2method(const char *method, int *vmin, int *vmax)
{
(void)vmin;
(void)vmax;
if (!strcmp(method, "any")) return SSLv23_method();
if (!strcmp(method, "sslv23")) return SSLv23_method(); // deprecated
if (!strcmp(method, "tlsv1")) return TLSv1_method();
if (!strcmp(method, "tlsv1_1")) return TLSv1_1_method();
if (!strcmp(method, "tlsv1_2")) return TLSv1_2_method();
return NULL;
}
#else
/**
* Find the protocol.
*/
static const SSL_METHOD* str2method(const char *method, int *vmin, int *vmax)
{
if (!strcmp(method, "any") || !strcmp(method, "sslv23")) { // 'sslv23' is deprecated
*vmin = 0;
*vmax = 0;
return TLS_method();
}
else if (!strcmp(method, "tlsv1")) {
*vmin = TLS1_VERSION;
*vmax = TLS1_VERSION;
return TLS_method();
}
else if (!strcmp(method, "tlsv1_1")) {
*vmin = TLS1_1_VERSION;
*vmax = TLS1_1_VERSION;
return TLS_method();
}
else if (!strcmp(method, "tlsv1_2")) {
*vmin = TLS1_2_VERSION;
*vmax = TLS1_2_VERSION;
return TLS_method();
}
#if defined(TLS1_3_VERSION)
else if (!strcmp(method, "tlsv1_3")) {
*vmin = TLS1_3_VERSION;
*vmax = TLS1_3_VERSION;
return TLS_method();
}
#endif
return NULL;
}
#endif
/**
* Prepare the SSL handshake verify flag.
*/
static int set_verify_flag(const char *str, int *flag)
{
if (!strcmp(str, "none")) {
*flag |= SSL_VERIFY_NONE;
return 1;
}
if (!strcmp(str, "peer")) {
*flag |= SSL_VERIFY_PEER;
return 1;
}
if (!strcmp(str, "client_once")) {
*flag |= SSL_VERIFY_CLIENT_ONCE;
return 1;
}
if (!strcmp(str, "fail_if_no_peer_cert")) {
*flag |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
return 1;
}
return 0;
}
/**
* Password callback for reading the private key.
*/
static int passwd_cb(char *buf, int size, int flag, void *udata)
{
lua_State *L = (lua_State*)udata;
switch (lua_type(L, 3)) {
case LUA_TFUNCTION:
lua_pushvalue(L, 3);
lua_call(L, 0, 1);
if (lua_type(L, -1) != LUA_TSTRING) {
lua_pop(L, 1); /* Remove the result from the stack */
return 0;
}
/* fallback */
case LUA_TSTRING:
strncpy(buf, lua_tostring(L, -1), size);
lua_pop(L, 1); /* Remove the result from the stack */
buf[size-1] = '\0';
return (int)strlen(buf);
}
return 0;
}
/**
* Add an error related to a depth certificate of the chain.
*/
static void add_cert_error(lua_State *L, SSL *ssl, int err, int depth)
{
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ssl);
lua_gettable(L, -2);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
/* Create an error table for this connection */
lua_newtable(L);
lua_pushlightuserdata(L, (void*)ssl);
lua_pushvalue(L, -2); /* keep the table on stack */
lua_settable(L, -4);
}
lua_rawgeti(L, -1, depth+1);
/* If the table doesn't exist, create it */
if (lua_isnil(L, -1)) {
lua_pop(L, 1); /* remove 'nil' from stack */
lua_newtable(L);
lua_pushvalue(L, -1); /* keep the table on stack */
lua_rawseti(L, -3, depth+1);
}
lua_pushstring(L, X509_verify_cert_error_string(err));
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
/* Clear the stack */
lua_pop(L, 3);
}
/**
* Call Lua user function to get the DH key.
*/
static DH *dhparam_cb(SSL *ssl, int is_export, int keylength)
{
BIO *bio;
lua_State *L;
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
p_context pctx = (p_context)SSL_CTX_get_app_data(ctx);
L = pctx->L;
/* Get the callback */
luaL_getmetatable(L, "SSL:DH:Registry");
lua_pushlightuserdata(L, (void*)ctx);
lua_gettable(L, -2);
/* Invoke the callback */
lua_pushboolean(L, is_export);
lua_pushnumber(L, keylength);
lua_call(L, 2, 1);
/* Load parameters from returned value */
if (lua_type(L, -1) != LUA_TSTRING) {
lua_pop(L, 2); /* Remove values from stack */
return NULL;
}
bio = BIO_new_mem_buf((void*)lua_tostring(L, -1), lua_rawlen(L, -1));
if (bio) {
pctx->dh_param = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
}
lua_pop(L, 2); /* Remove values from stack */
return pctx->dh_param;
}
/**
* Set the "ignore purpose" before to start verifing the certificate chain.
*/
static int cert_verify_cb(X509_STORE_CTX *x509_ctx, void *ptr)
{
int verify;
lua_State *L;
SSL_CTX *ctx = (SSL_CTX*)ptr;
p_context pctx = (p_context)SSL_CTX_get_app_data(ctx);
L = pctx->L;
/* Get verify flags */
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ctx);
lua_gettable(L, -2);
verify = (int)lua_tonumber(L, -1);
lua_pop(L, 2); /* Remove values from stack */
if (verify & LSEC_VERIFY_IGNORE_PURPOSE) {
/* Set parameters to ignore the server purpose */
X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(x509_ctx);
if (param) {
X509_VERIFY_PARAM_set_purpose(param, X509_PURPOSE_SSL_SERVER);
X509_VERIFY_PARAM_set_trust(param, X509_TRUST_SSL_SERVER);
}
}
/* Call OpenSSL standard verification function */
return X509_verify_cert(x509_ctx);
}
/**
* This callback implements the "continue on error" flag and log the errors.
*/
static int verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
int err;
int verify;
SSL *ssl;
SSL_CTX *ctx;
p_context pctx;
lua_State *L;
/* Short-circuit optimization */
if (preverify_ok)
return 1;
ssl = X509_STORE_CTX_get_ex_data(x509_ctx,
SSL_get_ex_data_X509_STORE_CTX_idx());
ctx = SSL_get_SSL_CTX(ssl);
pctx = (p_context)SSL_CTX_get_app_data(ctx);
L = pctx->L;
/* Get verify flags */
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ctx);
lua_gettable(L, -2);
verify = (int)lua_tonumber(L, -1);
lua_pop(L, 2); /* Remove values from stack */
err = X509_STORE_CTX_get_error(x509_ctx);
if (err != X509_V_OK)
add_cert_error(L, ssl, err, X509_STORE_CTX_get_error_depth(x509_ctx));
return (verify & LSEC_VERIFY_CONTINUE ? 1 : preverify_ok);
}
/*------------------------------ Lua Functions -------------------------------*/
/**
* Create a SSL context.
*/
static int create(lua_State *L)
{
p_context ctx;
const char *str_method;
const SSL_METHOD *method;
int vmin, vmax;
str_method = luaL_checkstring(L, 1);
method = str2method(str_method, &vmin, &vmax);
if (!method) {
lua_pushnil(L);
lua_pushfstring(L, "invalid protocol (%s)", str_method);
return 2;
}
ctx = (p_context) lua_newuserdata(L, sizeof(t_context));
if (!ctx) {
lua_pushnil(L);
lua_pushstring(L, "error creating context");
return 2;
}
memset(ctx, 0, sizeof(t_context));
ctx->context = SSL_CTX_new(method);
if (!ctx->context) {
lua_pushnil(L);
lua_pushfstring(L, "error creating context (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
#ifdef LSEC_API_OPENSSL_1_1_0
SSL_CTX_set_min_proto_version(ctx->context, vmin);
SSL_CTX_set_max_proto_version(ctx->context, vmax);
#endif
ctx->mode = LSEC_MODE_INVALID;
ctx->L = L;
luaL_getmetatable(L, "SSL:Context");
lua_setmetatable(L, -2);
/* No session support */
SSL_CTX_set_session_cache_mode(ctx->context, SSL_SESS_CACHE_OFF);
/* Link LuaSec context with the OpenSSL context */
SSL_CTX_set_app_data(ctx->context, ctx);
return 1;
}
/**
* Load the trusting certificates.
*/
static int load_locations(lua_State *L)
{
SSL_CTX *ctx = lsec_checkcontext(L, 1);
const char *cafile = luaL_optstring(L, 2, NULL);
const char *capath = luaL_optstring(L, 3, NULL);
if (SSL_CTX_load_verify_locations(ctx, cafile, capath) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error loading CA locations (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Load the certificate file.
*/
static int load_cert(lua_State *L)
{
SSL_CTX *ctx = lsec_checkcontext(L, 1);
const char *filename = luaL_checkstring(L, 2);
if (SSL_CTX_use_certificate_chain_file(ctx, filename) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error loading certificate (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Load the key file -- only in PEM format.
*/
static int load_key(lua_State *L)
{
int ret = 1;
SSL_CTX *ctx = lsec_checkcontext(L, 1);
const char *filename = luaL_checkstring(L, 2);
switch (lua_type(L, 3)) {
case LUA_TSTRING:
case LUA_TFUNCTION:
SSL_CTX_set_default_passwd_cb(ctx, passwd_cb);
SSL_CTX_set_default_passwd_cb_userdata(ctx, L);
/* fallback */
case LUA_TNIL:
if (SSL_CTX_use_PrivateKey_file(ctx, filename, SSL_FILETYPE_PEM) == 1)
lua_pushboolean(L, 1);
else {
ret = 2;
lua_pushboolean(L, 0);
lua_pushfstring(L, "error loading private key (%s)",
ERR_reason_error_string(ERR_get_error()));
}
SSL_CTX_set_default_passwd_cb(ctx, NULL);
SSL_CTX_set_default_passwd_cb_userdata(ctx, NULL);
break;
default:
lua_pushstring(L, "invalid callback value");
lua_error(L);
}
return ret;
}
/**
* Check that the certificate public key matches the private key
*/
static int check_key(lua_State *L)
{
SSL_CTX *ctx = lsec_checkcontext(L, 1);
lua_pushboolean(L, SSL_CTX_check_private_key(ctx));
return 1;
}
/**
* Set the cipher list.
*/
static int set_cipher(lua_State *L)
{
SSL_CTX *ctx = lsec_checkcontext(L, 1);
const char *list = luaL_checkstring(L, 2);
if (SSL_CTX_set_cipher_list(ctx, list) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error setting cipher list (%s)", ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the cipher suites.
*/
static int set_ciphersuites(lua_State *L)
{
#if defined(TLS1_3_VERSION)
SSL_CTX *ctx = lsec_checkcontext(L, 1);
const char *list = luaL_checkstring(L, 2);
if (SSL_CTX_set_ciphersuites(ctx, list) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error setting cipher list (%s)", ERR_reason_error_string(ERR_get_error()));
return 2;
}
#endif
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the depth for certificate checking.
*/
static int set_depth(lua_State *L)
{
SSL_CTX *ctx = lsec_checkcontext(L, 1);
SSL_CTX_set_verify_depth(ctx, (int)luaL_checkinteger(L, 2));
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the handshake verify options.
*/
static int set_verify(lua_State *L)
{
int i;
const char *str;
int flag = 0;
SSL_CTX *ctx = lsec_checkcontext(L, 1);
int max = lua_gettop(L);
for (i = 2; i <= max; i++) {
str = luaL_checkstring(L, i);
if (!set_verify_flag(str, &flag)) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "invalid verify option (%s)", str);
return 2;
}
}
if (flag) SSL_CTX_set_verify(ctx, flag, NULL);
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the protocol options.
*/
static int set_options(lua_State *L)
{
int i;
const char *str;
unsigned long flag = 0L;
SSL_CTX *ctx = lsec_checkcontext(L, 1);
int max = lua_gettop(L);
/* any option? */
if (max > 1) {
for (i = 2; i <= max; i++) {
str = luaL_checkstring(L, i);
if (!set_option_flag(str, &flag)) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "invalid option (%s)", str);
return 2;
}
}
SSL_CTX_set_options(ctx, flag);
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Set the context mode.
*/
static int set_mode(lua_State *L)
{
p_context ctx = checkctx(L, 1);
const char *str = luaL_checkstring(L, 2);
if (!strcmp("server", str)) {
ctx->mode = LSEC_MODE_SERVER;
lua_pushboolean(L, 1);
return 1;
}
if (!strcmp("client", str)) {
ctx->mode = LSEC_MODE_CLIENT;
lua_pushboolean(L, 1);
return 1;
}
lua_pushboolean(L, 0);
lua_pushfstring(L, "invalid mode (%s)", str);
return 1;
}
/**
* Configure DH parameters.
*/
static int set_dhparam(lua_State *L)
{
SSL_CTX *ctx = lsec_checkcontext(L, 1);
SSL_CTX_set_tmp_dh_callback(ctx, dhparam_cb);
/* Save callback */
luaL_getmetatable(L, "SSL:DH:Registry");
lua_pushlightuserdata(L, (void*)ctx);
lua_pushvalue(L, 2);
lua_settable(L, -3);
return 0;
}
#if !defined(OPENSSL_NO_EC)
/**
* Set elliptic curve.
*/
static int set_curve(lua_State *L)
{
long ret;
EC_KEY *key = NULL;
SSL_CTX *ctx = lsec_checkcontext(L, 1);
const char *str = luaL_checkstring(L, 2);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
key = lsec_find_ec_key(L, str);
if (!key) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "elliptic curve '%s' not supported", str);
return 2;
}
ret = SSL_CTX_set_tmp_ecdh(ctx, key);
/* SSL_CTX_set_tmp_ecdh takes its own reference */
EC_KEY_free(key);
if (!ret) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error setting elliptic curve (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* Set elliptic curves list.
*/
static int set_curves_list(lua_State *L)
{
SSL_CTX *ctx = lsec_checkcontext(L, 1);
const char *str = luaL_checkstring(L, 2);
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
if (SSL_CTX_set1_curves_list(ctx, str) != 1) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "unknown elliptic curve in \"%s\"", str);
return 2;
}
#if defined(LIBRESSL_VERSION_NUMBER) || !defined(LSEC_API_OPENSSL_1_1_0)
(void)SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
lua_pushboolean(L, 1);
return 1;
}
#endif
/**
* Set the protocols a client should send for ALPN.
*/
static int set_alpn(lua_State *L)
{
long ret;
size_t len;
p_context ctx = checkctx(L, 1);
const char *str = luaL_checklstring(L, 2, &len);
ret = SSL_CTX_set_alpn_protos(ctx->context, (const unsigned char*)str, len);
if (ret) {
lua_pushboolean(L, 0);
lua_pushfstring(L, "error setting ALPN (%s)", ERR_reason_error_string(ERR_get_error()));
return 2;
}
lua_pushboolean(L, 1);
return 1;
}
/**
* This standard callback calls the server's callback in Lua sapce.
* The server has to return a list in wire-format strings.
* This function uses a helper function to match server and client lists.
*/
static int alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen,
const unsigned char *in, unsigned int inlen, void *arg)
{
int ret;
size_t server_len;
const char *server;
p_context ctx = (p_context)arg;
lua_State *L = ctx->L;
luaL_getmetatable(L, "SSL:ALPN:Registry");
lua_pushlightuserdata(L, (void*)ctx->context);
lua_gettable(L, -2);
lua_pushlstring(L, (const char*)in, inlen);
lua_call(L, 1, 1);
if (!lua_isstring(L, -1)) {
lua_pop(L, 2);
return SSL_TLSEXT_ERR_NOACK;
}
// Protocol list from server in wire-format string
server = luaL_checklstring(L, -1, &server_len);
ret = SSL_select_next_proto((unsigned char**)out, outlen, (const unsigned char*)server,
server_len, in, inlen);
if (ret != OPENSSL_NPN_NEGOTIATED) {
lua_pop(L, 2);
return SSL_TLSEXT_ERR_NOACK;
}
// Copy the result because lua_pop() can collect the pointer
ctx->alpn = malloc(*outlen);
memcpy(ctx->alpn, (void*)*out, *outlen);
*out = (const unsigned char*)ctx->alpn;
lua_pop(L, 2);
return SSL_TLSEXT_ERR_OK;
}
/**
* Set a callback a server can use to select the next protocol with ALPN.
*/
static int set_alpn_cb(lua_State *L)
{
p_context ctx = checkctx(L, 1);
luaL_getmetatable(L, "SSL:ALPN:Registry");
lua_pushlightuserdata(L, (void*)ctx->context);
lua_pushvalue(L, 2);
lua_settable(L, -3);
SSL_CTX_set_alpn_select_cb(ctx->context, alpn_cb, ctx);
lua_pushboolean(L, 1);
return 1;
}
#if defined(LSEC_ENABLE_DANE)
/*
* DANE
*/
static int set_dane(lua_State *L)
{
int ret;
SSL_CTX *ctx = lsec_checkcontext(L, 1);
ret = SSL_CTX_dane_enable(ctx);
lua_pushboolean(L, (ret > 0));
return 1;
}
#endif
/**
* Package functions
*/
static luaL_Reg funcs[] = {
{"create", create},
{"locations", load_locations},
{"loadcert", load_cert},
{"loadkey", load_key},
{"checkkey", check_key},
{"setalpn", set_alpn},
{"setalpncb", set_alpn_cb},
{"setcipher", set_cipher},
{"setciphersuites", set_ciphersuites},
{"setdepth", set_depth},
{"setdhparam", set_dhparam},
{"setverify", set_verify},
{"setoptions", set_options},
{"setmode", set_mode},
#if !defined(OPENSSL_NO_EC)
{"setcurve", set_curve},
{"setcurveslist", set_curves_list},
#endif
#if defined(LSEC_ENABLE_DANE)
{"setdane", set_dane},
#endif
{NULL, NULL}
};
/*-------------------------------- Metamethods -------------------------------*/
/**
* Collect SSL context -- GC metamethod.
*/
static int meth_destroy(lua_State *L)
{
p_context ctx = checkctx(L, 1);
if (ctx->context) {
/* Clear registries */
luaL_getmetatable(L, "SSL:DH:Registry");
lua_pushlightuserdata(L, (void*)ctx->context);
lua_pushnil(L);
lua_settable(L, -3);
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ctx->context);
lua_pushnil(L);
lua_settable(L, -3);
luaL_getmetatable(L, "SSL:ALPN:Registry");
lua_pushlightuserdata(L, (void*)ctx->context);
lua_pushnil(L);
lua_settable(L, -3);
SSL_CTX_free(ctx->context);
ctx->context = NULL;
}
return 0;
}
/**
* Object information -- tostring metamethod.
*/
static int meth_tostring(lua_State *L)
{
p_context ctx = checkctx(L, 1);
lua_pushfstring(L, "SSL context: %p", ctx);
return 1;
}
/**
* Set extra flags for handshake verification.
*/
static int meth_set_verify_ext(lua_State *L)
{
int i;
const char *str;
int crl_flag = 0;
int lsec_flag = 0;
SSL_CTX *ctx = lsec_checkcontext(L, 1);
int max = lua_gettop(L);
for (i = 2; i <= max; i++) {
str = luaL_checkstring(L, i);
if (!strcmp(str, "lsec_continue")) {
lsec_flag |= LSEC_VERIFY_CONTINUE;
} else if (!strcmp(str, "lsec_ignore_purpose")) {
lsec_flag |= LSEC_VERIFY_IGNORE_PURPOSE;
} else if (!strcmp(str, "crl_check")) {
crl_flag |= X509_V_FLAG_CRL_CHECK;
} else if (!strcmp(str, "crl_check_chain")) {
crl_flag |= X509_V_FLAG_CRL_CHECK_ALL;
} else {
lua_pushboolean(L, 0);
lua_pushfstring(L, "invalid verify option (%s)", str);
return 2;
}
}
/* Set callback? */
if (lsec_flag) {
SSL_CTX_set_verify(ctx, SSL_CTX_get_verify_mode(ctx), verify_cb);
SSL_CTX_set_cert_verify_callback(ctx, cert_verify_cb, (void*)ctx);
/* Save flag */
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ctx);
lua_pushnumber(L, lsec_flag);
lua_settable(L, -3);
} else {
SSL_CTX_set_verify(ctx, SSL_CTX_get_verify_mode(ctx), NULL);
SSL_CTX_set_cert_verify_callback(ctx, NULL, NULL);
/* Remove flag */
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ctx);
lua_pushnil(L);
lua_settable(L, -3);
}
/* X509 flag */
X509_STORE_set_flags(SSL_CTX_get_cert_store(ctx), crl_flag);
/* Ok */
lua_pushboolean(L, 1);
return 1;
}
/**
* Context metamethods.
*/
static luaL_Reg meta[] = {
{"__close", meth_destroy},
{"__gc", meth_destroy},
{"__tostring", meth_tostring},
{NULL, NULL}
};
/**
* Index metamethods.
*/
static luaL_Reg meta_index[] = {
{"setverifyext", meth_set_verify_ext},
{NULL, NULL}
};
/*----------------------------- Public Functions ---------------------------*/
/**
* Retrieve the SSL context from the Lua stack.
*/
SSL_CTX* lsec_checkcontext(lua_State *L, int idx)
{
p_context ctx = checkctx(L, idx);
return ctx->context;
}
SSL_CTX* lsec_testcontext(lua_State *L, int idx)
{
p_context ctx = testctx(L, idx);
return (ctx) ? ctx->context : NULL;
}
/**
* Retrieve the mode from the context in the Lua stack.
*/
int lsec_getmode(lua_State *L, int idx)
{
p_context ctx = checkctx(L, idx);
return ctx->mode;
}
/*-- Compat - Lua 5.1 --*/
#if (LUA_VERSION_NUM == 501)
void *lsec_testudata (lua_State *L, int ud, const char *tname) {
void *p = lua_touserdata(L, ud);
if (p != NULL) { /* value is a userdata? */
if (lua_getmetatable(L, ud)) { /* does it have a metatable? */
luaL_getmetatable(L, tname); /* get correct metatable */
if (!lua_rawequal(L, -1, -2)) /* not the same? */
p = NULL; /* value is a userdata with wrong metatable */
lua_pop(L, 2); /* remove both metatables */
return p;
}
}
return NULL; /* value is not a userdata with a metatable */
}
#endif
/*------------------------------ Initialization ------------------------------*/
/**
* Registre the module.
*/
LSEC_API int luaopen_ssl_context(lua_State *L)
{
luaL_newmetatable(L, "SSL:DH:Registry"); /* Keep all DH callbacks */
luaL_newmetatable(L, "SSL:ALPN:Registry"); /* Keep all ALPN callbacks */
luaL_newmetatable(L, "SSL:Verify:Registry"); /* Keep all verify flags */
luaL_newmetatable(L, "SSL:Context");
setfuncs(L, meta);
/* Create __index metamethods for context */
luaL_newlib(L, meta_index);
lua_setfield(L, -2, "__index");
lsec_load_curves(L);
/* Return the module */
luaL_newlib(L, funcs);
lua_pushvalue(L, -1);
lua_setglobal(L, "context");
return 1;
}

47
src/luasec/context.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef LSEC_CONTEXT_H
#define LSEC_CONTEXT_H
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2006-2021 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#include "../lua.h"
#include <openssl/ssl.h>
#include "compat.h"
#define LSEC_MODE_INVALID 0
#define LSEC_MODE_SERVER 1
#define LSEC_MODE_CLIENT 2
#define LSEC_VERIFY_CONTINUE 1
#define LSEC_VERIFY_IGNORE_PURPOSE 2
typedef struct t_context_ {
SSL_CTX *context;
lua_State *L;
DH *dh_param;
void *alpn;
int mode;
} t_context;
typedef t_context* p_context;
/* Retrieve the SSL context from the Lua stack */
SSL_CTX *lsec_checkcontext(lua_State *L, int idx);
SSL_CTX *lsec_testcontext(lua_State *L, int idx);
/* Retrieve the mode from the context in the Lua stack */
int lsec_getmode(lua_State *L, int idx);
/* Registre the module. */
LSEC_API int luaopen_ssl_context(lua_State *L);
/* Compat - Lua 5.1 */
#if (LUA_VERSION_NUM == 501)
void *lsec_testudata (lua_State *L, int ud, const char *tname);
#endif
#endif

109
src/luasec/ec.c Normal file
View File

@ -0,0 +1,109 @@
#include <openssl/objects.h>
#include "ec.h"
#ifndef OPENSSL_NO_EC
EC_KEY *lsec_find_ec_key(lua_State *L, const char *str)
{
int nid;
lua_pushstring(L, "SSL:EC:CURVES");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushstring(L, str);
lua_rawget(L, -2);
if (!lua_isnumber(L, -1))
return NULL;
nid = (int)lua_tonumber(L, -1);
return EC_KEY_new_by_curve_name(nid);
}
void lsec_load_curves(lua_State *L)
{
size_t i;
size_t size;
const char *name;
EC_builtin_curve *curves = NULL;
lua_pushstring(L, "SSL:EC:CURVES");
lua_newtable(L);
size = EC_get_builtin_curves(NULL, 0);
if (size > 0) {
curves = (EC_builtin_curve*)malloc(sizeof(EC_builtin_curve) * size);
EC_get_builtin_curves(curves, size);
for (i = 0; i < size; i++) {
name = OBJ_nid2sn(curves[i].nid);
if (name != NULL) {
lua_pushstring(L, name);
lua_pushnumber(L, curves[i].nid);
lua_rawset(L, -3);
}
switch (curves[i].nid) {
case NID_X9_62_prime256v1:
lua_pushstring(L, "P-256");
lua_pushnumber(L, curves[i].nid);
lua_rawset(L, -3);
break;
case NID_secp384r1:
lua_pushstring(L, "P-384");
lua_pushnumber(L, curves[i].nid);
lua_rawset(L, -3);
break;
case NID_secp521r1:
lua_pushstring(L, "P-521");
lua_pushnumber(L, curves[i].nid);
lua_rawset(L, -3);
break;
}
}
free(curves);
}
/* These are special so are manually added here */
#ifdef NID_X25519
lua_pushstring(L, "X25519");
lua_pushnumber(L, NID_X25519);
lua_rawset(L, -3);
#endif
#ifdef NID_X448
lua_pushstring(L, "X448");
lua_pushnumber(L, NID_X448);
lua_rawset(L, -3);
#endif
lua_rawset(L, LUA_REGISTRYINDEX);
}
void lsec_get_curves(lua_State *L)
{
lua_newtable(L);
lua_pushstring(L, "SSL:EC:CURVES");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
lua_pop(L, 1);
lua_pushvalue(L, -1);
lua_pushboolean(L, 1);
lua_rawset(L, -5);
}
lua_pop(L, 1);
}
#else
void lsec_load_curves(lua_State *L)
{
// do nothing
}
void lsec_get_curves(lua_State *L)
{
lua_newtable(L);
}
#endif

22
src/luasec/ec.h Normal file
View File

@ -0,0 +1,22 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2006-2021 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#ifndef LSEC_EC_H
#define LSEC_EC_H
#include "../lua.h"
#ifndef OPENSSL_NO_EC
#include <openssl/ec.h>
EC_KEY *lsec_find_ec_key(lua_State *L, const char *str);
#endif
void lsec_get_curves(lua_State *L);
void lsec_load_curves(lua_State *L);
#endif

140
src/luasec/https.lua Normal file
View File

@ -0,0 +1,140 @@
----------------------------------------------------------------------------
-- LuaSec 1.0.2
-- Copyright (C) 2009-2021 PUC-Rio
--
-- Author: Pablo Musa
-- Author: Tomas Guisasola
---------------------------------------------------------------------------
local try = socket.try
--
-- Module
--
local _M = {
_VERSION = "1.0.2",
_COPYRIGHT = "LuaSec 1.0.2 - Copyright (C) 2009-2021 PUC-Rio",
PORT = 443,
TIMEOUT = 60
}
-- TLS configuration
local cfg = {
protocol = "any",
options = {"all", "no_sslv2", "no_sslv3", "no_tlsv1"},
verify = "none",
}
--------------------------------------------------------------------
-- Auxiliar Functions
--------------------------------------------------------------------
-- Insert default HTTPS port.
local function default_https_port(u)
return url.build(url.parse(u, {port = _M.PORT}))
end
-- Convert an URL to a table according to Luasocket needs.
local function urlstring_totable(url, body, result_table)
url = {
url = default_https_port(url),
method = body and "POST" or "GET",
sink = ltn12.sink.table(result_table)
}
if body then
url.source = ltn12.source.string(body)
url.headers = {
["content-length"] = #body,
["content-type"] = "application/x-www-form-urlencoded",
}
end
return url
end
-- Forward calls to the real connection object.
local function reg(conn)
local mt = getmetatable(conn.sock).__index
for name, method in pairs(mt) do
if type(method) == "function" then
conn[name] = function (self, ...)
return method(self.sock, ...)
end
end
end
end
-- Return a function which performs the SSL/TLS connection.
local function tcp(params)
params = params or {}
-- Default settings
for k, v in pairs(cfg) do
params[k] = params[k] or v
end
-- Force client mode
params.mode = "client"
-- 'create' function for LuaSocket
return function ()
local conn = {}
conn.sock = try(socket.tcp())
local st = getmetatable(conn.sock).__index.settimeout
function conn:settimeout(...)
return st(self.sock, _M.TIMEOUT)
end
-- Replace TCP's connection function
function conn:connect(host, port)
try(self.sock:connect(host, port))
self.sock = try(ssl.wrap(self.sock, params))
self.sock:sni(host)
self.sock:settimeout(_M.TIMEOUT)
try(self.sock:dohandshake())
reg(self, getmetatable(self.sock))
return 1
end
return conn
end
end
--------------------------------------------------------------------
-- Main Function
--------------------------------------------------------------------
-- Make a HTTP request over secure connection. This function receives
-- the same parameters of LuaSocket's HTTP module (except 'proxy' and
-- 'redirect') plus LuaSec parameters.
--
-- @param url mandatory (string or table)
-- @param body optional (string)
-- @return (string if url == string or 1), code, headers, status
--
local function request(url, body)
local result_table = {}
local stringrequest = type(url) == "string"
if stringrequest then
url = urlstring_totable(url, body, result_table)
else
url.url = default_https_port(url.url)
end
if http.PROXY or url.proxy then
return nil, "proxy not supported"
elseif url.redirect then
return nil, "redirect not supported"
elseif url.create then
return nil, "create function not permitted"
end
-- New 'create' function to establish a secure connection
url.create = tcp(url)
local res, code, headers, status = http.request(url)
if res and stringrequest then
return table.concat(result_table), code, headers, status
end
return res, code, headers, status
end
--------------------------------------------------------------------------------
-- Export module
--
_M.request = request
_M.tcp = tcp
return _M

View File

@ -0,0 +1,21 @@
LuaSocket 3.0-RC1 license
Copyright (C) 2004-2013 Diego Nehab
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,26 @@
OBJS= \
io.o \
buffer.o \
timeout.o \
usocket.o
CC ?= cc
CFLAGS += $(MYCFLAGS) -DLUASOCKET_DEBUG
AR ?= ar
RANLIB ?= ranlib
.PHONY: all clean
all: libluasocket.a
libluasocket.a: $(OBJS)
$(AR) rcu $@ $(OBJS)
$(RANLIB) $@
clean:
rm -f $(OBJS) libluasocket.a
buffer.o: buffer.c buffer.h io.h timeout.h
io.o: io.c io.h timeout.h
timeout.o: timeout.c timeout.h
usocket.o: usocket.c socket.h io.h timeout.h usocket.h

View File

@ -0,0 +1,282 @@
/*=========================================================================*\
* Input/Output interface for Lua programs
* LuaSocket toolkit
\*=========================================================================*/
#include "../../lua.h"
#include "../../lauxlib.h"
#include "buffer.h"
/*=========================================================================*\
* Internal function prototypes
\*=========================================================================*/
static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b);
static int recvline(p_buffer buf, luaL_Buffer *b);
static int recvall(p_buffer buf, luaL_Buffer *b);
static int buffer_get(p_buffer buf, const char **data, size_t *count);
static void buffer_skip(p_buffer buf, size_t count);
static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent);
/* min and max macros */
#ifndef MIN
#define MIN(x, y) ((x) < (y) ? x : y)
#endif
#ifndef MAX
#define MAX(x, y) ((x) > (y) ? x : y)
#endif
/*=========================================================================*\
* Exported functions
\*=========================================================================*/
/*-------------------------------------------------------------------------*\
* Initializes module
\*-------------------------------------------------------------------------*/
int buffer_open(lua_State *L) {
(void) L;
return 0;
}
/*-------------------------------------------------------------------------*\
* Initializes C structure
\*-------------------------------------------------------------------------*/
void buffer_init(p_buffer buf, p_io io, p_timeout tm) {
buf->first = buf->last = 0;
buf->io = io;
buf->tm = tm;
buf->received = buf->sent = 0;
buf->birthday = timeout_gettime();
}
/*-------------------------------------------------------------------------*\
* object:getstats() interface
\*-------------------------------------------------------------------------*/
int buffer_meth_getstats(lua_State *L, p_buffer buf) {
lua_pushnumber(L, (lua_Number) buf->received);
lua_pushnumber(L, (lua_Number) buf->sent);
lua_pushnumber(L, timeout_gettime() - buf->birthday);
return 3;
}
/*-------------------------------------------------------------------------*\
* object:setstats() interface
\*-------------------------------------------------------------------------*/
int buffer_meth_setstats(lua_State *L, p_buffer buf) {
buf->received = (long) luaL_optnumber(L, 2, (lua_Number) buf->received);
buf->sent = (long) luaL_optnumber(L, 3, (lua_Number) buf->sent);
if (lua_isnumber(L, 4)) buf->birthday = timeout_gettime() - lua_tonumber(L, 4);
lua_pushnumber(L, 1);
return 1;
}
/*-------------------------------------------------------------------------*\
* object:send() interface
\*-------------------------------------------------------------------------*/
int buffer_meth_send(lua_State *L, p_buffer buf) {
int top = lua_gettop(L);
int err = IO_DONE;
size_t size = 0, sent = 0;
const char *data = luaL_checklstring(L, 2, &size);
long start = (long) luaL_optnumber(L, 3, 1);
long end = (long) luaL_optnumber(L, 4, -1);
#ifdef LUASOCKET_DEBUG
p_timeout tm = timeout_markstart(buf->tm);
#endif
if (start < 0) start = (long) (size+start+1);
if (end < 0) end = (long) (size+end+1);
if (start < 1) start = (long) 1;
if (end > (long) size) end = (long) size;
if (start <= end) err = sendraw(buf, data+start-1, end-start+1, &sent);
/* check if there was an error */
if (err != IO_DONE) {
lua_pushnil(L);
lua_pushstring(L, buf->io->error(buf->io->ctx, err));
lua_pushnumber(L, (lua_Number) (sent+start-1));
} else {
lua_pushnumber(L, (lua_Number) (sent+start-1));
lua_pushnil(L);
lua_pushnil(L);
}
#ifdef LUASOCKET_DEBUG
/* push time elapsed during operation as the last return value */
lua_pushnumber(L, timeout_gettime() - timeout_getstart(tm));
#endif
return lua_gettop(L) - top;
}
/*-------------------------------------------------------------------------*\
* object:receive() interface
\*-------------------------------------------------------------------------*/
int buffer_meth_receive(lua_State *L, p_buffer buf) {
luaL_Buffer b;
size_t size;
const char *part;
int err = IO_DONE;
int top = lua_gettop(L);
if (top < 3) {
lua_settop(L, 3);
top = 3;
}
part = luaL_optlstring(L, 3, "", &size);
#ifdef LUASOCKET_DEBUG
p_timeout tm = timeout_markstart(buf->tm);
#endif
/* initialize buffer with optional extra prefix
* (useful for concatenating previous partial results) */
luaL_buffinit(L, &b);
luaL_addlstring(&b, part, size);
/* receive new patterns */
if (!lua_isnumber(L, 2)) {
const char *p= luaL_optstring(L, 2, "*l");
if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b);
else if (p[0] == '*' && p[1] == 'a') err = recvall(buf, &b);
else luaL_argcheck(L, 0, 2, "invalid receive pattern");
/* get a fixed number of bytes (minus what was already partially
* received) */
} else {
double n = lua_tonumber(L, 2);
size_t wanted = (size_t) n;
luaL_argcheck(L, n >= 0, 2, "invalid receive pattern");
if (size == 0 || wanted > size)
err = recvraw(buf, wanted-size, &b);
}
/* check if there was an error */
if (err != IO_DONE) {
/* we can't push anything in the stack before pushing the
* contents of the buffer. this is the reason for the complication */
luaL_pushresult(&b);
lua_pushstring(L, buf->io->error(buf->io->ctx, err));
lua_pushvalue(L, -2);
lua_pushnil(L);
lua_replace(L, -4);
} else {
luaL_pushresult(&b);
lua_pushnil(L);
lua_pushnil(L);
}
#ifdef LUASOCKET_DEBUG
/* push time elapsed during operation as the last return value */
lua_pushnumber(L, timeout_gettime() - timeout_getstart(tm));
#endif
return lua_gettop(L) - top;
}
/*-------------------------------------------------------------------------*\
* Determines if there is any data in the read buffer
\*-------------------------------------------------------------------------*/
int buffer_isempty(p_buffer buf) {
return buf->first >= buf->last;
}
/*=========================================================================*\
* Internal functions
\*=========================================================================*/
/*-------------------------------------------------------------------------*\
* Sends a block of data (unbuffered)
\*-------------------------------------------------------------------------*/
#define STEPSIZE 8192
static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent) {
p_io io = buf->io;
p_timeout tm = buf->tm;
size_t total = 0;
int err = IO_DONE;
while (total < count && err == IO_DONE) {
size_t done = 0;
size_t step = (count-total <= STEPSIZE)? count-total: STEPSIZE;
err = io->send(io->ctx, data+total, step, &done, tm);
total += done;
}
*sent = total;
buf->sent += total;
return err;
}
/*-------------------------------------------------------------------------*\
* Reads a fixed number of bytes (buffered)
\*-------------------------------------------------------------------------*/
static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b) {
int err = IO_DONE;
size_t total = 0;
while (err == IO_DONE) {
size_t count; const char *data;
err = buffer_get(buf, &data, &count);
count = MIN(count, wanted - total);
luaL_addlstring(b, data, count);
buffer_skip(buf, count);
total += count;
if (total >= wanted) break;
}
return err;
}
/*-------------------------------------------------------------------------*\
* Reads everything until the connection is closed (buffered)
\*-------------------------------------------------------------------------*/
static int recvall(p_buffer buf, luaL_Buffer *b) {
int err = IO_DONE;
size_t total = 0;
while (err == IO_DONE) {
const char *data; size_t count;
err = buffer_get(buf, &data, &count);
total += count;
luaL_addlstring(b, data, count);
buffer_skip(buf, count);
}
if (err == IO_CLOSED) {
if (total > 0) return IO_DONE;
else return IO_CLOSED;
} else return err;
}
/*-------------------------------------------------------------------------*\
* Reads a line terminated by a CR LF pair or just by a LF. The CR and LF
* are not returned by the function and are discarded from the buffer
\*-------------------------------------------------------------------------*/
static int recvline(p_buffer buf, luaL_Buffer *b) {
int err = IO_DONE;
while (err == IO_DONE) {
size_t count, pos; const char *data;
err = buffer_get(buf, &data, &count);
pos = 0;
while (pos < count && data[pos] != '\n') {
/* we ignore all \r's */
if (data[pos] != '\r') luaL_addchar(b, data[pos]);
pos++;
}
if (pos < count) { /* found '\n' */
buffer_skip(buf, pos+1); /* skip '\n' too */
break; /* we are done */
} else /* reached the end of the buffer */
buffer_skip(buf, pos);
}
return err;
}
/*-------------------------------------------------------------------------*\
* Skips a given number of bytes from read buffer. No data is read from the
* transport layer
\*-------------------------------------------------------------------------*/
static void buffer_skip(p_buffer buf, size_t count) {
buf->received += count;
buf->first += count;
if (buffer_isempty(buf))
buf->first = buf->last = 0;
}
/*-------------------------------------------------------------------------*\
* Return any data available in buffer, or get more data from transport layer
* if buffer is empty
\*-------------------------------------------------------------------------*/
static int buffer_get(p_buffer buf, const char **data, size_t *count) {
int err = IO_DONE;
p_io io = buf->io;
p_timeout tm = buf->tm;
if (buffer_isempty(buf)) {
size_t got;
err = io->recv(io->ctx, buf->data, BUF_SIZE, &got, tm);
buf->first = 0;
buf->last = got;
}
*count = buf->last - buf->first;
*data = buf->data + buf->first;
return err;
}

View File

@ -0,0 +1,45 @@
#ifndef BUF_H
#define BUF_H
/*=========================================================================*\
* Input/Output interface for Lua programs
* LuaSocket toolkit
*
* Line patterns require buffering. Reading one character at a time involves
* too many system calls and is very slow. This module implements the
* LuaSocket interface for input/output on connected objects, as seen by
* Lua programs.
*
* Input is buffered. Output is *not* buffered because there was no simple
* way of making sure the buffered output data would ever be sent.
*
* The module is built on top of the I/O abstraction defined in io.h and the
* timeout management is done with the timeout.h interface.
\*=========================================================================*/
#include "../../lua.h"
#include "io.h"
#include "timeout.h"
/* buffer size in bytes */
#define BUF_SIZE 8192
/* buffer control structure */
typedef struct t_buffer_ {
double birthday; /* throttle support info: creation time, */
size_t sent, received; /* bytes sent, and bytes received */
p_io io; /* IO driver used for this buffer */
p_timeout tm; /* timeout management for this buffer */
size_t first, last; /* index of first and last bytes of stored data */
char data[BUF_SIZE]; /* storage space for buffer data */
} t_buffer;
typedef t_buffer *p_buffer;
int buffer_open(lua_State *L);
void buffer_init(p_buffer buf, p_io io, p_timeout tm);
int buffer_meth_send(lua_State *L, p_buffer buf);
int buffer_meth_receive(lua_State *L, p_buffer buf);
int buffer_meth_getstats(lua_State *L, p_buffer buf);
int buffer_meth_setstats(lua_State *L, p_buffer buf);
int buffer_isempty(p_buffer buf);
#endif /* BUF_H */

30
src/luasec/luasocket/io.c Normal file
View File

@ -0,0 +1,30 @@
/*=========================================================================*\
* Input/Output abstraction
* LuaSocket toolkit
\*=========================================================================*/
#include "io.h"
/*=========================================================================*\
* Exported functions
\*=========================================================================*/
/*-------------------------------------------------------------------------*\
* Initializes C structure
\*-------------------------------------------------------------------------*/
void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx) {
io->send = send;
io->recv = recv;
io->error = error;
io->ctx = ctx;
}
/*-------------------------------------------------------------------------*\
* I/O error strings
\*-------------------------------------------------------------------------*/
const char *io_strerror(int err) {
switch (err) {
case IO_DONE: return NULL;
case IO_CLOSED: return "closed";
case IO_TIMEOUT: return "timeout";
default: return "unknown error";
}
}

65
src/luasec/luasocket/io.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef IO_H
#define IO_H
/*=========================================================================*\
* Input/Output abstraction
* LuaSocket toolkit
*
* This module defines the interface that LuaSocket expects from the
* transport layer for streamed input/output. The idea is that if any
* transport implements this interface, then the buffer.c functions
* automatically work on it.
*
* The module socket.h implements this interface, and thus the module tcp.h
* is very simple.
\*=========================================================================*/
#include <stdio.h>
#include "../../lua.h"
#include "timeout.h"
/* IO error codes */
enum {
IO_DONE = 0, /* operation completed successfully */
IO_TIMEOUT = -1, /* operation timed out */
IO_CLOSED = -2, /* the connection has been closed */
IO_UNKNOWN = -3
};
/* interface to error message function */
typedef const char *(*p_error) (
void *ctx, /* context needed by send */
int err /* error code */
);
/* interface to send function */
typedef int (*p_send) (
void *ctx, /* context needed by send */
const char *data, /* pointer to buffer with data to send */
size_t count, /* number of bytes to send from buffer */
size_t *sent, /* number of bytes sent uppon return */
p_timeout tm /* timeout control */
);
/* interface to recv function */
typedef int (*p_recv) (
void *ctx, /* context needed by recv */
char *data, /* pointer to buffer where data will be written */
size_t count, /* number of bytes to receive into buffer */
size_t *got, /* number of bytes received uppon return */
p_timeout tm /* timeout control */
);
/* IO driver definition */
typedef struct t_io_ {
void *ctx; /* context needed by send/recv */
p_send send; /* send function pointer */
p_recv recv; /* receive function pointer */
p_error error; /* strerror function */
} t_io;
typedef t_io *p_io;
void io_init(p_io io, p_send send, p_recv recv, p_error error, void *ctx);
const char *io_strerror(int err);
#endif /* IO_H */

View File

@ -0,0 +1,78 @@
#ifndef SOCKET_H
#define SOCKET_H
/*=========================================================================*\
* Socket compatibilization module
* LuaSocket toolkit
*
* BSD Sockets and WinSock are similar, but there are a few irritating
* differences. Also, not all *nix platforms behave the same. This module
* (and the associated usocket.h and wsocket.h) factor these differences and
* creates a interface compatible with the io.h module.
\*=========================================================================*/
#include "io.h"
/*=========================================================================*\
* Platform specific compatibilization
\*=========================================================================*/
#ifdef _WIN32
#include "wsocket.h"
#else
#include "usocket.h"
#endif
/*=========================================================================*\
* The connect and accept functions accept a timeout and their
* implementations are somewhat complicated. We chose to move
* the timeout control into this module for these functions in
* order to simplify the modules that use them.
\*=========================================================================*/
#include "timeout.h"
/* we are lazy... */
typedef struct sockaddr SA;
/*=========================================================================*\
* Functions below implement a comfortable platform independent
* interface to sockets
\*=========================================================================*/
int socket_open(void);
int socket_close(void);
void socket_destroy(p_socket ps);
void socket_shutdown(p_socket ps, int how);
int socket_sendto(p_socket ps, const char *data, size_t count,
size_t *sent, SA *addr, socklen_t addr_len, p_timeout tm);
int socket_recvfrom(p_socket ps, char *data, size_t count,
size_t *got, SA *addr, socklen_t *addr_len, p_timeout tm);
void socket_setnonblocking(p_socket ps);
void socket_setblocking(p_socket ps);
int socket_waitfd(p_socket ps, int sw, p_timeout tm);
int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds,
p_timeout tm);
int socket_connect(p_socket ps, SA *addr, socklen_t addr_len, p_timeout tm);
int socket_create(p_socket ps, int domain, int type, int protocol);
int socket_bind(p_socket ps, SA *addr, socklen_t addr_len);
int socket_listen(p_socket ps, int backlog);
int socket_accept(p_socket ps, p_socket pa, SA *addr,
socklen_t *addr_len, p_timeout tm);
const char *socket_hoststrerror(int err);
const char *socket_gaistrerror(int err);
const char *socket_strerror(int err);
/* these are perfect to use with the io abstraction module
and the buffered input module */
int socket_send(p_socket ps, const char *data, size_t count,
size_t *sent, p_timeout tm);
int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm);
int socket_write(p_socket ps, const char *data, size_t count,
size_t *sent, p_timeout tm);
int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm);
const char *socket_ioerror(p_socket ps, int err);
int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp);
int socket_gethostbyname(const char *addr, struct hostent **hp);
#endif /* SOCKET_H */

View File

@ -0,0 +1,220 @@
/*=========================================================================*\
* Timeout management functions
* LuaSocket toolkit
\*=========================================================================*/
#include <stdio.h>
#include <limits.h>
#include <float.h>
#include "../../lua.h"
#include "../../lauxlib.h"
#include "timeout.h"
#ifdef _WIN32
#include <windows.h>
#else
#include <time.h>
#include <sys/time.h>
#endif
/* min and max macros */
#ifndef MIN
#define MIN(x, y) ((x) < (y) ? x : y)
#endif
#ifndef MAX
#define MAX(x, y) ((x) > (y) ? x : y)
#endif
/*=========================================================================*\
* Internal function prototypes
\*=========================================================================*/
static int timeout_lua_gettime(lua_State *L);
static int timeout_lua_sleep(lua_State *L);
static luaL_Reg func[] = {
{ "gettime", timeout_lua_gettime },
{ "sleep", timeout_lua_sleep },
{ NULL, NULL }
};
/*=========================================================================*\
* Exported functions.
\*=========================================================================*/
/*-------------------------------------------------------------------------*\
* Initialize structure
\*-------------------------------------------------------------------------*/
void timeout_init(p_timeout tm, double block, double total) {
tm->block = block;
tm->total = total;
}
/*-------------------------------------------------------------------------*\
* Determines how much time we have left for the next system call,
* if the previous call was successful
* Input
* tm: timeout control structure
* Returns
* the number of ms left or -1 if there is no time limit
\*-------------------------------------------------------------------------*/
double timeout_get(p_timeout tm) {
if (tm->block < 0.0 && tm->total < 0.0) {
return -1;
} else if (tm->block < 0.0) {
double t = tm->total - timeout_gettime() + tm->start;
return MAX(t, 0.0);
} else if (tm->total < 0.0) {
return tm->block;
} else {
double t = tm->total - timeout_gettime() + tm->start;
return MIN(tm->block, MAX(t, 0.0));
}
}
/*-------------------------------------------------------------------------*\
* Returns time since start of operation
* Input
* tm: timeout control structure
* Returns
* start field of structure
\*-------------------------------------------------------------------------*/
double timeout_getstart(p_timeout tm) {
return tm->start;
}
/*-------------------------------------------------------------------------*\
* Determines how much time we have left for the next system call,
* if the previous call was a failure
* Input
* tm: timeout control structure
* Returns
* the number of ms left or -1 if there is no time limit
\*-------------------------------------------------------------------------*/
double timeout_getretry(p_timeout tm) {
if (tm->block < 0.0 && tm->total < 0.0) {
return -1;
} else if (tm->block < 0.0) {
double t = tm->total - timeout_gettime() + tm->start;
return MAX(t, 0.0);
} else if (tm->total < 0.0) {
double t = tm->block - timeout_gettime() + tm->start;
return MAX(t, 0.0);
} else {
double t = tm->total - timeout_gettime() + tm->start;
return MIN(tm->block, MAX(t, 0.0));
}
}
/*-------------------------------------------------------------------------*\
* Marks the operation start time in structure
* Input
* tm: timeout control structure
\*-------------------------------------------------------------------------*/
p_timeout timeout_markstart(p_timeout tm) {
tm->start = timeout_gettime();
return tm;
}
/*-------------------------------------------------------------------------*\
* Gets time in s, relative to January 1, 1970 (UTC)
* Returns
* time in s.
\*-------------------------------------------------------------------------*/
#ifdef _WIN32
double timeout_gettime(void) {
FILETIME ft;
double t;
GetSystemTimeAsFileTime(&ft);
/* Windows file time (time since January 1, 1601 (UTC)) */
t = ft.dwLowDateTime/1.0e7 + ft.dwHighDateTime*(4294967296.0/1.0e7);
/* convert to Unix Epoch time (time since January 1, 1970 (UTC)) */
return (t - 11644473600.0);
}
#else
double timeout_gettime(void) {
struct timeval v;
gettimeofday(&v, (struct timezone *) NULL);
/* Unix Epoch time (time since January 1, 1970 (UTC)) */
return v.tv_sec + v.tv_usec/1.0e6;
}
#endif
/*-------------------------------------------------------------------------*\
* Initializes module
\*-------------------------------------------------------------------------*/
int timeout_open(lua_State *L) {
#if LUA_VERSION_NUM > 501 && !defined(LUA_COMPAT_MODULE)
luaL_setfuncs(L, func, 0);
#else
luaL_openlib(L, NULL, func, 0);
#endif
return 0;
}
/*-------------------------------------------------------------------------*\
* Sets timeout values for IO operations
* Lua Input: base, time [, mode]
* time: time out value in seconds
* mode: "b" for block timeout, "t" for total timeout. (default: b)
\*-------------------------------------------------------------------------*/
int timeout_meth_settimeout(lua_State *L, p_timeout tm) {
double t = luaL_optnumber(L, 2, -1);
const char *mode = luaL_optstring(L, 3, "b");
switch (*mode) {
case 'b':
tm->block = t;
break;
case 'r': case 't':
tm->total = t;
break;
default:
luaL_argcheck(L, 0, 3, "invalid timeout mode");
break;
}
lua_pushnumber(L, 1);
return 1;
}
/*=========================================================================*\
* Test support functions
\*=========================================================================*/
/*-------------------------------------------------------------------------*\
* Returns the time the system has been up, in secconds.
\*-------------------------------------------------------------------------*/
static int timeout_lua_gettime(lua_State *L)
{
lua_pushnumber(L, timeout_gettime());
return 1;
}
/*-------------------------------------------------------------------------*\
* Sleep for n seconds.
\*-------------------------------------------------------------------------*/
#ifdef _WIN32
int timeout_lua_sleep(lua_State *L)
{
double n = luaL_checknumber(L, 1);
if (n < 0.0) n = 0.0;
if (n < DBL_MAX/1000.0) n *= 1000.0;
if (n > INT_MAX) n = INT_MAX;
Sleep((int)n);
return 0;
}
#else
int timeout_lua_sleep(lua_State *L)
{
double n = luaL_checknumber(L, 1);
struct timespec t, r;
if (n < 0.0) n = 0.0;
if (n > INT_MAX) n = INT_MAX;
t.tv_sec = (int) n;
n -= t.tv_sec;
t.tv_nsec = (int) (n * 1000000000);
if (t.tv_nsec >= 1000000000) t.tv_nsec = 999999999;
while (nanosleep(&t, &r) != 0) {
t.tv_sec = r.tv_sec;
t.tv_nsec = r.tv_nsec;
}
return 0;
}
#endif

View File

@ -0,0 +1,28 @@
#ifndef TIMEOUT_H
#define TIMEOUT_H
/*=========================================================================*\
* Timeout management functions
* LuaSocket toolkit
\*=========================================================================*/
#include "../../lua.h"
/* timeout control structure */
typedef struct t_timeout_ {
double block; /* maximum time for blocking calls */
double total; /* total number of milliseconds for operation */
double start; /* time of start of operation */
} t_timeout;
typedef t_timeout *p_timeout;
int timeout_open(lua_State *L);
void timeout_init(p_timeout tm, double block, double total);
double timeout_get(p_timeout tm);
double timeout_getretry(p_timeout tm);
p_timeout timeout_markstart(p_timeout tm);
double timeout_getstart(p_timeout tm);
double timeout_gettime(void);
int timeout_meth_settimeout(lua_State *L, p_timeout tm);
#define timeout_iszero(tm) ((tm)->block == 0.0)
#endif /* TIMEOUT_H */

View File

@ -0,0 +1,439 @@
/*=========================================================================*\
* Socket compatibilization module for Unix
* LuaSocket toolkit
*
* The code is now interrupt-safe.
* The penalty of calling select to avoid busy-wait is only paid when
* the I/O call fail in the first place.
\*=========================================================================*/
#include <string.h>
#include <signal.h>
#include "socket.h"
/*-------------------------------------------------------------------------*\
* Wait for readable/writable/connected socket with timeout
\*-------------------------------------------------------------------------*/
#ifndef SOCKET_SELECT
int socket_waitfd(p_socket ps, int sw, p_timeout tm) {
int ret;
struct pollfd pfd;
pfd.fd = *ps;
pfd.events = sw;
pfd.revents = 0;
if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */
do {
int t = (int)(timeout_getretry(tm)*1e3);
ret = poll(&pfd, 1, t >= 0? t: -1);
} while (ret == -1 && errno == EINTR);
if (ret == -1) return errno;
if (ret == 0) return IO_TIMEOUT;
if (sw == WAITFD_C && (pfd.revents & (POLLIN|POLLERR))) return IO_CLOSED;
return IO_DONE;
}
#else
int socket_waitfd(p_socket ps, int sw, p_timeout tm) {
int ret;
fd_set rfds, wfds, *rp, *wp;
struct timeval tv, *tp;
double t;
if (*ps >= FD_SETSIZE) return EINVAL;
if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */
do {
/* must set bits within loop, because select may have modified them */
rp = wp = NULL;
if (sw & WAITFD_R) { FD_ZERO(&rfds); FD_SET(*ps, &rfds); rp = &rfds; }
if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; }
t = timeout_getretry(tm);
tp = NULL;
if (t >= 0.0) {
tv.tv_sec = (int)t;
tv.tv_usec = (int)((t-tv.tv_sec)*1.0e6);
tp = &tv;
}
ret = select(*ps+1, rp, wp, NULL, tp);
} while (ret == -1 && errno == EINTR);
if (ret == -1) return errno;
if (ret == 0) return IO_TIMEOUT;
if (sw == WAITFD_C && FD_ISSET(*ps, &rfds)) return IO_CLOSED;
return IO_DONE;
}
#endif
/*-------------------------------------------------------------------------*\
* Initializes module
\*-------------------------------------------------------------------------*/
int socket_open(void) {
/* instals a handler to ignore sigpipe or it will crash us */
signal(SIGPIPE, SIG_IGN);
return 1;
}
/*-------------------------------------------------------------------------*\
* Close module
\*-------------------------------------------------------------------------*/
int socket_close(void) {
return 1;
}
/*-------------------------------------------------------------------------*\
* Close and inutilize socket
\*-------------------------------------------------------------------------*/
void socket_destroy(p_socket ps) {
if (*ps != SOCKET_INVALID) {
socket_setblocking(ps);
close(*ps);
*ps = SOCKET_INVALID;
}
}
/*-------------------------------------------------------------------------*\
* Select with timeout control
\*-------------------------------------------------------------------------*/
int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds,
p_timeout tm) {
int ret;
do {
struct timeval tv;
double t = timeout_getretry(tm);
tv.tv_sec = (int) t;
tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6);
/* timeout = 0 means no wait */
ret = select(n, rfds, wfds, efds, t >= 0.0 ? &tv: NULL);
} while (ret < 0 && errno == EINTR);
return ret;
}
/*-------------------------------------------------------------------------*\
* Creates and sets up a socket
\*-------------------------------------------------------------------------*/
int socket_create(p_socket ps, int domain, int type, int protocol) {
*ps = socket(domain, type, protocol);
if (*ps != SOCKET_INVALID) return IO_DONE;
else return errno;
}
/*-------------------------------------------------------------------------*\
* Binds or returns error message
\*-------------------------------------------------------------------------*/
int socket_bind(p_socket ps, SA *addr, socklen_t len) {
int err = IO_DONE;
socket_setblocking(ps);
if (bind(*ps, addr, len) < 0) err = errno;
socket_setnonblocking(ps);
return err;
}
/*-------------------------------------------------------------------------*\
*
\*-------------------------------------------------------------------------*/
int socket_listen(p_socket ps, int backlog) {
int err = IO_DONE;
socket_setblocking(ps);
if (listen(*ps, backlog)) err = errno;
socket_setnonblocking(ps);
return err;
}
/*-------------------------------------------------------------------------*\
*
\*-------------------------------------------------------------------------*/
void socket_shutdown(p_socket ps, int how) {
socket_setblocking(ps);
shutdown(*ps, how);
socket_setnonblocking(ps);
}
/*-------------------------------------------------------------------------*\
* Connects or returns error message
\*-------------------------------------------------------------------------*/
int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) {
int err;
/* avoid calling on closed sockets */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* call connect until done or failed without being interrupted */
do if (connect(*ps, addr, len) == 0) return IO_DONE;
while ((err = errno) == EINTR);
/* if connection failed immediately, return error code */
if (err != EINPROGRESS && err != EAGAIN) return err;
/* zero timeout case optimization */
if (timeout_iszero(tm)) return IO_TIMEOUT;
/* wait until we have the result of the connection attempt or timeout */
err = socket_waitfd(ps, WAITFD_C, tm);
if (err == IO_CLOSED) {
if (recv(*ps, (char *) &err, 0, 0) == 0) return IO_DONE;
else return errno;
} else return err;
}
/*-------------------------------------------------------------------------*\
* Accept with timeout
\*-------------------------------------------------------------------------*/
int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len, p_timeout tm) {
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
int err;
if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE;
err = errno;
if (err == EINTR) continue;
if (err != EAGAIN && err != ECONNABORTED) return err;
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
/* can't reach here */
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Send with timeout
\*-------------------------------------------------------------------------*/
int socket_send(p_socket ps, const char *data, size_t count,
size_t *sent, p_timeout tm)
{
int err;
*sent = 0;
/* avoid making system calls on closed sockets */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* loop until we send something or we give up on error */
for ( ;; ) {
long put = (long) send(*ps, data, count, 0);
/* if we sent anything, we are done */
if (put >= 0) {
*sent = put;
return IO_DONE;
}
err = errno;
/* EPIPE means the connection was closed */
if (err == EPIPE) return IO_CLOSED;
/* we call was interrupted, just try again */
if (err == EINTR) continue;
/* if failed fatal reason, report error */
if (err != EAGAIN) return err;
/* wait until we can send something or we timeout */
if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err;
}
/* can't reach here */
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Sendto with timeout
\*-------------------------------------------------------------------------*/
int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent,
SA *addr, socklen_t len, p_timeout tm)
{
int err;
*sent = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
long put = (long) sendto(*ps, data, count, 0, addr, len);
if (put >= 0) {
*sent = put;
return IO_DONE;
}
err = errno;
if (err == EPIPE) return IO_CLOSED;
if (err == EINTR) continue;
if (err != EAGAIN) return err;
if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err;
}
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Receive with timeout
\*-------------------------------------------------------------------------*/
int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) {
int err;
*got = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
long taken = (long) recv(*ps, data, count, 0);
if (taken > 0) {
*got = taken;
return IO_DONE;
}
err = errno;
if (taken == 0) return IO_CLOSED;
if (err == EINTR) continue;
if (err != EAGAIN) return err;
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Recvfrom with timeout
\*-------------------------------------------------------------------------*/
int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got,
SA *addr, socklen_t *len, p_timeout tm) {
int err;
*got = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
long taken = (long) recvfrom(*ps, data, count, 0, addr, len);
if (taken > 0) {
*got = taken;
return IO_DONE;
}
err = errno;
if (taken == 0) return IO_CLOSED;
if (err == EINTR) continue;
if (err != EAGAIN) return err;
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Write with timeout
*
* socket_read and socket_write are cut-n-paste of socket_send and socket_recv,
* with send/recv replaced with write/read. We can't just use write/read
* in the socket version, because behaviour when size is zero is different.
\*-------------------------------------------------------------------------*/
int socket_write(p_socket ps, const char *data, size_t count,
size_t *sent, p_timeout tm)
{
int err;
*sent = 0;
/* avoid making system calls on closed sockets */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* loop until we send something or we give up on error */
for ( ;; ) {
long put = (long) write(*ps, data, count);
/* if we sent anything, we are done */
if (put >= 0) {
*sent = put;
return IO_DONE;
}
err = errno;
/* EPIPE means the connection was closed */
if (err == EPIPE) return IO_CLOSED;
/* we call was interrupted, just try again */
if (err == EINTR) continue;
/* if failed fatal reason, report error */
if (err != EAGAIN) return err;
/* wait until we can send something or we timeout */
if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err;
}
/* can't reach here */
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Read with timeout
* See note for socket_write
\*-------------------------------------------------------------------------*/
int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) {
int err;
*got = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
long taken = (long) read(*ps, data, count);
if (taken > 0) {
*got = taken;
return IO_DONE;
}
err = errno;
if (taken == 0) return IO_CLOSED;
if (err == EINTR) continue;
if (err != EAGAIN) return err;
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Put socket into blocking mode
\*-------------------------------------------------------------------------*/
void socket_setblocking(p_socket ps) {
int flags = fcntl(*ps, F_GETFL, 0);
flags &= (~(O_NONBLOCK));
fcntl(*ps, F_SETFL, flags);
}
/*-------------------------------------------------------------------------*\
* Put socket into non-blocking mode
\*-------------------------------------------------------------------------*/
void socket_setnonblocking(p_socket ps) {
int flags = fcntl(*ps, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(*ps, F_SETFL, flags);
}
/*-------------------------------------------------------------------------*\
* DNS helpers
\*-------------------------------------------------------------------------*/
int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) {
*hp = gethostbyaddr(addr, len, AF_INET);
if (*hp) return IO_DONE;
else if (h_errno) return h_errno;
else if (errno) return errno;
else return IO_UNKNOWN;
}
int socket_gethostbyname(const char *addr, struct hostent **hp) {
*hp = gethostbyname(addr);
if (*hp) return IO_DONE;
else if (h_errno) return h_errno;
else if (errno) return errno;
else return IO_UNKNOWN;
}
/*-------------------------------------------------------------------------*\
* Error translation functions
* Make sure important error messages are standard
\*-------------------------------------------------------------------------*/
const char *socket_hoststrerror(int err) {
if (err <= 0) return io_strerror(err);
switch (err) {
case HOST_NOT_FOUND: return "host not found";
default: return hstrerror(err);
}
}
const char *socket_strerror(int err) {
if (err <= 0) return io_strerror(err);
switch (err) {
case EADDRINUSE: return "address already in use";
case EISCONN: return "already connected";
case EACCES: return "permission denied";
case ECONNREFUSED: return "connection refused";
case ECONNABORTED: return "closed";
case ECONNRESET: return "closed";
case ETIMEDOUT: return "timeout";
default: return strerror(err);
}
}
const char *socket_ioerror(p_socket ps, int err) {
(void) ps;
return socket_strerror(err);
}
const char *socket_gaistrerror(int err) {
if (err == 0) return NULL;
switch (err) {
case EAI_AGAIN: return "temporary failure in name resolution";
case EAI_BADFLAGS: return "invalid value for ai_flags";
#ifdef EAI_BADHINTS
case EAI_BADHINTS: return "invalid value for hints";
#endif
case EAI_FAIL: return "non-recoverable failure in name resolution";
case EAI_FAMILY: return "ai_family not supported";
case EAI_MEMORY: return "memory allocation failure";
case EAI_NONAME:
return "host or service not provided, or not known";
case EAI_OVERFLOW: return "argument buffer overflow";
#ifdef EAI_PROTOCOL
case EAI_PROTOCOL: return "resolved protocol is unknown";
#endif
case EAI_SERVICE: return "service not supported for socket type";
case EAI_SOCKTYPE: return "ai_socktype not supported";
case EAI_SYSTEM: return strerror(errno);
default: return gai_strerror(err);
}
}

View File

@ -0,0 +1,70 @@
#ifndef USOCKET_H
#define USOCKET_H
/*=========================================================================*\
* Socket compatibilization module for Unix
* LuaSocket toolkit
\*=========================================================================*/
/*=========================================================================*\
* BSD include files
\*=========================================================================*/
/* error codes */
#include <errno.h>
/* close function */
#include <unistd.h>
/* fnctnl function and associated constants */
#include <fcntl.h>
/* struct sockaddr */
#include <sys/types.h>
/* socket function */
#include <sys/socket.h>
/* struct timeval */
#include <sys/time.h>
/* gethostbyname and gethostbyaddr functions */
#include <netdb.h>
/* sigpipe handling */
#include <signal.h>
/* IP stuff*/
#include <netinet/in.h>
#include <arpa/inet.h>
/* TCP options (nagle algorithm disable) */
#include <netinet/tcp.h>
#include <net/if.h>
#ifndef SOCKET_SELECT
#include <sys/poll.h>
#define WAITFD_R POLLIN
#define WAITFD_W POLLOUT
#define WAITFD_C (POLLIN|POLLOUT)
#else
#define WAITFD_R 1
#define WAITFD_W 2
#define WAITFD_C (WAITFD_R|WAITFD_W)
#endif
#ifndef SO_REUSEPORT
#define SO_REUSEPORT SO_REUSEADDR
#endif
/* Some platforms use IPV6_JOIN_GROUP instead if
* IPV6_ADD_MEMBERSHIP. The semantics are same, though. */
#ifndef IPV6_ADD_MEMBERSHIP
#ifdef IPV6_JOIN_GROUP
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#endif /* IPV6_JOIN_GROUP */
#endif /* !IPV6_ADD_MEMBERSHIP */
/* Same with IPV6_DROP_MEMBERSHIP / IPV6_LEAVE_GROUP. */
#ifndef IPV6_DROP_MEMBERSHIP
#ifdef IPV6_LEAVE_GROUP
#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
#endif /* IPV6_LEAVE_GROUP */
#endif /* !IPV6_DROP_MEMBERSHIP */
typedef int t_socket;
typedef t_socket *p_socket;
typedef struct sockaddr_storage t_sockaddr_storage;
#define SOCKET_INVALID (-1)
#endif /* USOCKET_H */

View File

@ -0,0 +1,429 @@
/*=========================================================================*\
* Socket compatibilization module for Win32
* LuaSocket toolkit
*
* The penalty of calling select to avoid busy-wait is only paid when
* the I/O call fail in the first place.
\*=========================================================================*/
#include <string.h>
#include "socket.h"
/* WinSock doesn't have a strerror... */
static const char *wstrerror(int err);
/*-------------------------------------------------------------------------*\
* Initializes module
\*-------------------------------------------------------------------------*/
int socket_open(void) {
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 0);
int err = WSAStartup(wVersionRequested, &wsaData );
if (err != 0) return 0;
if ((LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 0) &&
(LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)) {
WSACleanup();
return 0;
}
return 1;
}
/*-------------------------------------------------------------------------*\
* Close module
\*-------------------------------------------------------------------------*/
int socket_close(void) {
WSACleanup();
return 1;
}
/*-------------------------------------------------------------------------*\
* Wait for readable/writable/connected socket with timeout
\*-------------------------------------------------------------------------*/
int socket_waitfd(p_socket ps, int sw, p_timeout tm) {
int ret;
fd_set rfds, wfds, efds, *rp = NULL, *wp = NULL, *ep = NULL;
struct timeval tv, *tp = NULL;
double t;
if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */
if (sw & WAITFD_R) {
FD_ZERO(&rfds);
FD_SET(*ps, &rfds);
rp = &rfds;
}
if (sw & WAITFD_W) { FD_ZERO(&wfds); FD_SET(*ps, &wfds); wp = &wfds; }
if (sw & WAITFD_C) { FD_ZERO(&efds); FD_SET(*ps, &efds); ep = &efds; }
if ((t = timeout_get(tm)) >= 0.0) {
tv.tv_sec = (int) t;
tv.tv_usec = (int) ((t-tv.tv_sec)*1.0e6);
tp = &tv;
}
ret = select(0, rp, wp, ep, tp);
if (ret == -1) return WSAGetLastError();
if (ret == 0) return IO_TIMEOUT;
if (sw == WAITFD_C && FD_ISSET(*ps, &efds)) return IO_CLOSED;
return IO_DONE;
}
/*-------------------------------------------------------------------------*\
* Select with int timeout in ms
\*-------------------------------------------------------------------------*/
int socket_select(t_socket n, fd_set *rfds, fd_set *wfds, fd_set *efds,
p_timeout tm) {
struct timeval tv;
double t = timeout_get(tm);
tv.tv_sec = (int) t;
tv.tv_usec = (int) ((t - tv.tv_sec) * 1.0e6);
if (n <= 0) {
Sleep((DWORD) (1000*t));
return 0;
} else return select(0, rfds, wfds, efds, t >= 0.0? &tv: NULL);
}
/*-------------------------------------------------------------------------*\
* Close and inutilize socket
\*-------------------------------------------------------------------------*/
void socket_destroy(p_socket ps) {
if (*ps != SOCKET_INVALID) {
socket_setblocking(ps); /* close can take a long time on WIN32 */
closesocket(*ps);
*ps = SOCKET_INVALID;
}
}
/*-------------------------------------------------------------------------*\
*
\*-------------------------------------------------------------------------*/
void socket_shutdown(p_socket ps, int how) {
socket_setblocking(ps);
shutdown(*ps, how);
socket_setnonblocking(ps);
}
/*-------------------------------------------------------------------------*\
* Creates and sets up a socket
\*-------------------------------------------------------------------------*/
int socket_create(p_socket ps, int domain, int type, int protocol) {
*ps = socket(domain, type, protocol);
if (*ps != SOCKET_INVALID) return IO_DONE;
else return WSAGetLastError();
}
/*-------------------------------------------------------------------------*\
* Connects or returns error message
\*-------------------------------------------------------------------------*/
int socket_connect(p_socket ps, SA *addr, socklen_t len, p_timeout tm) {
int err;
/* don't call on closed socket */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* ask system to connect */
if (connect(*ps, addr, len) == 0) return IO_DONE;
/* make sure the system is trying to connect */
err = WSAGetLastError();
if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return err;
/* zero timeout case optimization */
if (timeout_iszero(tm)) return IO_TIMEOUT;
/* we wait until something happens */
err = socket_waitfd(ps, WAITFD_C, tm);
if (err == IO_CLOSED) {
int len = sizeof(err);
/* give windows time to set the error (yes, disgusting) */
Sleep(10);
/* find out why we failed */
getsockopt(*ps, SOL_SOCKET, SO_ERROR, (char *)&err, &len);
/* we KNOW there was an error. if 'why' is 0, we will return
* "unknown error", but it's not really our fault */
return err > 0? err: IO_UNKNOWN;
} else return err;
}
/*-------------------------------------------------------------------------*\
* Binds or returns error message
\*-------------------------------------------------------------------------*/
int socket_bind(p_socket ps, SA *addr, socklen_t len) {
int err = IO_DONE;
socket_setblocking(ps);
if (bind(*ps, addr, len) < 0) err = WSAGetLastError();
socket_setnonblocking(ps);
return err;
}
/*-------------------------------------------------------------------------*\
*
\*-------------------------------------------------------------------------*/
int socket_listen(p_socket ps, int backlog) {
int err = IO_DONE;
socket_setblocking(ps);
if (listen(*ps, backlog) < 0) err = WSAGetLastError();
socket_setnonblocking(ps);
return err;
}
/*-------------------------------------------------------------------------*\
* Accept with timeout
\*-------------------------------------------------------------------------*/
int socket_accept(p_socket ps, p_socket pa, SA *addr, socklen_t *len,
p_timeout tm) {
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
int err;
/* try to get client socket */
if ((*pa = accept(*ps, addr, len)) != SOCKET_INVALID) return IO_DONE;
/* find out why we failed */
err = WSAGetLastError();
/* if we failed because there was no connectoin, keep trying */
if (err != WSAEWOULDBLOCK && err != WSAECONNABORTED) return err;
/* call select to avoid busy wait */
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
}
/*-------------------------------------------------------------------------*\
* Send with timeout
* On windows, if you try to send 10MB, the OS will buffer EVERYTHING
* this can take an awful lot of time and we will end up blocked.
* Therefore, whoever calls this function should not pass a huge buffer.
\*-------------------------------------------------------------------------*/
int socket_send(p_socket ps, const char *data, size_t count,
size_t *sent, p_timeout tm)
{
int err;
*sent = 0;
/* avoid making system calls on closed sockets */
if (*ps == SOCKET_INVALID) return IO_CLOSED;
/* loop until we send something or we give up on error */
for ( ;; ) {
/* try to send something */
int put = send(*ps, data, (int) count, 0);
/* if we sent something, we are done */
if (put > 0) {
*sent = put;
return IO_DONE;
}
/* deal with failure */
err = WSAGetLastError();
/* we can only proceed if there was no serious error */
if (err != WSAEWOULDBLOCK) return err;
/* avoid busy wait */
if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err;
}
}
/*-------------------------------------------------------------------------*\
* Sendto with timeout
\*-------------------------------------------------------------------------*/
int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent,
SA *addr, socklen_t len, p_timeout tm)
{
int err;
*sent = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
int put = sendto(*ps, data, (int) count, 0, addr, len);
if (put > 0) {
*sent = put;
return IO_DONE;
}
err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) return err;
if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err;
}
}
/*-------------------------------------------------------------------------*\
* Receive with timeout
\*-------------------------------------------------------------------------*/
int socket_recv(p_socket ps, char *data, size_t count, size_t *got,
p_timeout tm)
{
int err, prev = IO_DONE;
*got = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
int taken = recv(*ps, data, (int) count, 0);
if (taken > 0) {
*got = taken;
return IO_DONE;
}
if (taken == 0) return IO_CLOSED;
err = WSAGetLastError();
/* On UDP, a connreset simply means the previous send failed.
* So we try again.
* On TCP, it means our socket is now useless, so the error passes.
* (We will loop again, exiting because the same error will happen) */
if (err != WSAEWOULDBLOCK) {
if (err != WSAECONNRESET || prev == WSAECONNRESET) return err;
prev = err;
}
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
}
/*-------------------------------------------------------------------------*\
* Recvfrom with timeout
\*-------------------------------------------------------------------------*/
int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got,
SA *addr, socklen_t *len, p_timeout tm)
{
int err, prev = IO_DONE;
*got = 0;
if (*ps == SOCKET_INVALID) return IO_CLOSED;
for ( ;; ) {
int taken = recvfrom(*ps, data, (int) count, 0, addr, len);
if (taken > 0) {
*got = taken;
return IO_DONE;
}
if (taken == 0) return IO_CLOSED;
err = WSAGetLastError();
/* On UDP, a connreset simply means the previous send failed.
* So we try again.
* On TCP, it means our socket is now useless, so the error passes.
* (We will loop again, exiting because the same error will happen) */
if (err != WSAEWOULDBLOCK) {
if (err != WSAECONNRESET || prev == WSAECONNRESET) return err;
prev = err;
}
if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err;
}
}
/*-------------------------------------------------------------------------*\
* Put socket into blocking mode
\*-------------------------------------------------------------------------*/
void socket_setblocking(p_socket ps) {
u_long argp = 0;
ioctlsocket(*ps, FIONBIO, &argp);
}
/*-------------------------------------------------------------------------*\
* Put socket into non-blocking mode
\*-------------------------------------------------------------------------*/
void socket_setnonblocking(p_socket ps) {
u_long argp = 1;
ioctlsocket(*ps, FIONBIO, &argp);
}
/*-------------------------------------------------------------------------*\
* DNS helpers
\*-------------------------------------------------------------------------*/
int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp) {
*hp = gethostbyaddr(addr, len, AF_INET);
if (*hp) return IO_DONE;
else return WSAGetLastError();
}
int socket_gethostbyname(const char *addr, struct hostent **hp) {
*hp = gethostbyname(addr);
if (*hp) return IO_DONE;
else return WSAGetLastError();
}
/*-------------------------------------------------------------------------*\
* Error translation functions
\*-------------------------------------------------------------------------*/
const char *socket_hoststrerror(int err) {
if (err <= 0) return io_strerror(err);
switch (err) {
case WSAHOST_NOT_FOUND: return "host not found";
default: return wstrerror(err);
}
}
const char *socket_strerror(int err) {
if (err <= 0) return io_strerror(err);
switch (err) {
case WSAEADDRINUSE: return "address already in use";
case WSAECONNREFUSED: return "connection refused";
case WSAEISCONN: return "already connected";
case WSAEACCES: return "permission denied";
case WSAECONNABORTED: return "closed";
case WSAECONNRESET: return "closed";
case WSAETIMEDOUT: return "timeout";
default: return wstrerror(err);
}
}
const char *socket_ioerror(p_socket ps, int err) {
(void) ps;
return socket_strerror(err);
}
static const char *wstrerror(int err) {
switch (err) {
case WSAEINTR: return "Interrupted function call";
case WSAEACCES: return "Permission denied";
case WSAEFAULT: return "Bad address";
case WSAEINVAL: return "Invalid argument";
case WSAEMFILE: return "Too many open files";
case WSAEWOULDBLOCK: return "Resource temporarily unavailable";
case WSAEINPROGRESS: return "Operation now in progress";
case WSAEALREADY: return "Operation already in progress";
case WSAENOTSOCK: return "Socket operation on nonsocket";
case WSAEDESTADDRREQ: return "Destination address required";
case WSAEMSGSIZE: return "Message too long";
case WSAEPROTOTYPE: return "Protocol wrong type for socket";
case WSAENOPROTOOPT: return "Bad protocol option";
case WSAEPROTONOSUPPORT: return "Protocol not supported";
case WSAESOCKTNOSUPPORT: return "Socket type not supported";
case WSAEOPNOTSUPP: return "Operation not supported";
case WSAEPFNOSUPPORT: return "Protocol family not supported";
case WSAEAFNOSUPPORT:
return "Address family not supported by protocol family";
case WSAEADDRINUSE: return "Address already in use";
case WSAEADDRNOTAVAIL: return "Cannot assign requested address";
case WSAENETDOWN: return "Network is down";
case WSAENETUNREACH: return "Network is unreachable";
case WSAENETRESET: return "Network dropped connection on reset";
case WSAECONNABORTED: return "Software caused connection abort";
case WSAECONNRESET: return "Connection reset by peer";
case WSAENOBUFS: return "No buffer space available";
case WSAEISCONN: return "Socket is already connected";
case WSAENOTCONN: return "Socket is not connected";
case WSAESHUTDOWN: return "Cannot send after socket shutdown";
case WSAETIMEDOUT: return "Connection timed out";
case WSAECONNREFUSED: return "Connection refused";
case WSAEHOSTDOWN: return "Host is down";
case WSAEHOSTUNREACH: return "No route to host";
case WSAEPROCLIM: return "Too many processes";
case WSASYSNOTREADY: return "Network subsystem is unavailable";
case WSAVERNOTSUPPORTED: return "Winsock.dll version out of range";
case WSANOTINITIALISED:
return "Successful WSAStartup not yet performed";
case WSAEDISCON: return "Graceful shutdown in progress";
case WSAHOST_NOT_FOUND: return "Host not found";
case WSATRY_AGAIN: return "Nonauthoritative host not found";
case WSANO_RECOVERY: return "Nonrecoverable name lookup error";
case WSANO_DATA: return "Valid name, no data record of requested type";
default: return "Unknown error";
}
}
const char *socket_gaistrerror(int err) {
if (err == 0) return NULL;
switch (err) {
case EAI_AGAIN: return "temporary failure in name resolution";
case EAI_BADFLAGS: return "invalid value for ai_flags";
#ifdef EAI_BADHINTS
case EAI_BADHINTS: return "invalid value for hints";
#endif
case EAI_FAIL: return "non-recoverable failure in name resolution";
case EAI_FAMILY: return "ai_family not supported";
case EAI_MEMORY: return "memory allocation failure";
case EAI_NONAME:
return "host or service not provided, or not known";
#ifdef EAI_OVERFLOW
case EAI_OVERFLOW: return "argument buffer overflow";
#endif
#ifdef EAI_PROTOCOL
case EAI_PROTOCOL: return "resolved protocol is unknown";
#endif
case EAI_SERVICE: return "service not supported for socket type";
case EAI_SOCKTYPE: return "ai_socktype not supported";
#ifdef EAI_SYSTEM
case EAI_SYSTEM: return strerror(errno);
#endif
default: return gai_strerror(err);
}
}

View File

@ -0,0 +1,38 @@
#ifndef WSOCKET_H
#define WSOCKET_H
/*=========================================================================*\
* Socket compatibilization module for Win32
* LuaSocket toolkit
\*=========================================================================*/
/*=========================================================================*\
* WinSock include files
\*=========================================================================*/
#include <winsock2.h>
#include <ws2tcpip.h>
typedef int socklen_t;
typedef SOCKADDR_STORAGE t_sockaddr_storage;
typedef SOCKET t_socket;
typedef t_socket *p_socket;
#define WAITFD_R 1
#define WAITFD_W 2
#define WAITFD_E 4
#define WAITFD_C (WAITFD_E|WAITFD_W)
#ifndef IPV6_V6ONLY
#define IPV6_V6ONLY 27
#endif
#define SOCKET_INVALID (INVALID_SOCKET)
#ifndef SO_REUSEPORT
#define SO_REUSEPORT SO_REUSEADDR
#endif
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV (0)
#endif
#endif /* WSOCKET_H */

185
src/luasec/options.c Normal file
View File

@ -0,0 +1,185 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2006-2021 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#include <openssl/ssl.h>
#include "options.h"
/* If you need to generate these options again, see options.lua */
/*
OpenSSL version: OpenSSL 3.0.0-beta2
*/
static lsec_ssl_option_t ssl_options[] = {
#if defined(SSL_OP_ALL)
{"all", SSL_OP_ALL},
#endif
#if defined(SSL_OP_ALLOW_CLIENT_RENEGOTIATION)
{"allow_client_renegotiation", SSL_OP_ALLOW_CLIENT_RENEGOTIATION},
#endif
#if defined(SSL_OP_ALLOW_NO_DHE_KEX)
{"allow_no_dhe_kex", SSL_OP_ALLOW_NO_DHE_KEX},
#endif
#if defined(SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION)
{"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION},
#endif
#if defined(SSL_OP_CIPHER_SERVER_PREFERENCE)
{"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE},
#endif
#if defined(SSL_OP_CISCO_ANYCONNECT)
{"cisco_anyconnect", SSL_OP_CISCO_ANYCONNECT},
#endif
#if defined(SSL_OP_CLEANSE_PLAINTEXT)
{"cleanse_plaintext", SSL_OP_CLEANSE_PLAINTEXT},
#endif
#if defined(SSL_OP_COOKIE_EXCHANGE)
{"cookie_exchange", SSL_OP_COOKIE_EXCHANGE},
#endif
#if defined(SSL_OP_CRYPTOPRO_TLSEXT_BUG)
{"cryptopro_tlsext_bug", SSL_OP_CRYPTOPRO_TLSEXT_BUG},
#endif
#if defined(SSL_OP_DISABLE_TLSEXT_CA_NAMES)
{"disable_tlsext_ca_names", SSL_OP_DISABLE_TLSEXT_CA_NAMES},
#endif
#if defined(SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS)
{"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS},
#endif
#if defined(SSL_OP_ENABLE_KTLS)
{"enable_ktls", SSL_OP_ENABLE_KTLS},
#endif
#if defined(SSL_OP_ENABLE_MIDDLEBOX_COMPAT)
{"enable_middlebox_compat", SSL_OP_ENABLE_MIDDLEBOX_COMPAT},
#endif
#if defined(SSL_OP_EPHEMERAL_RSA)
{"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA},
#endif
#if defined(SSL_OP_IGNORE_UNEXPECTED_EOF)
{"ignore_unexpected_eof", SSL_OP_IGNORE_UNEXPECTED_EOF},
#endif
#if defined(SSL_OP_LEGACY_SERVER_CONNECT)
{"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT},
#endif
#if defined(SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)
{"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER},
#endif
#if defined(SSL_OP_MICROSOFT_SESS_ID_BUG)
{"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG},
#endif
#if defined(SSL_OP_MSIE_SSLV2_RSA_PADDING)
{"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING},
#endif
#if defined(SSL_OP_NETSCAPE_CA_DN_BUG)
{"netscape_ca_dn_bug", SSL_OP_NETSCAPE_CA_DN_BUG},
#endif
#if defined(SSL_OP_NETSCAPE_CHALLENGE_BUG)
{"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG},
#endif
#if defined(SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG)
{"netscape_demo_cipher_change_bug", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG},
#endif
#if defined(SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG)
{"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG},
#endif
#if defined(SSL_OP_NO_ANTI_REPLAY)
{"no_anti_replay", SSL_OP_NO_ANTI_REPLAY},
#endif
#if defined(SSL_OP_NO_COMPRESSION)
{"no_compression", SSL_OP_NO_COMPRESSION},
#endif
#if defined(SSL_OP_NO_DTLS_MASK)
{"no_dtls_mask", SSL_OP_NO_DTLS_MASK},
#endif
#if defined(SSL_OP_NO_DTLSv1)
{"no_dtlsv1", SSL_OP_NO_DTLSv1},
#endif
#if defined(SSL_OP_NO_DTLSv1_2)
{"no_dtlsv1_2", SSL_OP_NO_DTLSv1_2},
#endif
#if defined(SSL_OP_NO_ENCRYPT_THEN_MAC)
{"no_encrypt_then_mac", SSL_OP_NO_ENCRYPT_THEN_MAC},
#endif
#if defined(SSL_OP_NO_EXTENDED_MASTER_SECRET)
{"no_extended_master_secret", SSL_OP_NO_EXTENDED_MASTER_SECRET},
#endif
#if defined(SSL_OP_NO_QUERY_MTU)
{"no_query_mtu", SSL_OP_NO_QUERY_MTU},
#endif
#if defined(SSL_OP_NO_RENEGOTIATION)
{"no_renegotiation", SSL_OP_NO_RENEGOTIATION},
#endif
#if defined(SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)
{"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION},
#endif
#if defined(SSL_OP_NO_SSL_MASK)
{"no_ssl_mask", SSL_OP_NO_SSL_MASK},
#endif
#if defined(SSL_OP_NO_SSLv2)
{"no_sslv2", SSL_OP_NO_SSLv2},
#endif
#if defined(SSL_OP_NO_SSLv3)
{"no_sslv3", SSL_OP_NO_SSLv3},
#endif
#if defined(SSL_OP_NO_TICKET)
{"no_ticket", SSL_OP_NO_TICKET},
#endif
#if defined(SSL_OP_NO_TLSv1)
{"no_tlsv1", SSL_OP_NO_TLSv1},
#endif
#if defined(SSL_OP_NO_TLSv1_1)
{"no_tlsv1_1", SSL_OP_NO_TLSv1_1},
#endif
#if defined(SSL_OP_NO_TLSv1_2)
{"no_tlsv1_2", SSL_OP_NO_TLSv1_2},
#endif
#if defined(SSL_OP_NO_TLSv1_3)
{"no_tlsv1_3", SSL_OP_NO_TLSv1_3},
#endif
#if defined(SSL_OP_PKCS1_CHECK_1)
{"pkcs1_check_1", SSL_OP_PKCS1_CHECK_1},
#endif
#if defined(SSL_OP_PKCS1_CHECK_2)
{"pkcs1_check_2", SSL_OP_PKCS1_CHECK_2},
#endif
#if defined(SSL_OP_PRIORITIZE_CHACHA)
{"prioritize_chacha", SSL_OP_PRIORITIZE_CHACHA},
#endif
#if defined(SSL_OP_SAFARI_ECDHE_ECDSA_BUG)
{"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG},
#endif
#if defined(SSL_OP_SINGLE_DH_USE)
{"single_dh_use", SSL_OP_SINGLE_DH_USE},
#endif
#if defined(SSL_OP_SINGLE_ECDH_USE)
{"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE},
#endif
#if defined(SSL_OP_SSLEAY_080_CLIENT_DH_BUG)
{"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG},
#endif
#if defined(SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG)
{"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG},
#endif
#if defined(SSL_OP_TLSEXT_PADDING)
{"tlsext_padding", SSL_OP_TLSEXT_PADDING},
#endif
#if defined(SSL_OP_TLS_BLOCK_PADDING_BUG)
{"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG},
#endif
#if defined(SSL_OP_TLS_D5_BUG)
{"tls_d5_bug", SSL_OP_TLS_D5_BUG},
#endif
#if defined(SSL_OP_TLS_ROLLBACK_BUG)
{"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG},
#endif
{NULL, 0L}
};
LSEC_API lsec_ssl_option_t* lsec_get_ssl_options() {
return ssl_options;
}

22
src/luasec/options.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef LSEC_OPTIONS_H
#define LSEC_OPTIONS_H
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2006-2021 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#include "compat.h"
struct lsec_ssl_option_s {
const char *name;
unsigned long code;
};
typedef struct lsec_ssl_option_s lsec_ssl_option_t;
LSEC_API lsec_ssl_option_t* lsec_get_ssl_options();
#endif

93
src/luasec/options.lua Normal file
View File

@ -0,0 +1,93 @@
local function usage()
print("Usage:")
print("* Generate options of your system:")
print(" lua options.lua -g /path/to/ssl.h [version] > options.c")
print("* Examples:")
print(" lua options.lua -g /usr/include/openssl/ssl.h > options.c\n")
print(" lua options.lua -g /usr/include/openssl/ssl.h \"OpenSSL 1.1.1f\" > options.c\n")
print("* List options of your system:")
print(" lua options.lua -l /path/to/ssl.h\n")
end
--
local function printf(str, ...)
print(string.format(str, ...))
end
local function generate(options, version)
print([[
/*--------------------------------------------------------------------------
* LuaSec 1.1.1
*
* Copyright (C) 2006-2021 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#include <openssl/ssl.h>
#include "options.h"
/* If you need to generate these options again, see options.lua */
]])
printf([[
/*
OpenSSL version: %s
*/
]], version)
print([[static lsec_ssl_option_t ssl_options[] = {]])
for k, option in ipairs(options) do
local name = string.lower(string.sub(option, 8))
print(string.format([[#if defined(%s)]], option))
print(string.format([[ {"%s", %s},]], name, option))
print([[#endif]])
end
print([[ {NULL, 0L}]])
print([[
};
LSEC_API lsec_ssl_option_t* lsec_get_ssl_options() {
return ssl_options;
}
]])
end
local function loadoptions(file)
local options = {}
local f = assert(io.open(file, "r"))
for line in f:lines() do
local op = string.match(line, "define%s+(SSL_OP_BIT%()")
if not op then
op = string.match(line, "define%s+(SSL_OP_%S+)")
if op then
table.insert(options, op)
end
end
end
table.sort(options, function(a,b) return a<b end)
return options
end
--
local options
local flag, file, version = ...
version = version or "Unknown"
if not file then
usage()
elseif flag == "-g" then
options = loadoptions(file)
generate(options, version)
elseif flag == "-l" then
options = loadoptions(file)
for k, option in ipairs(options) do
print(option)
end
else
usage()
end

970
src/luasec/ssl.c Normal file
View File

@ -0,0 +1,970 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann,
* Matthew Wild.
* Copyright (C) 2006-2021 Bruno Silvestre.
*
*--------------------------------------------------------------------------*/
#include <errno.h>
#include <string.h>
#if defined(WIN32)
#include <winsock2.h>
#endif
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#include <openssl/err.h>
#include <openssl/dh.h>
#include "../lua.h"
#include "../lauxlib.h"
#include "luasocket/io.h"
#include "luasocket/buffer.h"
#include "luasocket/timeout.h"
#include "luasocket/socket.h"
#include "x509.h"
#include "context.h"
#include "ssl.h"
#ifndef LSEC_API_OPENSSL_1_1_0
#define SSL_is_server(s) (s->server)
#define SSL_up_ref(ssl) CRYPTO_add(&(ssl)->references, 1, CRYPTO_LOCK_SSL)
#define X509_up_ref(c) CRYPTO_add(&c->references, 1, CRYPTO_LOCK_X509)
#endif
/**
* Underline socket error.
*/
static int lsec_socket_error()
{
#if defined(WIN32)
return WSAGetLastError();
#else
#if defined(LSEC_OPENSSL_1_1_1)
// Bug in OpenSSL 1.1.1
if (errno == 0)
return LSEC_IO_SSL;
#endif
return errno;
#endif
}
/**
* Map error code into string.
*/
static const char *ssl_ioerror(void *ctx, int err)
{
if (err == LSEC_IO_SSL) {
p_ssl ssl = (p_ssl) ctx;
switch(ssl->error) {
case SSL_ERROR_NONE: return "No error";
case SSL_ERROR_ZERO_RETURN: return "closed";
case SSL_ERROR_WANT_READ: return "wantread";
case SSL_ERROR_WANT_WRITE: return "wantwrite";
case SSL_ERROR_WANT_CONNECT: return "'connect' not completed";
case SSL_ERROR_WANT_ACCEPT: return "'accept' not completed";
case SSL_ERROR_WANT_X509_LOOKUP: return "Waiting for callback";
case SSL_ERROR_SYSCALL: return "System error";
case SSL_ERROR_SSL: return ERR_reason_error_string(ERR_get_error());
default: return "Unknown SSL error";
}
}
return socket_strerror(err);
}
/**
* Close the connection before the GC collect the object.
*/
static int meth_destroy(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state == LSEC_STATE_CONNECTED) {
socket_setblocking(&ssl->sock);
SSL_shutdown(ssl->ssl);
}
if (ssl->sock != SOCKET_INVALID) {
socket_destroy(&ssl->sock);
}
ssl->state = LSEC_STATE_CLOSED;
if (ssl->ssl) {
/* Clear the registries */
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ssl->ssl);
lua_pushnil(L);
lua_settable(L, -3);
luaL_getmetatable(L, "SSL:SNI:Registry");
lua_pushlightuserdata(L, (void*)ssl->ssl);
lua_pushnil(L);
lua_settable(L, -3);
/* Destroy the object */
SSL_free(ssl->ssl);
ssl->ssl = NULL;
}
return 0;
}
/**
* Perform the TLS/SSL handshake
*/
static int handshake(p_ssl ssl)
{
int err;
p_timeout tm = timeout_markstart(&ssl->tm);
if (ssl->state == LSEC_STATE_CLOSED)
return IO_CLOSED;
for ( ; ; ) {
ERR_clear_error();
err = SSL_do_handshake(ssl->ssl);
ssl->error = SSL_get_error(ssl->ssl, err);
switch (ssl->error) {
case SSL_ERROR_NONE:
ssl->state = LSEC_STATE_CONNECTED;
return IO_DONE;
case SSL_ERROR_WANT_READ:
err = socket_waitfd(&ssl->sock, WAITFD_R, tm);
if (err == IO_TIMEOUT) return LSEC_IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_WANT_WRITE:
err = socket_waitfd(&ssl->sock, WAITFD_W, tm);
if (err == IO_TIMEOUT) return LSEC_IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error()) {
ssl->error = SSL_ERROR_SSL;
return LSEC_IO_SSL;
}
if (err == 0)
return IO_CLOSED;
return lsec_socket_error();
default:
return LSEC_IO_SSL;
}
}
return IO_UNKNOWN;
}
/**
* Send data
*/
static int ssl_send(void *ctx, const char *data, size_t count, size_t *sent,
p_timeout tm)
{
int err;
p_ssl ssl = (p_ssl)ctx;
if (ssl->state != LSEC_STATE_CONNECTED)
return IO_CLOSED;
*sent = 0;
for ( ; ; ) {
ERR_clear_error();
err = SSL_write(ssl->ssl, data, (int)count);
ssl->error = SSL_get_error(ssl->ssl, err);
switch (ssl->error) {
case SSL_ERROR_NONE:
*sent = err;
return IO_DONE;
case SSL_ERROR_WANT_READ:
err = socket_waitfd(&ssl->sock, WAITFD_R, tm);
if (err == IO_TIMEOUT) return LSEC_IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_WANT_WRITE:
err = socket_waitfd(&ssl->sock, WAITFD_W, tm);
if (err == IO_TIMEOUT) return LSEC_IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error()) {
ssl->error = SSL_ERROR_SSL;
return LSEC_IO_SSL;
}
if (err == 0)
return IO_CLOSED;
return lsec_socket_error();
default:
return LSEC_IO_SSL;
}
}
return IO_UNKNOWN;
}
/**
* Receive data
*/
static int ssl_recv(void *ctx, char *data, size_t count, size_t *got,
p_timeout tm)
{
int err;
p_ssl ssl = (p_ssl)ctx;
*got = 0;
if (ssl->state != LSEC_STATE_CONNECTED)
return IO_CLOSED;
for ( ; ; ) {
ERR_clear_error();
err = SSL_read(ssl->ssl, data, (int)count);
ssl->error = SSL_get_error(ssl->ssl, err);
switch (ssl->error) {
case SSL_ERROR_NONE:
*got = err;
return IO_DONE;
case SSL_ERROR_ZERO_RETURN:
return IO_CLOSED;
case SSL_ERROR_WANT_READ:
err = socket_waitfd(&ssl->sock, WAITFD_R, tm);
if (err == IO_TIMEOUT) return LSEC_IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_WANT_WRITE:
err = socket_waitfd(&ssl->sock, WAITFD_W, tm);
if (err == IO_TIMEOUT) return LSEC_IO_SSL;
if (err != IO_DONE) return err;
break;
case SSL_ERROR_SYSCALL:
if (ERR_peek_error()) {
ssl->error = SSL_ERROR_SSL;
return LSEC_IO_SSL;
}
if (err == 0)
return IO_CLOSED;
return lsec_socket_error();
default:
return LSEC_IO_SSL;
}
}
return IO_UNKNOWN;
}
static SSL_CTX* luaossl_testcontext(lua_State *L, int arg) {
SSL_CTX **ctx = luaL_testudata(L, arg, "SSL_CTX*");
if (ctx)
return *ctx;
return NULL;
}
static SSL* luaossl_testssl(lua_State *L, int arg) {
SSL **ssl = luaL_testudata(L, arg, "SSL*");
if (ssl)
return *ssl;
return NULL;
}
/**
* Create a new TLS/SSL object and mark it as new.
*/
static int meth_create(lua_State *L)
{
p_ssl ssl;
int mode;
SSL_CTX *ctx;
lua_settop(L, 1);
ssl = (p_ssl)lua_newuserdata(L, sizeof(t_ssl));
if (!ssl) {
lua_pushnil(L);
lua_pushstring(L, "error creating SSL object");
return 2;
}
if ((ctx = lsec_testcontext(L, 1))) {
mode = lsec_getmode(L, 1);
if (mode == LSEC_MODE_INVALID) {
lua_pushnil(L);
lua_pushstring(L, "invalid mode");
return 2;
}
ssl->ssl = SSL_new(ctx);
if (!ssl->ssl) {
lua_pushnil(L);
lua_pushfstring(L, "error creating SSL object (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
} else if ((ctx = luaossl_testcontext(L, 1))) {
ssl->ssl = SSL_new(ctx);
if (!ssl->ssl) {
lua_pushnil(L);
lua_pushfstring(L, "error creating SSL object (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
mode = SSL_is_server(ssl->ssl) ? LSEC_MODE_SERVER : LSEC_MODE_CLIENT;
} else if ((ssl->ssl = luaossl_testssl(L, 1))) {
SSL_up_ref(ssl->ssl);
mode = SSL_is_server(ssl->ssl) ? LSEC_MODE_SERVER : LSEC_MODE_CLIENT;
} else {
return luaL_argerror(L, 1, "invalid context");
}
ssl->state = LSEC_STATE_NEW;
SSL_set_fd(ssl->ssl, (int)SOCKET_INVALID);
SSL_set_mode(ssl->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE |
SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_set_mode(ssl->ssl, SSL_MODE_RELEASE_BUFFERS);
if (mode == LSEC_MODE_SERVER)
SSL_set_accept_state(ssl->ssl);
else
SSL_set_connect_state(ssl->ssl);
io_init(&ssl->io, (p_send)ssl_send, (p_recv)ssl_recv,
(p_error) ssl_ioerror, ssl);
timeout_init(&ssl->tm, -1, -1);
buffer_init(&ssl->buf, &ssl->io, &ssl->tm);
luaL_getmetatable(L, "SSL:Connection");
lua_setmetatable(L, -2);
return 1;
}
/**
* Buffer send function
*/
static int meth_send(lua_State *L) {
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
return buffer_meth_send(L, &ssl->buf);
}
/**
* Buffer receive function
*/
static int meth_receive(lua_State *L) {
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
return buffer_meth_receive(L, &ssl->buf);
}
/**
* Get the buffer's statistics.
*/
static int meth_getstats(lua_State *L) {
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
return buffer_meth_getstats(L, &ssl->buf);
}
/**
* Set the buffer's statistics.
*/
static int meth_setstats(lua_State *L) {
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
return buffer_meth_setstats(L, &ssl->buf);
}
/**
* Select support methods
*/
static int meth_getfd(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
lua_pushnumber(L, ssl->sock);
return 1;
}
/**
* Set the TLS/SSL file descriptor.
* Call it *before* the handshake.
*/
static int meth_setfd(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_NEW)
luaL_argerror(L, 1, "invalid SSL object state");
ssl->sock = (t_socket)luaL_checkinteger(L, 2);
socket_setnonblocking(&ssl->sock);
SSL_set_fd(ssl->ssl, (int)ssl->sock);
return 0;
}
/**
* Lua handshake function.
*/
static int meth_handshake(lua_State *L)
{
int err;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
p_context ctx = (p_context)SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl->ssl));
ctx->L = L;
err = handshake(ssl);
if (ctx->dh_param) {
DH_free(ctx->dh_param);
ctx->dh_param = NULL;
}
if (ctx->alpn) {
free(ctx->alpn);
ctx->alpn = NULL;
}
if (err == IO_DONE) {
lua_pushboolean(L, 1);
return 1;
}
lua_pushboolean(L, 0);
lua_pushstring(L, ssl_ioerror((void*)ssl, err));
return 2;
}
/**
* Close the connection.
*/
static int meth_close(lua_State *L)
{
meth_destroy(L);
return 0;
}
/**
* Set timeout.
*/
static int meth_settimeout(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
return timeout_meth_settimeout(L, &ssl->tm);
}
/**
* Check if there is data in the buffer.
*/
static int meth_dirty(lua_State *L)
{
int res = 0;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_CLOSED)
res = !buffer_isempty(&ssl->buf) || SSL_pending(ssl->ssl);
lua_pushboolean(L, res);
return 1;
}
/**
* Return the state information about the SSL object.
*/
static int meth_want(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
int code = (ssl->state == LSEC_STATE_CLOSED)
? SSL_NOTHING
: SSL_want(ssl->ssl);
switch(code) {
case SSL_NOTHING: lua_pushstring(L, "nothing"); break;
case SSL_READING: lua_pushstring(L, "read"); break;
case SSL_WRITING: lua_pushstring(L, "write"); break;
case SSL_X509_LOOKUP: lua_pushstring(L, "x509lookup"); break;
}
return 1;
}
/**
* Return the compression method used.
*/
static int meth_compression(lua_State *L)
{
#ifdef OPENSSL_NO_COMP
const void *comp;
#else
const COMP_METHOD *comp;
#endif
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_CONNECTED) {
lua_pushnil(L);
lua_pushstring(L, "closed");
return 2;
}
comp = SSL_get_current_compression(ssl->ssl);
if (comp)
lua_pushstring(L, SSL_COMP_get_name(comp));
else
lua_pushnil(L);
return 1;
}
/**
* Return the nth certificate of the peer's chain.
*/
static int meth_getpeercertificate(lua_State *L)
{
int n;
X509 *cert;
STACK_OF(X509) *certs;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_CONNECTED) {
lua_pushnil(L);
lua_pushstring(L, "closed");
return 2;
}
/* Default to the first cert */
n = (int)luaL_optinteger(L, 2, 1);
/* This function is 1-based, but OpenSSL is 0-based */
--n;
if (n < 0) {
lua_pushnil(L);
lua_pushliteral(L, "invalid certificate index");
return 2;
}
if (n == 0) {
cert = SSL_get_peer_certificate(ssl->ssl);
if (cert)
lsec_pushx509(L, cert);
else
lua_pushnil(L);
return 1;
}
/* In a server-context, the stack doesn't contain the peer cert,
* so adjust accordingly.
*/
if (SSL_is_server(ssl->ssl))
--n;
certs = SSL_get_peer_cert_chain(ssl->ssl);
if (n >= sk_X509_num(certs)) {
lua_pushnil(L);
return 1;
}
cert = sk_X509_value(certs, n);
/* Increment the reference counting of the object. */
/* See SSL_get_peer_certificate() source code. */
X509_up_ref(cert);
lsec_pushx509(L, cert);
return 1;
}
/**
* Return the chain of certificate of the peer.
*/
static int meth_getpeerchain(lua_State *L)
{
int i;
int idx = 1;
int n_certs;
X509 *cert;
STACK_OF(X509) *certs;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_CONNECTED) {
lua_pushnil(L);
lua_pushstring(L, "closed");
return 2;
}
lua_newtable(L);
if (SSL_is_server(ssl->ssl)) {
lsec_pushx509(L, SSL_get_peer_certificate(ssl->ssl));
lua_rawseti(L, -2, idx++);
}
certs = SSL_get_peer_cert_chain(ssl->ssl);
n_certs = sk_X509_num(certs);
for (i = 0; i < n_certs; i++) {
cert = sk_X509_value(certs, i);
/* Increment the reference counting of the object. */
/* See SSL_get_peer_certificate() source code. */
X509_up_ref(cert);
lsec_pushx509(L, cert);
lua_rawseti(L, -2, idx++);
}
return 1;
}
/**
* Copy the table src to the table dst.
*/
static void copy_error_table(lua_State *L, int src, int dst)
{
lua_pushnil(L);
while (lua_next(L, src) != 0) {
if (lua_istable(L, -1)) {
/* Replace the table with its copy */
lua_newtable(L);
copy_error_table(L, dst+2, dst+3);
lua_remove(L, dst+2);
}
lua_pushvalue(L, -2);
lua_pushvalue(L, -2);
lua_rawset(L, dst);
/* Remove the value and leave the key */
lua_pop(L, 1);
}
}
/**
* Return the verification state of the peer chain.
*/
static int meth_getpeerverification(lua_State *L)
{
long err;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_CONNECTED) {
lua_pushboolean(L, 0);
lua_pushstring(L, "closed");
return 2;
}
err = SSL_get_verify_result(ssl->ssl);
if (err == X509_V_OK) {
lua_pushboolean(L, 1);
return 1;
}
luaL_getmetatable(L, "SSL:Verify:Registry");
lua_pushlightuserdata(L, (void*)ssl->ssl);
lua_gettable(L, -2);
if (lua_isnil(L, -1))
lua_pushstring(L, X509_verify_cert_error_string(err));
else {
/* Copy the table of errors to avoid modifications */
lua_newtable(L);
copy_error_table(L, lua_gettop(L)-1, lua_gettop(L));
}
lua_pushboolean(L, 0);
lua_pushvalue(L, -2);
return 2;
}
/**
* Get the latest "Finished" message sent out.
*/
static int meth_getfinished(lua_State *L)
{
size_t len = 0;
char *buffer = NULL;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_CONNECTED) {
lua_pushnil(L);
lua_pushstring(L, "closed");
return 2;
}
if ((len = SSL_get_finished(ssl->ssl, NULL, 0)) == 0)
return 0;
buffer = (char*)malloc(len);
if (!buffer) {
lua_pushnil(L);
lua_pushstring(L, "out of memory");
return 2;
}
SSL_get_finished(ssl->ssl, buffer, len);
lua_pushlstring(L, buffer, len);
free(buffer);
return 1;
}
/**
* Gets the latest "Finished" message received.
*/
static int meth_getpeerfinished(lua_State *L)
{
size_t len = 0;
char *buffer = NULL;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
if (ssl->state != LSEC_STATE_CONNECTED) {
lua_pushnil(L);
lua_pushstring(L, "closed");
return 0;
}
if ((len = SSL_get_peer_finished(ssl->ssl, NULL, 0)) == 0)
return 0;
buffer = (char*)malloc(len);
if (!buffer) {
lua_pushnil(L);
lua_pushstring(L, "out of memory");
return 2;
}
SSL_get_peer_finished(ssl->ssl, buffer, len);
lua_pushlstring(L, buffer, len);
free(buffer);
return 1;
}
/**
* Object information -- tostring metamethod
*/
static int meth_tostring(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
lua_pushfstring(L, "SSL connection: %p%s", ssl,
ssl->state == LSEC_STATE_CLOSED ? " (closed)" : "");
return 1;
}
/**
* Add a method in the SSL metatable.
*/
static int meth_setmethod(lua_State *L)
{
luaL_getmetatable(L, "SSL:Connection");
lua_pushstring(L, "__index");
lua_gettable(L, -2);
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_settable(L, -3);
return 0;
}
/**
* Return information about the connection.
*/
static int meth_info(lua_State *L)
{
int bits = 0;
int algbits = 0;
char buf[256] = {0};
const SSL_CIPHER *cipher;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
cipher = SSL_get_current_cipher(ssl->ssl);
if (!cipher)
return 0;
SSL_CIPHER_description(cipher, buf, sizeof(buf));
bits = SSL_CIPHER_get_bits(cipher, &algbits);
lua_pushstring(L, buf);
lua_pushnumber(L, bits);
lua_pushnumber(L, algbits);
lua_pushstring(L, SSL_get_version(ssl->ssl));
return 4;
}
static int sni_cb(SSL *ssl, int *ad, void *arg)
{
int strict;
SSL_CTX *newctx = NULL;
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl);
lua_State *L = ((p_context)SSL_CTX_get_app_data(ctx))->L;
const char *name = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
/* No name, use default context */
if (!name)
return SSL_TLSEXT_ERR_NOACK;
/* Retrieve struct from registry */
luaL_getmetatable(L, "SSL:SNI:Registry");
lua_pushlightuserdata(L, (void*)ssl);
lua_gettable(L, -2);
/* Strict search? */
lua_pushstring(L, "strict");
lua_gettable(L, -2);
strict = lua_toboolean(L, -1);
lua_pop(L, 1);
/* Search for the name in the map */
lua_pushstring(L, "map");
lua_gettable(L, -2);
lua_pushstring(L, name);
lua_gettable(L, -2);
if (lua_isuserdata(L, -1))
newctx = lsec_checkcontext(L, -1);
lua_pop(L, 4);
/* Found, use this context */
if (newctx) {
p_context pctx = (p_context)SSL_CTX_get_app_data(newctx);
pctx->L = L;
SSL_set_SSL_CTX(ssl, newctx);
return SSL_TLSEXT_ERR_OK;
}
/* Not found, but use initial context */
if (!strict)
return SSL_TLSEXT_ERR_OK;
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
static int meth_sni(lua_State *L)
{
int strict;
SSL_CTX *aux;
const char *name;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl->ssl);
p_context pctx = (p_context)SSL_CTX_get_app_data(ctx);
if (pctx->mode == LSEC_MODE_CLIENT) {
name = luaL_checkstring(L, 2);
SSL_set_tlsext_host_name(ssl->ssl, name);
return 0;
} else if (pctx->mode == LSEC_MODE_SERVER) {
luaL_checktype(L, 2, LUA_TTABLE);
strict = lua_toboolean(L, 3);
/* Check if the table contains only (string -> context) */
lua_pushnil(L);
while (lua_next(L, 2)) {
luaL_checkstring(L, -2);
aux = lsec_checkcontext(L, -1);
/* Set callback in every context */
SSL_CTX_set_tlsext_servername_callback(aux, sni_cb);
/* leave the next key on the stack */
lua_pop(L, 1);
}
/* Save table in the register */
luaL_getmetatable(L, "SSL:SNI:Registry");
lua_pushlightuserdata(L, (void*)ssl->ssl);
lua_newtable(L);
lua_pushstring(L, "map");
lua_pushvalue(L, 2);
lua_settable(L, -3);
lua_pushstring(L, "strict");
lua_pushboolean(L, strict);
lua_settable(L, -3);
lua_settable(L, -3);
/* Set callback in the default context */
SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb);
}
return 0;
}
static int meth_getsniname(lua_State *L)
{
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
const char *name = SSL_get_servername(ssl->ssl, TLSEXT_NAMETYPE_host_name);
if (name)
lua_pushstring(L, name);
else
lua_pushnil(L);
return 1;
}
static int meth_getalpn(lua_State *L)
{
unsigned len;
const unsigned char *data;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
SSL_get0_alpn_selected(ssl->ssl, &data, &len);
if (data == NULL && len == 0)
lua_pushnil(L);
else
lua_pushlstring(L, (const char*)data, len);
return 1;
}
static int meth_copyright(lua_State *L)
{
lua_pushstring(L, "LuaSec 1.0.2 - Copyright (C) 2006-2021 Bruno Silvestre, UFG"
#if defined(WITH_LUASOCKET)
"\nLuaSocket 3.0-RC1 - Copyright (C) 2004-2013 Diego Nehab"
#endif
);
return 1;
}
#if defined(LSEC_ENABLE_DANE)
static int meth_dane(lua_State *L)
{
int ret;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
ret = SSL_dane_enable(ssl->ssl, luaL_checkstring(L, 2));
lua_pushboolean(L, (ret > 0));
return 1;
}
static int meth_tlsa(lua_State *L)
{
int ret;
size_t len;
p_ssl ssl = (p_ssl)luaL_checkudata(L, 1, "SSL:Connection");
uint8_t usage = (uint8_t)luaL_checkinteger(L, 2);
uint8_t selector = (uint8_t)luaL_checkinteger(L, 3);
uint8_t mtype = (uint8_t)luaL_checkinteger(L, 4);
unsigned char *data = (unsigned char*)luaL_checklstring(L, 5, &len);
ERR_clear_error();
ret = SSL_dane_tlsa_add(ssl->ssl, usage, selector, mtype, data, len);
lua_pushboolean(L, (ret > 0));
return 1;
}
#endif
/*---------------------------------------------------------------------------*/
/**
* SSL methods
*/
static luaL_Reg methods[] = {
{"close", meth_close},
{"getalpn", meth_getalpn},
{"getfd", meth_getfd},
{"getfinished", meth_getfinished},
{"getpeercertificate", meth_getpeercertificate},
{"getpeerchain", meth_getpeerchain},
{"getpeerverification", meth_getpeerverification},
{"getpeerfinished", meth_getpeerfinished},
{"getsniname", meth_getsniname},
{"getstats", meth_getstats},
{"setstats", meth_setstats},
{"dirty", meth_dirty},
{"dohandshake", meth_handshake},
{"receive", meth_receive},
{"send", meth_send},
{"settimeout", meth_settimeout},
{"sni", meth_sni},
{"want", meth_want},
#if defined(LSEC_ENABLE_DANE)
{"setdane", meth_dane},
{"settlsa", meth_tlsa},
#endif
{NULL, NULL}
};
/**
* SSL metamethods.
*/
static luaL_Reg meta[] = {
{"__close", meth_destroy},
{"__gc", meth_destroy},
{"__tostring", meth_tostring},
{NULL, NULL}
};
/**
* SSL functions.
*/
static luaL_Reg funcs[] = {
{"compression", meth_compression},
{"create", meth_create},
{"info", meth_info},
{"setfd", meth_setfd},
{"setmethod", meth_setmethod},
{"copyright", meth_copyright},
{NULL, NULL}
};
/**
* Initialize modules.
*/
LSEC_API int luaopen_ssl_core(lua_State *L)
{
#ifndef LSEC_API_OPENSSL_1_1_0
/* Initialize SSL */
if (!SSL_library_init()) {
lua_pushstring(L, "unable to initialize SSL library");
lua_error(L);
}
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
#endif
#if defined(WITH_LUASOCKET)
/* Initialize internal library */
socket_open();
#endif
luaL_newmetatable(L, "SSL:SNI:Registry");
/* Register the functions and tables */
luaL_newmetatable(L, "SSL:Connection");
setfuncs(L, meta);
luaL_newlib(L, methods);
lua_setfield(L, -2, "__index");
luaL_newlib(L, funcs);
lua_pushvalue(L, -1);
lua_setglobal(L, "ssl");
lua_pushstring(L, "SOCKET_INVALID");
lua_pushinteger(L, SOCKET_INVALID);
lua_rawset(L, -3);
return 1;
}
//------------------------------------------------------------------------------
#if defined(_MSC_VER)
/* Empty implementation to allow building with LuaRocks and MS compilers */
LSEC_API int luaopen_ssl(lua_State *L) {
lua_pushstring(L, "you should not call this function");
lua_error(L);
return 0;
}
#endif

41
src/luasec/ssl.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef LSEC_SSL_H
#define LSEC_SSL_H
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2006-2021 Bruno Silvestre
*
*--------------------------------------------------------------------------*/
#include <openssl/ssl.h>
#include "../lua.h"
#include "luasocket/io.h"
#include "luasocket/buffer.h"
#include "luasocket/timeout.h"
#include "luasocket/socket.h"
#include "compat.h"
#include "context.h"
#define LSEC_STATE_NEW 1
#define LSEC_STATE_CONNECTED 2
#define LSEC_STATE_CLOSED 3
#define LSEC_IO_SSL -100
typedef struct t_ssl_ {
t_socket sock;
t_io io;
t_buffer buf;
t_timeout tm;
SSL *ssl;
int state;
int error;
} t_ssl;
typedef t_ssl* p_ssl;
LSEC_API int luaopen_ssl_core(lua_State *L);
#endif

277
src/luasec/ssl.lua Normal file
View File

@ -0,0 +1,277 @@
------------------------------------------------------------------------------
-- LuaSec 1.0.2
--
-- Copyright (C) 2006-2021 Bruno Silvestre
--
------------------------------------------------------------------------------
local core = ssl
-- We must prevent the contexts to be collected before the connections,
-- otherwise the C registry will be cleared.
local registry = setmetatable({}, {__mode="k"})
--
--
--
local function optexec(func, param, ctx)
if param then
if type(param) == "table" then
return func(ctx, unpack(param))
else
return func(ctx, param)
end
end
return true
end
--
-- Convert an array of strings to wire-format
--
local function array2wireformat(array)
local str = ""
for k, v in ipairs(array) do
if type(v) ~= "string" then return nil end
local len = #v
if len == 0 then
return nil, "invalid ALPN name (empty string)"
elseif len > 255 then
return nil, "invalid ALPN name (length > 255)"
end
str = str .. string.char(len) .. v
end
if str == "" then return nil, "invalid ALPN list (empty)" end
return str
end
--
-- Convert wire-string format to array
--
local function wireformat2array(str)
local i = 1
local array = {}
while i < #str do
local len = str:byte(i)
array[#array + 1] = str:sub(i + 1, i + len)
i = i + len + 1
end
return array
end
--
--
--
local function newcontext(cfg)
local succ, msg, ctx
-- Create the context
ctx, msg = context.create(cfg.protocol)
if not ctx then return nil, msg end
-- Mode
succ, msg = context.setmode(ctx, cfg.mode)
if not succ then return nil, msg end
local certificates = cfg.certificates
if not certificates then
certificates = {
{ certificate = cfg.certificate, key = cfg.key, password = cfg.password }
}
end
for _, certificate in ipairs(certificates) do
-- Load the key
if certificate.key then
if certificate.password and
type(certificate.password) ~= "function" and
type(certificate.password) ~= "string"
then
return nil, "invalid password type"
end
succ, msg = context.loadkey(ctx, certificate.key, certificate.password)
if not succ then return nil, msg end
end
-- Load the certificate(s)
if certificate.certificate then
succ, msg = context.loadcert(ctx, certificate.certificate)
if not succ then return nil, msg end
if certificate.key and context.checkkey then
succ = context.checkkey(ctx)
if not succ then return nil, "private key does not match public key" end
end
end
end
-- Load the CA certificates
if cfg.cafile or cfg.capath then
succ, msg = context.locations(ctx, cfg.cafile, cfg.capath)
if not succ then return nil, msg end
end
-- Set SSL ciphers
if cfg.ciphers then
succ, msg = context.setcipher(ctx, cfg.ciphers)
if not succ then return nil, msg end
end
-- Set SSL cipher suites
if cfg.ciphersuites then
succ, msg = context.setciphersuites(ctx, cfg.ciphersuites)
if not succ then return nil, msg end
end
-- Set the verification options
succ, msg = optexec(context.setverify, cfg.verify, ctx)
if not succ then return nil, msg end
-- Set SSL options
succ, msg = optexec(context.setoptions, cfg.options, ctx)
if not succ then return nil, msg end
-- Set the depth for certificate verification
if cfg.depth then
succ, msg = context.setdepth(ctx, cfg.depth)
if not succ then return nil, msg end
end
-- NOTE: Setting DH parameters and elliptic curves needs to come after
-- setoptions(), in case the user has specified the single_{dh,ecdh}_use
-- options.
-- Set DH parameters
if cfg.dhparam then
if type(cfg.dhparam) ~= "function" then
return nil, "invalid DH parameter type"
end
context.setdhparam(ctx, cfg.dhparam)
end
-- Set elliptic curves
if (not config.algorithms.ec) and (cfg.curve or cfg.curveslist) then
return false, "elliptic curves not supported"
end
if config.capabilities.curves_list and cfg.curveslist then
succ, msg = context.setcurveslist(ctx, cfg.curveslist)
if not succ then return nil, msg end
elseif cfg.curve then
succ, msg = context.setcurve(ctx, cfg.curve)
if not succ then return nil, msg end
end
-- Set extra verification options
if cfg.verifyext and ctx.setverifyext then
succ, msg = optexec(ctx.setverifyext, cfg.verifyext, ctx)
if not succ then return nil, msg end
end
-- ALPN
if cfg.mode == "server" and cfg.alpn then
if type(cfg.alpn) == "function" then
local alpncb = cfg.alpn
-- This callback function has to return one value only
succ, msg = context.setalpncb(ctx, function(str)
local protocols = alpncb(wireformat2array(str))
if type(protocols) == "string" then
protocols = { protocols }
elseif type(protocols) ~= "table" then
return nil
end
return (array2wireformat(protocols)) -- use "()" to drop error message
end)
if not succ then return nil, msg end
elseif type(cfg.alpn) == "table" then
local protocols = cfg.alpn
-- check if array is valid before use it
succ, msg = array2wireformat(protocols)
if not succ then return nil, msg end
-- This callback function has to return one value only
succ, msg = context.setalpncb(ctx, function()
return (array2wireformat(protocols)) -- use "()" to drop error message
end)
if not succ then return nil, msg end
else
return nil, "invalid ALPN parameter"
end
elseif cfg.mode == "client" and cfg.alpn then
local alpn
if type(cfg.alpn) == "string" then
alpn, msg = array2wireformat({ cfg.alpn })
elseif type(cfg.alpn) == "table" then
alpn, msg = array2wireformat(cfg.alpn)
else
return nil, "invalid ALPN parameter"
end
if not alpn then return nil, msg end
succ, msg = context.setalpn(ctx, alpn)
if not succ then return nil, msg end
end
if config.capabilities.dane and cfg.dane then
context.setdane(ctx)
end
return ctx
end
--
--
--
local function wrap(sock, cfg)
local ctx, msg
if type(cfg) == "table" then
ctx, msg = newcontext(cfg)
if not ctx then return nil, msg end
else
ctx = cfg
end
local s, msg = core.create(ctx)
if s then
core.setfd(s, sock:getfd())
sock:setfd(core.SOCKET_INVALID)
registry[s] = ctx
return s
end
return nil, msg
end
--
-- Extract connection information.
--
local function info(ssl, field)
local str, comp, err, protocol
comp, err = core.compression(ssl)
if err then
return comp, err
end
-- Avoid parser
if field == "compression" then
return comp
end
local info = {compression = comp}
str, info.bits, info.algbits, protocol = core.info(ssl)
if str then
info.cipher, info.protocol, info.key,
info.authentication, info.encryption, info.mac =
string.match(str,
"^(%S+)%s+(%S+)%s+Kx=(%S+)%s+Au=(%S+)%s+Enc=(%S+)%s+Mac=(%S+)")
info.export = (string.match(str, "%sexport%s*$") ~= nil)
end
if protocol then
info.protocol = protocol
end
if field then
return info[field]
end
-- Empty?
return ( (next(info)) and info )
end
--
-- Set method for SSL connections.
--
core.setmethod("info", info)
--------------------------------------------------------------------------------
-- Export module
--
local _M = {
_VERSION = "1.0.2",
_COPYRIGHT = core.copyright(),
config = config,
loadcertificate = x509.load,
newcontext = newcontext,
wrap = wrap,
}
return _M

748
src/luasec/x509.c Normal file
View File

@ -0,0 +1,748 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann
* Matthew Wild, Bruno Silvestre.
*
*--------------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#if defined(WIN32)
#include <ws2tcpip.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/bn.h>
#include "../lua.h"
#include "../lauxlib.h"
#include "x509.h"
#ifndef LSEC_API_OPENSSL_1_1_0
#define X509_get0_notBefore X509_get_notBefore
#define X509_get0_notAfter X509_get_notAfter
#define ASN1_STRING_get0_data ASN1_STRING_data
#endif
static const char* hex_tab = "0123456789abcdef";
/**
* Push the certificate on the stack.
*/
void lsec_pushx509(lua_State* L, X509 *cert)
{
p_x509 cert_obj = (p_x509)lua_newuserdata(L, sizeof(t_x509));
cert_obj->cert = cert;
cert_obj->encode = LSEC_AI5_STRING;
luaL_getmetatable(L, "SSL:Certificate");
lua_setmetatable(L, -2);
}
/**
* Return the OpenSSL certificate X509.
*/
X509* lsec_checkx509(lua_State* L, int idx)
{
return ((p_x509)luaL_checkudata(L, idx, "SSL:Certificate"))->cert;
}
/**
* Return LuaSec certificate X509 representation.
*/
p_x509 lsec_checkp_x509(lua_State* L, int idx)
{
return (p_x509)luaL_checkudata(L, idx, "SSL:Certificate");
}
/*---------------------------------------------------------------------------*/
#if defined(LUASEC_INET_NTOP)
/*
* For WinXP (SP3), set the following preprocessor macros:
* LUASEC_INET_NTOP
* WINVER=0x0501
* _WIN32_WINNT=0x0501
* NTDDI_VERSION=0x05010300
*
* For IPv6 addresses, you need to add IPv6 Protocol to your interface.
*
*/
static const char *inet_ntop(int af, const char *src, char *dst, socklen_t size)
{
int addrsize;
struct sockaddr *addr;
struct sockaddr_in addr4;
struct sockaddr_in6 addr6;
switch (af) {
case AF_INET:
memset((void*)&addr4, 0, sizeof(addr4));
addr4.sin_family = AF_INET;
memcpy((void*)&addr4.sin_addr, src, sizeof(struct in_addr));
addr = (struct sockaddr*)&addr4;
addrsize = sizeof(struct sockaddr_in);
break;
case AF_INET6:
memset((void*)&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
memcpy((void*)&addr6.sin6_addr, src, sizeof(struct in6_addr));
addr = (struct sockaddr*)&addr6;
addrsize = sizeof(struct sockaddr_in6);
break;
default:
return NULL;
}
if(getnameinfo(addr, addrsize, dst, size, NULL, 0, NI_NUMERICHOST) != 0)
return NULL;
return dst;
}
#endif
/*---------------------------------------------------------------------------*/
/**
* Convert the buffer 'in' to hexadecimal.
*/
static void to_hex(const char* in, int length, char* out)
{
int i;
for (i = 0; i < length; i++) {
out[i*2] = hex_tab[(in[i] >> 4) & 0xF];
out[i*2+1] = hex_tab[(in[i]) & 0xF];
}
}
/**
* Converts the ASN1_OBJECT into a textual representation and put it
* on the Lua stack.
*/
static void push_asn1_objname(lua_State* L, ASN1_OBJECT *object, int no_name)
{
char buffer[256];
int len = OBJ_obj2txt(buffer, sizeof(buffer), object, no_name);
len = (len < sizeof(buffer)) ? len : sizeof(buffer);
lua_pushlstring(L, buffer, len);
}
/**
* Push the ASN1 string on the stack.
*/
static void push_asn1_string(lua_State* L, ASN1_STRING *string, int encode)
{
int len;
unsigned char *data;
if (!string) {
lua_pushnil(L);
return;
}
switch (encode) {
case LSEC_AI5_STRING:
lua_pushlstring(L, (char*)ASN1_STRING_get0_data(string), ASN1_STRING_length(string));
break;
case LSEC_UTF8_STRING:
len = ASN1_STRING_to_UTF8(&data, string);
if (len >= 0) {
lua_pushlstring(L, (char*)data, len);
OPENSSL_free(data);
}
else
lua_pushnil(L);
}
}
/**
* Return a human readable time.
*/
static int push_asn1_time(lua_State *L, const ASN1_UTCTIME *tm)
{
char *tmp;
long size;
BIO *out = BIO_new(BIO_s_mem());
ASN1_TIME_print(out, tm);
size = BIO_get_mem_data(out, &tmp);
lua_pushlstring(L, tmp, size);
BIO_free(out);
return 1;
}
/**
* Return a human readable IP address.
*/
static void push_asn1_ip(lua_State *L, ASN1_STRING *string)
{
int af;
char dst[INET6_ADDRSTRLEN];
unsigned char *ip = (unsigned char*)ASN1_STRING_get0_data(string);
switch(ASN1_STRING_length(string)) {
case 4:
af = AF_INET;
break;
case 16:
af = AF_INET6;
break;
default:
lua_pushnil(L);
return;
}
if(inet_ntop(af, ip, dst, INET6_ADDRSTRLEN))
lua_pushstring(L, dst);
else
lua_pushnil(L);
}
/**
*
*/
static int push_subtable(lua_State* L, int idx)
{
lua_pushvalue(L, -1);
lua_gettable(L, idx-1);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_pushvalue(L, -2);
lua_pushvalue(L, -2);
lua_settable(L, idx-3);
lua_replace(L, -2); /* Replace key with table */
return 1;
}
lua_replace(L, -2); /* Replace key with table */
return 0;
}
/**
* Retrieve the general names from the object.
*/
static int push_x509_name(lua_State* L, X509_NAME *name, int encode)
{
int i;
int n_entries;
ASN1_OBJECT *object;
X509_NAME_ENTRY *entry;
lua_newtable(L);
n_entries = X509_NAME_entry_count(name);
for (i = 0; i < n_entries; i++) {
entry = X509_NAME_get_entry(name, i);
object = X509_NAME_ENTRY_get_object(entry);
lua_newtable(L);
push_asn1_objname(L, object, 1);
lua_setfield(L, -2, "oid");
push_asn1_objname(L, object, 0);
lua_setfield(L, -2, "name");
push_asn1_string(L, X509_NAME_ENTRY_get_data(entry), encode);
lua_setfield(L, -2, "value");
lua_rawseti(L, -2, i+1);
}
return 1;
}
/*---------------------------------------------------------------------------*/
/**
* Retrieve the Subject from the certificate.
*/
static int meth_subject(lua_State* L)
{
p_x509 px = lsec_checkp_x509(L, 1);
return push_x509_name(L, X509_get_subject_name(px->cert), px->encode);
}
/**
* Retrieve the Issuer from the certificate.
*/
static int meth_issuer(lua_State* L)
{
p_x509 px = lsec_checkp_x509(L, 1);
return push_x509_name(L, X509_get_issuer_name(px->cert), px->encode);
}
/**
* Retrieve the extensions from the certificate.
*/
int meth_extensions(lua_State* L)
{
int j;
int i = -1;
int n_general_names;
OTHERNAME *otherName;
X509_EXTENSION *extension;
GENERAL_NAME *general_name;
STACK_OF(GENERAL_NAME) *values;
p_x509 px = lsec_checkp_x509(L, 1);
X509 *peer = px->cert;
/* Return (ret) */
lua_newtable(L);
while ((i = X509_get_ext_by_NID(peer, NID_subject_alt_name, i)) != -1) {
extension = X509_get_ext(peer, i);
if (extension == NULL)
break;
values = X509V3_EXT_d2i(extension);
if (values == NULL)
break;
/* Push ret[oid] */
push_asn1_objname(L, X509_EXTENSION_get_object(extension), 1);
push_subtable(L, -2);
/* Set ret[oid].name = name */
push_asn1_objname(L, X509_EXTENSION_get_object(extension), 0);
lua_setfield(L, -2, "name");
n_general_names = sk_GENERAL_NAME_num(values);
for (j = 0; j < n_general_names; j++) {
general_name = sk_GENERAL_NAME_value(values, j);
switch (general_name->type) {
case GEN_OTHERNAME:
otherName = general_name->d.otherName;
push_asn1_objname(L, otherName->type_id, 1);
if (push_subtable(L, -2)) {
push_asn1_objname(L, otherName->type_id, 0);
lua_setfield(L, -2, "name");
}
push_asn1_string(L, otherName->value->value.asn1_string, px->encode);
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
lua_pop(L, 1);
break;
case GEN_DNS:
lua_pushstring(L, "dNSName");
push_subtable(L, -2);
push_asn1_string(L, general_name->d.dNSName, px->encode);
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
lua_pop(L, 1);
break;
case GEN_EMAIL:
lua_pushstring(L, "rfc822Name");
push_subtable(L, -2);
push_asn1_string(L, general_name->d.rfc822Name, px->encode);
lua_rawseti(L, -2, lua_rawlen(L, -2) + 1);
lua_pop(L, 1);
break;
case GEN_URI:
lua_pushstring(L, "uniformResourceIdentifier");
push_subtable(L, -2);
push_asn1_string(L, general_name->d.uniformResourceIdentifier, px->encode);
lua_rawseti(L, -2, lua_rawlen(L, -2)+1);
lua_pop(L, 1);
break;
case GEN_IPADD:
lua_pushstring(L, "iPAddress");
push_subtable(L, -2);
push_asn1_ip(L, general_name->d.iPAddress);
lua_rawseti(L, -2, lua_rawlen(L, -2)+1);
lua_pop(L, 1);
break;
case GEN_X400:
/* x400Address */
/* not supported */
break;
case GEN_DIRNAME:
/* directoryName */
/* not supported */
break;
case GEN_EDIPARTY:
/* ediPartyName */
/* not supported */
break;
case GEN_RID:
/* registeredID */
/* not supported */
break;
}
GENERAL_NAME_free(general_name);
}
sk_GENERAL_NAME_free(values);
lua_pop(L, 1); /* ret[oid] */
i++; /* Next extension */
}
return 1;
}
/**
* Convert the certificate to PEM format.
*/
static int meth_pem(lua_State* L)
{
char* data;
long bytes;
X509* cert = lsec_checkx509(L, 1);
BIO *bio = BIO_new(BIO_s_mem());
if (!PEM_write_bio_X509(bio, cert)) {
lua_pushnil(L);
return 1;
}
bytes = BIO_get_mem_data(bio, &data);
if (bytes > 0)
lua_pushlstring(L, data, bytes);
else
lua_pushnil(L);
BIO_free(bio);
return 1;
}
/**
* Extract public key in PEM format.
*/
static int meth_pubkey(lua_State* L)
{
char* data;
long bytes;
int ret = 1;
X509* cert = lsec_checkx509(L, 1);
BIO *bio = BIO_new(BIO_s_mem());
EVP_PKEY *pkey = X509_get_pubkey(cert);
if(PEM_write_bio_PUBKEY(bio, pkey)) {
bytes = BIO_get_mem_data(bio, &data);
if (bytes > 0) {
lua_pushlstring(L, data, bytes);
switch(EVP_PKEY_base_id(pkey)) {
case EVP_PKEY_RSA:
lua_pushstring(L, "RSA");
break;
case EVP_PKEY_DSA:
lua_pushstring(L, "DSA");
break;
case EVP_PKEY_DH:
lua_pushstring(L, "DH");
break;
case EVP_PKEY_EC:
lua_pushstring(L, "EC");
break;
default:
lua_pushstring(L, "Unknown");
break;
}
lua_pushinteger(L, EVP_PKEY_bits(pkey));
ret = 3;
}
else
lua_pushnil(L);
}
else
lua_pushnil(L);
/* Cleanup */
BIO_free(bio);
EVP_PKEY_free(pkey);
return ret;
}
/**
* Compute the fingerprint.
*/
static int meth_digest(lua_State* L)
{
unsigned int bytes;
const EVP_MD *digest = NULL;
unsigned char buffer[EVP_MAX_MD_SIZE];
char hex_buffer[EVP_MAX_MD_SIZE*2];
X509 *cert = lsec_checkx509(L, 1);
const char *str = luaL_optstring(L, 2, NULL);
if (!str)
digest = EVP_sha1();
else {
if (!strcmp(str, "sha1"))
digest = EVP_sha1();
else if (!strcmp(str, "sha256"))
digest = EVP_sha256();
else if (!strcmp(str, "sha512"))
digest = EVP_sha512();
}
if (!digest) {
lua_pushnil(L);
lua_pushfstring(L, "digest algorithm not supported (%s)", str);
return 2;
}
if (!X509_digest(cert, digest, buffer, &bytes)) {
lua_pushnil(L);
lua_pushfstring(L, "error processing the certificate (%s)",
ERR_reason_error_string(ERR_get_error()));
return 2;
}
to_hex((char*)buffer, bytes, hex_buffer);
lua_pushlstring(L, hex_buffer, bytes*2);
return 1;
}
/**
* Check if the certificate is valid in a given time.
*/
static int meth_valid_at(lua_State* L)
{
int nb, na;
X509* cert = lsec_checkx509(L, 1);
time_t time = luaL_checkinteger(L, 2);
nb = X509_cmp_time(X509_get0_notBefore(cert), &time);
time -= 1;
na = X509_cmp_time(X509_get0_notAfter(cert), &time);
lua_pushboolean(L, nb == -1 && na == 1);
return 1;
}
/**
* Return the serial number.
*/
static int meth_serial(lua_State *L)
{
char *tmp;
BIGNUM *bn;
ASN1_INTEGER *serial;
X509* cert = lsec_checkx509(L, 1);
serial = X509_get_serialNumber(cert);
bn = ASN1_INTEGER_to_BN(serial, NULL);
tmp = BN_bn2hex(bn);
lua_pushstring(L, tmp);
BN_free(bn);
OPENSSL_free(tmp);
return 1;
}
/**
* Return not before date.
*/
static int meth_notbefore(lua_State *L)
{
X509* cert = lsec_checkx509(L, 1);
return push_asn1_time(L, X509_get0_notBefore(cert));
}
/**
* Return not after date.
*/
static int meth_notafter(lua_State *L)
{
X509* cert = lsec_checkx509(L, 1);
return push_asn1_time(L, X509_get0_notAfter(cert));
}
/**
* Check if this certificate issued some other certificate
*/
static int meth_issued(lua_State* L)
{
int ret, i, len;
X509_STORE_CTX* ctx = NULL;
X509_STORE* root = NULL;
STACK_OF(X509)* chain = NULL;
X509* issuer = lsec_checkx509(L, 1);
X509* subject = lsec_checkx509(L, 2);
X509* cert = NULL;
len = lua_gettop(L);
/* Check that all arguments are certificates */
for (i = 3; i <= len; i++) {
lsec_checkx509(L, i);
}
/* Before allocating things that require freeing afterwards */
chain = sk_X509_new_null();
ctx = X509_STORE_CTX_new();
root = X509_STORE_new();
if (ctx == NULL || root == NULL) {
lua_pushnil(L);
lua_pushstring(L, "X509_STORE_new() or X509_STORE_CTX_new() error");
ret = 2;
goto cleanup;
}
ret = X509_STORE_add_cert(root, issuer);
if(!ret) {
lua_pushnil(L);
lua_pushstring(L, "X509_STORE_add_cert() error");
ret = 2;
goto cleanup;
}
for (i = 3; i <= len && lua_isuserdata(L, i); i++) {
cert = lsec_checkx509(L, i);
sk_X509_push(chain, cert);
}
ret = X509_STORE_CTX_init(ctx, root, subject, chain);
if(!ret) {
lua_pushnil(L);
lua_pushstring(L, "X509_STORE_CTX_init() error");
ret = 2;
goto cleanup;
}
/* Actual verification */
if (X509_verify_cert(ctx) <= 0) {
ret = X509_STORE_CTX_get_error(ctx);
lua_pushnil(L);
lua_pushstring(L, X509_verify_cert_error_string(ret));
ret = 2;
} else {
lua_pushboolean(L, 1);
ret = 1;
}
cleanup:
if (ctx != NULL) {
X509_STORE_CTX_free(ctx);
}
if (chain != NULL) {
X509_STORE_free(root);
}
sk_X509_free(chain);
return ret;
}
/**
* Collect X509 objects.
*/
static int meth_destroy(lua_State* L)
{
p_x509 px = lsec_checkp_x509(L, 1);
if (px->cert) {
X509_free(px->cert);
px->cert = NULL;
}
return 0;
}
static int meth_tostring(lua_State *L)
{
X509* cert = lsec_checkx509(L, 1);
lua_pushfstring(L, "X509 certificate: %p", cert);
return 1;
}
/**
* Set the encode for ASN.1 string.
*/
static int meth_set_encode(lua_State* L)
{
int succ = 0;
p_x509 px = lsec_checkp_x509(L, 1);
const char *enc = luaL_checkstring(L, 2);
if (strncmp(enc, "ai5", 3) == 0) {
succ = 1;
px->encode = LSEC_AI5_STRING;
} else if (strncmp(enc, "utf8", 4) == 0) {
succ = 1;
px->encode = LSEC_UTF8_STRING;
}
lua_pushboolean(L, succ);
return 1;
}
/**
* Get signature name.
*/
static int meth_get_signature_name(lua_State* L)
{
p_x509 px = lsec_checkp_x509(L, 1);
int nid = X509_get_signature_nid(px->cert);
const char *name = OBJ_nid2sn(nid);
if (!name)
lua_pushnil(L);
else
lua_pushstring(L, name);
return 1;
}
/*---------------------------------------------------------------------------*/
static int load_cert(lua_State* L)
{
X509 *cert;
size_t bytes;
const char* data;
BIO *bio = BIO_new(BIO_s_mem());
data = luaL_checklstring(L, 1, &bytes);
BIO_write(bio, data, bytes);
cert = PEM_read_bio_X509(bio, NULL, NULL, NULL);
if (cert)
lsec_pushx509(L, cert);
else
lua_pushnil(L);
BIO_free(bio);
return 1;
}
/*---------------------------------------------------------------------------*/
/**
* Certificate methods.
*/
static luaL_Reg methods[] = {
{"digest", meth_digest},
{"setencode", meth_set_encode},
{"extensions", meth_extensions},
{"getsignaturename", meth_get_signature_name},
{"issuer", meth_issuer},
{"notbefore", meth_notbefore},
{"notafter", meth_notafter},
{"issued", meth_issued},
{"pem", meth_pem},
{"pubkey", meth_pubkey},
{"serial", meth_serial},
{"subject", meth_subject},
{"validat", meth_valid_at},
{NULL, NULL}
};
/**
* X509 metamethods.
*/
static luaL_Reg meta[] = {
{"__close", meth_destroy},
{"__gc", meth_destroy},
{"__tostring", meth_tostring},
{NULL, NULL}
};
/**
* X509 functions.
*/
static luaL_Reg funcs[] = {
{"load", load_cert},
{NULL, NULL}
};
/*--------------------------------------------------------------------------*/
LSEC_API int luaopen_ssl_x509(lua_State *L)
{
/* Register the functions and tables */
luaL_newmetatable(L, "SSL:Certificate");
setfuncs(L, meta);
luaL_newlib(L, methods);
lua_setfield(L, -2, "__index");
luaL_newlib(L, funcs);
lua_pushvalue(L, -1);
lua_setglobal(L, "x509");
return 1;
}

31
src/luasec/x509.h Normal file
View File

@ -0,0 +1,31 @@
/*--------------------------------------------------------------------------
* LuaSec 1.0.2
*
* Copyright (C) 2014-2021 Kim Alvefur, Paul Aurich, Tobias Markmann
* Matthew Wild, Bruno Silvestre.
*
*--------------------------------------------------------------------------*/
#ifndef LSEC_X509_H
#define LSEC_X509_H
#include <openssl/x509v3.h>
#include "../lua.h"
#include "compat.h"
/* We do not support UniversalString nor BMPString as ASN.1 String types */
enum { LSEC_AI5_STRING, LSEC_UTF8_STRING };
typedef struct t_x509_ {
X509 *cert;
int encode;
} t_x509;
typedef t_x509* p_x509;
void lsec_pushx509(lua_State* L, X509* cert);
X509* lsec_checkx509(lua_State* L, int idx);
LSEC_API int luaopen_ssl_x509(lua_State *L);
#endif

View File

@ -27,11 +27,7 @@ local SCHEMES = {
, https = {
port = 443
, create = function(t)
local https = assert(
require("ssl.https"), 'LuaSocket: LuaSec not found')
local tcp = assert(
https.tcp, 'LuaSocket: Function tcp() not available from LuaSec')
return tcp(t) end }}
return https.tcp(t) end }}
-- default scheme and port for document retrieval
local SCHEME = 'http'