/* * Copyright (C) 2011-2013 Tobias Brunner * Copyright (C) 2008 Martin Willi * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "stroke_socket.h" #include #include #include #include #include #include #include #include #include "stroke_config.h" #include "stroke_control.h" #include "stroke_cred.h" #include "stroke_ca.h" #include "stroke_attribute.h" #include "stroke_handler.h" #include "stroke_list.h" #include "stroke_counter.h" /** * To avoid clogging the thread pool with (blocking) jobs, we limit the number * of concurrently handled stroke commands. */ #define MAX_CONCURRENT_DEFAULT 4 typedef struct stroke_job_context_t stroke_job_context_t; typedef struct private_stroke_socket_t private_stroke_socket_t; /** * private data of stroke_socket */ struct private_stroke_socket_t { /** * public functions */ stroke_socket_t public; /** * Service accepting stroke connections */ stream_service_t *service; /** * configuration backend */ stroke_config_t *config; /** * attribute provider */ stroke_attribute_t *attribute; /** * attribute handler (requests only) */ stroke_handler_t *handler; /** * controller to control daemon */ stroke_control_t *control; /** * credential set */ stroke_cred_t *cred; /** * CA sections */ stroke_ca_t *ca; /** * status information logging */ stroke_list_t *list; /** * Counter values for IKE events */ stroke_counter_t *counter; /** * TRUE if log level changes are not allowed */ bool prevent_loglevel_changes; }; /** * Helper macro to log configuration options, but only if they are defined. */ #define DBG_OPT(...) VA_ARGS_DISPATCH(DBG_OPT, __VA_ARGS__)(__VA_ARGS__) #define DBG_OPT2(fmt, val) ({ \ typeof(val) _val = val; \ if (_val) { DBG2(DBG_CFG, fmt, _val); } \ }) #define DBG_OPT3(fmt, label, val) ({ \ typeof(val) _val = val; \ if (_val) { DBG2(DBG_CFG, fmt, label, _val); } \ }) /** * Helper function which corrects the string pointers * in a stroke_msg_t. Strings in a stroke_msg sent over "wire" * contains RELATIVE addresses (relative to the beginning of the * stroke_msg). They must be corrected if they reach our address * space... */ static void pop_string(stroke_msg_t *msg, char **string) { if (*string == NULL) { return; } /* check for sanity of string pointer and string */ if (string < (char**)msg || string > (char**)((char*)msg + sizeof(stroke_msg_t)) || (unsigned long)*string < (unsigned long)((char*)msg->buffer - (char*)msg) || (unsigned long)*string > msg->length) { *string = "(invalid pointer in stroke msg)"; } else { *string = (char*)msg + (unsigned long)*string; } } /** * Pop the strings of a stroke_end_t struct and log them for debugging purposes */ static void pop_end(stroke_msg_t *msg, const char* label, stroke_end_t *end) { pop_string(msg, &end->address); pop_string(msg, &end->subnets); pop_string(msg, &end->sourceip); pop_string(msg, &end->dns); pop_string(msg, &end->auth); pop_string(msg, &end->auth2); pop_string(msg, &end->id); pop_string(msg, &end->id2); pop_string(msg, &end->rsakey); pop_string(msg, &end->cert); pop_string(msg, &end->cert2); pop_string(msg, &end->ca); pop_string(msg, &end->ca2); pop_string(msg, &end->groups); pop_string(msg, &end->groups2); pop_string(msg, &end->cert_policy); pop_string(msg, &end->updown); DBG_OPT(" %s=%s", label, end->address); DBG_OPT(" %ssubnet=%s", label, end->subnets); DBG_OPT(" %ssourceip=%s", label, end->sourceip); DBG_OPT(" %sdns=%s", label, end->dns); DBG_OPT(" %sauth=%s", label, end->auth); DBG_OPT(" %sauth2=%s", label, end->auth2); DBG_OPT(" %sid=%s", label, end->id); DBG_OPT(" %sid2=%s", label, end->id2); DBG_OPT(" %srsakey=%s", label, end->rsakey); DBG_OPT(" %scert=%s", label, end->cert); DBG_OPT(" %scert2=%s", label, end->cert2); DBG_OPT(" %sca=%s", label, end->ca); DBG_OPT(" %sca2=%s", label, end->ca2); DBG_OPT(" %sgroups=%s", label, end->groups); DBG_OPT(" %sgroups2=%s", label, end->groups2); DBG_OPT(" %supdown=%s", label, end->updown); } /** * Add a connection to the configuration list */ static void stroke_add_conn(private_stroke_socket_t *this, stroke_msg_t *msg) { pop_string(msg, &msg->add_conn.name); DBG1(DBG_CFG, "received stroke: add connection '%s'", msg->add_conn.name); DBG2(DBG_CFG, "conn %s", msg->add_conn.name); pop_end(msg, "left", &msg->add_conn.me); pop_end(msg, "right", &msg->add_conn.other); pop_string(msg, &msg->add_conn.eap_identity); pop_string(msg, &msg->add_conn.aaa_identity); pop_string(msg, &msg->add_conn.xauth_identity); pop_string(msg, &msg->add_conn.algorithms.ike); pop_string(msg, &msg->add_conn.algorithms.esp); pop_string(msg, &msg->add_conn.algorithms.ah); pop_string(msg, &msg->add_conn.ikeme.mediated_by); pop_string(msg, &msg->add_conn.ikeme.peerid); DBG_OPT(" eap_identity=%s", msg->add_conn.eap_identity); DBG_OPT(" aaa_identity=%s", msg->add_conn.aaa_identity); DBG_OPT(" xauth_identity=%s", msg->add_conn.xauth_identity); DBG_OPT(" ike=%s", msg->add_conn.algorithms.ike); DBG_OPT(" esp=%s", msg->add_conn.algorithms.esp); DBG_OPT(" ah=%s", msg->add_conn.algorithms.ah); DBG_OPT(" dpddelay=%d", msg->add_conn.dpd.delay); DBG_OPT(" dpdtimeout=%d", msg->add_conn.dpd.timeout); DBG_OPT(" dpdaction=%d", msg->add_conn.dpd.action); DBG_OPT(" closeaction=%d", msg->add_conn.close_action); DBG_OPT(" sha256_96=%s", msg->add_conn.sha256_96 ? "yes" : "no"); DBG_OPT(" mediation=%s", msg->add_conn.ikeme.mediation ? "yes" : "no"); DBG_OPT(" mediated_by=%s", msg->add_conn.ikeme.mediated_by); DBG_OPT(" me_peerid=%s", msg->add_conn.ikeme.peerid); DBG_OPT(" keyexchange=ikev%u", msg->add_conn.version); this->config->add(this->config, msg); this->attribute->add_dns(this->attribute, msg); this->handler->add_attributes(this->handler, msg); } /** * Delete a connection from the list */ static void stroke_del_conn(private_stroke_socket_t *this, stroke_msg_t *msg) { pop_string(msg, &msg->del_conn.name); DBG1(DBG_CFG, "received stroke: delete connection '%s'", msg->del_conn.name); this->config->del(this->config, msg); this->attribute->del_dns(this->attribute, msg); this->handler->del_attributes(this->handler, msg); } /** * initiate a connection by name */ static void stroke_initiate(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->initiate.name); DBG1(DBG_CFG, "received stroke: initiate '%s'", msg->initiate.name); this->control->initiate(this->control, msg, out); } /** * terminate a connection by name */ static void stroke_terminate(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->terminate.name); DBG1(DBG_CFG, "received stroke: terminate '%s'", msg->terminate.name); this->control->terminate(this->control, msg, out); } /** * terminate a connection by peers virtual IP */ static void stroke_terminate_srcip(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->terminate_srcip.start); pop_string(msg, &msg->terminate_srcip.end); DBG1(DBG_CFG, "received stroke: terminate-srcip %s-%s", msg->terminate_srcip.start, msg->terminate_srcip.end); this->control->terminate_srcip(this->control, msg, out); } /** * rekey a connection by name/id */ static void stroke_rekey(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->terminate.name); DBG1(DBG_CFG, "received stroke: rekey '%s'", msg->rekey.name); this->control->rekey(this->control, msg, out); } /** * route a policy (install SPD entries) */ static void stroke_route(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->route.name); DBG1(DBG_CFG, "received stroke: route '%s'", msg->route.name); this->control->route(this->control, msg, out); } /** * unroute a policy */ static void stroke_unroute(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->terminate.name); DBG1(DBG_CFG, "received stroke: unroute '%s'", msg->route.name); this->control->unroute(this->control, msg, out); } /** * Add a ca information record to the cainfo list */ static void stroke_add_ca(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->add_ca.name); DBG1(DBG_CFG, "received stroke: add ca '%s'", msg->add_ca.name); pop_string(msg, &msg->add_ca.cacert); pop_string(msg, &msg->add_ca.crluri); pop_string(msg, &msg->add_ca.crluri2); pop_string(msg, &msg->add_ca.ocspuri); pop_string(msg, &msg->add_ca.ocspuri2); pop_string(msg, &msg->add_ca.certuribase); DBG2(DBG_CFG, "ca %s", msg->add_ca.name); DBG_OPT(" cacert=%s", msg->add_ca.cacert); DBG_OPT(" crluri=%s", msg->add_ca.crluri); DBG_OPT(" crluri2=%s", msg->add_ca.crluri2); DBG_OPT(" ocspuri=%s", msg->add_ca.ocspuri); DBG_OPT(" ocspuri2=%s", msg->add_ca.ocspuri2); DBG_OPT(" certuribase=%s", msg->add_ca.certuribase); this->ca->add(this->ca, msg); } /** * Delete a ca information record from the cainfo list */ static void stroke_del_ca(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->del_ca.name); DBG1(DBG_CFG, "received stroke: delete ca '%s'", msg->del_ca.name); this->ca->del(this->ca, msg); } /** * show status of daemon */ static void stroke_status(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out, bool all, bool wait) { pop_string(msg, &(msg->status.name)); this->list->status(this->list, msg, out, all, wait); } /** * list various information */ static void stroke_list(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { if (msg->list.flags & LIST_CAINFOS) { this->ca->list(this->ca, msg, out); } this->list->list(this->list, msg, out); } /** * reread various information */ static void stroke_reread(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { this->cred->reread(this->cred, msg, out); } /** * purge various information */ static void stroke_purge(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { if (msg->purge.flags & PURGE_OCSP) { lib->credmgr->flush_cache(lib->credmgr, CERT_X509_OCSP_RESPONSE); } if (msg->purge.flags & PURGE_CRLS) { lib->credmgr->flush_cache(lib->credmgr, CERT_X509_CRL); } if (msg->purge.flags & PURGE_CERTS) { lib->credmgr->flush_cache(lib->credmgr, CERT_X509); } if (msg->purge.flags & PURGE_IKE) { this->control->purge_ike(this->control, msg, out); } } /** * Print a certificate in PEM to out */ static void print_pem_cert(FILE *out, certificate_t *cert) { chunk_t encoded; if (cert->get_encoding(cert, CERT_PEM, &encoded)) { fprintf(out, "%.*s", (int)encoded.len, encoded.ptr); free(encoded.ptr); } } /** * Export in-memory credentials */ static void stroke_export(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->export.selector); if (msg->export.flags & EXPORT_X509) { enumerator_t *enumerator; identification_t *id; certificate_t *cert; id = identification_create_from_string(msg->export.selector); enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, CERT_X509, KEY_ANY, id, FALSE); while (enumerator->enumerate(enumerator, &cert)) { print_pem_cert(out, cert); } enumerator->destroy(enumerator); id->destroy(id); } if (msg->export.flags & (EXPORT_CONN_CERT | EXPORT_CONN_CHAIN)) { enumerator_t *sas, *auths, *certs; ike_sa_t *ike_sa; auth_cfg_t *auth; certificate_t *cert; auth_rule_t rule; sas = charon->ike_sa_manager->create_enumerator( charon->ike_sa_manager, TRUE); while (sas->enumerate(sas, &ike_sa)) { if (streq(msg->export.selector, ike_sa->get_name(ike_sa))) { auths = ike_sa->create_auth_cfg_enumerator(ike_sa, FALSE); while (auths->enumerate(auths, &auth)) { bool got_subject = FALSE; certs = auth->create_enumerator(auth); while (certs->enumerate(certs, &rule, &cert)) { switch (rule) { case AUTH_RULE_CA_CERT: case AUTH_RULE_IM_CERT: if (msg->export.flags & EXPORT_CONN_CHAIN) { print_pem_cert(out, cert); } break; case AUTH_RULE_SUBJECT_CERT: if (!got_subject) { print_pem_cert(out, cert); got_subject = TRUE; } break; default: break; } } certs->destroy(certs); } auths->destroy(auths); } } sas->destroy(sas); } } /** * list pool leases */ static void stroke_leases(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->leases.pool); pop_string(msg, &msg->leases.address); this->list->leases(this->list, msg, out); } /** * Callback function for usage report */ static void report_usage(FILE *out, int count, size_t bytes, backtrace_t *bt, bool detailed) { fprintf(out, "%zu bytes total, %d allocations, %zu bytes average:\n", bytes, count, bytes / count); bt->log(bt, out, detailed); } /** * Callback function for memusage summary */ static void sum_usage(FILE *out, int count, size_t bytes, int whitelisted) { fprintf(out, "Total memory usage: %zu\n", bytes); } /** * Show memory usage */ static void stroke_memusage(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { if (lib->leak_detective) { lib->leak_detective->usage(lib->leak_detective, (leak_detective_report_cb_t)report_usage, (leak_detective_summary_cb_t)sum_usage, out); } } /** * Set username and password for a connection */ static void stroke_user_creds(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->user_creds.name); pop_string(msg, &msg->user_creds.username); pop_string(msg, &msg->user_creds.password); DBG1(DBG_CFG, "received stroke: user-creds '%s'", msg->user_creds.name); this->config->set_user_credentials(this->config, msg, out); } /** * Print stroke counter values */ static void stroke_counters(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { pop_string(msg, &msg->counters.name); if (msg->counters.reset) { this->counter->reset(this->counter, msg->counters.name); } else { this->counter->print(this->counter, out, msg->counters.name); } } /** * set the verbosity debug output */ static void stroke_loglevel(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { debug_t group; pop_string(msg, &(msg->loglevel.type)); DBG1(DBG_CFG, "received stroke: loglevel %d for %s", msg->loglevel.level, msg->loglevel.type); if (this->prevent_loglevel_changes) { DBG1(DBG_CFG, "prevented log level change"); fprintf(out, "command not allowed!\n"); return; } if (!enum_from_name(debug_names, msg->loglevel.type, &group)) { fprintf(out, "unknown type '%s'!\n", msg->loglevel.type); return; } charon->set_level(charon, group, msg->loglevel.level); } /** * set various config options */ static void stroke_config(private_stroke_socket_t *this, stroke_msg_t *msg, FILE *out) { this->cred->cachecrl(this->cred, msg->config.cachecrl); } /** * process a stroke request */ static bool on_accept(private_stroke_socket_t *this, stream_t *stream) { stroke_msg_t *msg; uint16_t len; FILE *out; /* read length */ if (!stream->read_all(stream, &len, sizeof(len))) { if (errno != EWOULDBLOCK) { DBG1(DBG_CFG, "reading length of stroke message failed: %s", strerror(errno)); } return FALSE; } if (len < offsetof(stroke_msg_t, buffer)) { DBG1(DBG_CFG, "invalid stroke message length %d", len); return FALSE; } /* read message (we need an additional byte to terminate the buffer) */ msg = malloc(len + 1); msg->length = len; if (!stream->read_all(stream, (char*)msg + sizeof(len), len - sizeof(len))) { if (errno != EWOULDBLOCK) { DBG1(DBG_CFG, "reading stroke message failed: %s", strerror(errno)); } free(msg); return FALSE; } /* make sure even incorrectly unterminated strings don't extend over the * message boundaries */ ((char*)msg)[len] = '\0'; DBG3(DBG_CFG, "stroke message %b", (void*)msg, len); out = stream->get_file(stream); if (!out) { DBG1(DBG_CFG, "creating stroke output stream failed"); free(msg); return FALSE; } switch (msg->type) { case STR_INITIATE: stroke_initiate(this, msg, out); break; case STR_ROUTE: stroke_route(this, msg, out); break; case STR_UNROUTE: stroke_unroute(this, msg, out); break; case STR_TERMINATE: stroke_terminate(this, msg, out); break; case STR_TERMINATE_SRCIP: stroke_terminate_srcip(this, msg, out); break; case STR_REKEY: stroke_rekey(this, msg, out); break; case STR_STATUS: stroke_status(this, msg, out, FALSE, TRUE); break; case STR_STATUS_ALL: stroke_status(this, msg, out, TRUE, TRUE); break; case STR_STATUS_ALL_NOBLK: stroke_status(this, msg, out, TRUE, FALSE); break; case STR_ADD_CONN: stroke_add_conn(this, msg); break; case STR_DEL_CONN: stroke_del_conn(this, msg); break; case STR_ADD_CA: stroke_add_ca(this, msg, out); break; case STR_DEL_CA: stroke_del_ca(this, msg, out); break; case STR_LOGLEVEL: stroke_loglevel(this, msg, out); break; case STR_CONFIG: stroke_config(this, msg, out); break; case STR_LIST: stroke_list(this, msg, out); break; case STR_REREAD: stroke_reread(this, msg, out); break; case STR_PURGE: stroke_purge(this, msg, out); break; case STR_EXPORT: stroke_export(this, msg, out); break; case STR_LEASES: stroke_leases(this, msg, out); break; case STR_MEMUSAGE: stroke_memusage(this, msg, out); break; case STR_USER_CREDS: stroke_user_creds(this, msg, out); break; case STR_COUNTERS: stroke_counters(this, msg, out); break; default: DBG1(DBG_CFG, "received unknown stroke"); break; } free(msg); fclose(out); return FALSE; } METHOD(stroke_socket_t, destroy, void, private_stroke_socket_t *this) { DESTROY_IF(this->service); lib->credmgr->remove_set(lib->credmgr, &this->ca->set); lib->credmgr->remove_set(lib->credmgr, &this->cred->set); charon->backends->remove_backend(charon->backends, &this->config->backend); charon->attributes->remove_provider(charon->attributes, &this->attribute->provider); charon->attributes->remove_handler(charon->attributes, &this->handler->handler); this->cred->destroy(this->cred); this->ca->destroy(this->ca); this->config->destroy(this->config); this->attribute->destroy(this->attribute); this->handler->destroy(this->handler); this->control->destroy(this->control); this->list->destroy(this->list); this->counter->destroy(this->counter); free(this); } /* * see header file */ stroke_socket_t *stroke_socket_create() { private_stroke_socket_t *this; int max_concurrent; char *uri; INIT(this, .public = { .destroy = _destroy, }, .prevent_loglevel_changes = lib->settings->get_bool(lib->settings, "%s.plugins.stroke.prevent_loglevel_changes", FALSE, lib->ns), ); this->ca = stroke_ca_create(); this->cred = stroke_cred_create(this->ca); this->attribute = stroke_attribute_create(); this->handler = stroke_handler_create(); this->config = stroke_config_create(this->ca, this->cred, this->attribute); this->control = stroke_control_create(); this->list = stroke_list_create(this->attribute); this->counter = stroke_counter_create(); lib->credmgr->add_set(lib->credmgr, &this->ca->set); lib->credmgr->add_set(lib->credmgr, &this->cred->set); charon->backends->add_backend(charon->backends, &this->config->backend); charon->attributes->add_provider(charon->attributes, &this->attribute->provider); charon->attributes->add_handler(charon->attributes, &this->handler->handler); max_concurrent = lib->settings->get_int(lib->settings, "%s.plugins.stroke.max_concurrent", MAX_CONCURRENT_DEFAULT, lib->ns); uri = lib->settings->get_str(lib->settings, "%s.plugins.stroke.socket", "unix://" STROKE_SOCKET, lib->ns); this->service = lib->streams->create_service(lib->streams, uri, 10); if (!this->service) { DBG1(DBG_CFG, "creating stroke socket failed"); destroy(this); return NULL; } this->service->on_accept(this->service, (stream_service_cb_t)on_accept, this, JOB_PRIO_CRITICAL, max_concurrent); return &this->public; }