diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000000000000000000000000000000000..d7c137b6c9358c450e59618df2576b2bdc5b331d --- /dev/null +++ b/.clang-format @@ -0,0 +1,110 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<boost/.+\.hpp>' + Priority: 3 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 4 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 8 +UseTab: Never +... + diff --git a/config b/config new file mode 100644 index 0000000000000000000000000000000000000000..787fcf5f868af8ac771246baf4bd9032e46c4ed2 --- /dev/null +++ b/config @@ -0,0 +1,7 @@ +ngx_module_type=HTTP +ngx_addon_name=voms +ngx_module_name=ngx_http_voms_module +ngx_module_srcs="$ngx_addon_dir/ngx_http_voms_module.cpp" +ngx_module_libs="-lvomsapi -lstdc++" + +. auto/module diff --git a/ngx_http_voms_module.cpp b/ngx_http_voms_module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2bf2ef3e237d06e5d2ff3d1c04eda3637542e8c0 --- /dev/null +++ b/ngx_http_voms_module.cpp @@ -0,0 +1,335 @@ +extern "C" { +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> +} +#include <voms/voms_api.h> +#include <cassert> +#include <chrono> +#include <iostream> +#include <memory> +#include <numeric> +#include <string> +#include <boost/algorithm/string/join.hpp> +#include <boost/optional.hpp> + +using BioPtr = std::unique_ptr<BIO, decltype(&BIO_free)>; +using X509Ptr = std::unique_ptr<X509, decltype(&X509_free)>; +using VomsAc = voms; +using MaybeVomsAc = boost::optional<VomsAc>; + +static ngx_int_t add_variables(ngx_conf_t* cf); + +static ngx_http_module_t ctx = { + add_variables, // preconfiguration + NULL, // postconfiguration + NULL, // create main configuration + NULL, // init main configuration + NULL, // create server configuration + NULL, // merge server configuration + NULL, // create location configuration + NULL // merge location configuration +}; + +ngx_module_t ngx_http_voms_module = { + NGX_MODULE_V1, + &ctx, // module context + NULL, // module directives + NGX_HTTP_MODULE, // module type + NULL, // init master + NULL, // init module + NULL, // init process + NULL, // init thread + NULL, // exit thread + NULL, // exit process + NULL, // exit master + NGX_MODULE_V1_PADDING // +}; + +static ngx_int_t get_voms_fqans( // + ngx_http_request_t* r, + ngx_http_variable_value_t* v, + uintptr_t data); +static ngx_int_t get_voms_user( // + ngx_http_request_t* r, + ngx_http_variable_value_t* v, + uintptr_t data); + +static ngx_http_variable_t variables[] = { + { + ngx_string("voms_fqans"), + NULL, + get_voms_fqans, + 0, + NGX_HTTP_VAR_NOCACHEABLE, + 0 // + }, + { + ngx_string("voms_user"), + NULL, + get_voms_user, + 0, + NGX_HTTP_VAR_NOCACHEABLE, + 0 // + }, + ngx_http_null_variable // +}; + +static ngx_int_t add_variables(ngx_conf_t* cf) +{ + for (ngx_http_variable_t* v = variables; v->name.len; ++v) { + ngx_http_variable_t* var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + +template <class S> +static std::string to_string(S const* s) +{ + return std::string(reinterpret_cast<char const*>(s->data), + reinterpret_cast<char const*>(s->data) + s->len); +} + +boost::optional<std::string> to_pem(X509& x509) +{ + BioPtr bio{BIO_new(BIO_s_mem()), BIO_free}; + if (PEM_write_bio_X509(bio.get(), &x509) == 0) { + return boost::none; + } else { + char* data = nullptr; + auto len = BIO_get_mem_data(bio.get(), &data); + if (len > 0) { + return std::string(data, data + len); + } else { + return boost::none; + } + } +} + +// return the first AC, if present +static MaybeVomsAc retrieve_voms_ac_from_proxy(ngx_http_request_t* r) +{ + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__); + + if (!r->http_connection->ssl) { + return boost::none; + } + + auto client_cert = X509Ptr{ + SSL_get_peer_certificate(r->connection->ssl->connection), X509_free}; + if (!client_cert) { + ngx_log_error(NGX_LOG_ERR, + r->connection->log, + 0, + "SSL_get_peer_certificate() failed"); + return boost::none; + } + + auto client_chain = SSL_get_peer_cert_chain(r->connection->ssl->connection); + if (!client_chain) { + ngx_log_error( + NGX_LOG_ERR, r->connection->log, 0, "SSL_get_peer_cert_chain() failed"); + return boost::none; + } + + vomsdata vd; + auto ok = vd.Retrieve(client_cert.get(), client_chain, RECURSE_CHAIN); + if (!ok) { + // vd.error is not interpreted correctly by the logger, which probably uses + // errno + ngx_log_error(NGX_LOG_ERR, + r->connection->log, + vd.error, + "%s", + vd.ErrorMessage().c_str()); + return boost::none; + } + + if (vd.data.empty()) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "no ACs in proxy"); + return boost::none; + } + + return vd.data.front(); +} + +static void clean_voms_ac(void* data) +{ + auto r = static_cast<ngx_http_request_t*>(data); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__); + + auto p = static_cast<MaybeVomsAc*>( + ngx_http_get_module_ctx(r, ngx_http_voms_module)); + delete p; +} + +static void cache_voms_ac(ngx_http_request_t* r, MaybeVomsAc* ac) +{ + ngx_http_set_ctx(r, ac, ngx_http_voms_module); + auto cln = ngx_http_cleanup_add(r, 0); + if (cln) { + cln->handler = clean_voms_ac; + cln->data = r; + } else { + ngx_log_error( + NGX_LOG_ERR, r->connection->log, 0, "ngx_http_cleanup_add() failed"); + } +} + +static MaybeVomsAc* get_voms_ac_from_cache(ngx_http_request_t* r) +{ + return static_cast<MaybeVomsAc*>( + ngx_http_get_module_ctx(r, ngx_http_voms_module)); +} + +static MaybeVomsAc const& get_voms_ac(ngx_http_request_t* r) +{ + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__); + + MaybeVomsAc* acp = get_voms_ac_from_cache(r); + + if (!acp) { + acp = new MaybeVomsAc(retrieve_voms_ac_from_proxy(r)); + cache_voms_ac(r, acp); + } + + return *acp; +} + +static ngx_int_t get_voms_fqans(ngx_http_request_t* r, + ngx_http_variable_value_t* v, + uintptr_t) +{ + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__); + + v->not_found = 1; + v->valid = 0; + + auto& ac = get_voms_ac(r); + + if (!ac) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "get_voms_ac() failed"); + return NGX_OK; + } + + auto fqans = boost::algorithm::join(ac->fqans, ","); + + auto data = static_cast<u_char*>(ngx_pnalloc(r->pool, fqans.size())); + if (!data) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pnalloc() failed"); + return NGX_OK; + } + ngx_memcpy(data, fqans.c_str(), fqans.size()); + + v->data = data; + v->len = fqans.size(); + v->valid = 1; + v->not_found = 0; + v->no_cacheable = 0; + return NGX_OK; +} + +static ngx_int_t get_voms_user(ngx_http_request_t* r, + ngx_http_variable_value_t* v, + uintptr_t) +{ + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "%s", __FUNCTION__); + + v->not_found = 1; + v->valid = 0; + + auto& ac = get_voms_ac(r); + + if (!ac) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "get_voms_ac() failed"); + return NGX_OK; + } + + auto const& user = ac->user; + + auto data = static_cast<u_char*>(ngx_pnalloc(r->pool, user.size())); + if (!data) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pnalloc() failed"); + return NGX_OK; + } + ngx_memcpy(data, user.c_str(), user.size()); + + v->data = data; + v->len = user.size(); + v->valid = 1; + v->not_found = 0; + v->no_cacheable = 0; + return NGX_OK; +} + +ngx_int_t get_voms(ngx_http_request_t* r, + ngx_http_variable_value_t* v, + uintptr_t data) +{ + // to show that get_voms gets called only once, even if the variable is used + // twice in the configuration file + static int count = 0; + assert(count == 0); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "get_voms"); + + { + auto t0 = std::chrono::high_resolution_clock::now(); + + static ngx_str_t var = ngx_string("ssl_client_raw_cert"); + u_char unused[sizeof("ssl_client_raw_cert")]; + + auto hash = ngx_hash_strlow(unused, var.data, var.len); + + ngx_http_variable_value_t* raw_cert = ngx_http_get_variable(r, &var, hash); + + // da rivedere gli errori ritornati (sempre che siano errori) + + if (!raw_cert || raw_cert->not_found || !raw_cert->valid) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "invalid raw_cert"); + return NGX_OK; + } + + BioPtr bio{BIO_new(BIO_s_mem()), BIO_free}; + if (BIO_write(bio.get(), raw_cert->data, raw_cert->len) != raw_cert->len) { + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "BIO_write() failed"); + return NGX_OK; + } + + X509Ptr x509{PEM_read_bio_X509(bio.get(), NULL, NULL, NULL), X509_free}; + if (!x509) { + ngx_log_error( + NGX_LOG_DEBUG, r->connection->log, 0, "PEM_read_bio_X509() failed"); + return NGX_OK; + } + + auto t1 = std::chrono::high_resolution_clock::now(); + + ngx_log_error(NGX_LOG_DEBUG, + r->connection->log, + 0, + "time 1: %f us", + std::chrono::duration<double, std::micro>(t1 - t0).count()); + + ngx_log_error(NGX_LOG_DEBUG, + r->connection->log, + 0, + "raw_cert: %s", + to_string(raw_cert).c_str()); + } + + v->valid = 1; + v->no_cacheable = 1; + v->not_found = 0; + + v->data = (u_char*)"VOMS"; + v->len = 4; + + return NGX_OK; +}