| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "vpn_instance.h" |
| |
| #include <arpa/inet.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <openconnect.h> |
| #include <netdb.h> |
| #include <pthread.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <unistd.h> |
| |
| #include <functional> |
| #include <queue> |
| |
| #include "nacl_io/nacl_io.h" |
| #include "ppapi/c/ppb_console.h" |
| #include "ppapi/cpp/completion_callback.h" |
| #include "ppapi/cpp/instance.h" |
| #include "ppapi/cpp/module.h" |
| #include "ppapi/cpp/var.h" |
| #include "ppapi/cpp/var_array.h" |
| #include "ppapi/cpp/var_array_buffer.h" |
| #include "ppapi/cpp/var_dictionary.h" |
| |
| namespace { |
| |
| // keys |
| const char* const kCmd = "cmd"; |
| const char* const kData = "data"; |
| |
| // js -> nacl |
| const char* const kConnect = "connect"; |
| const char* const kDisconnect = "disconnect"; |
| const char* const kPause = "pause"; |
| const char* const kResume = "resume"; |
| const char* const kReconnect = "reconnect"; |
| const char* const kCmdDebug = "debug"; |
| const char* const kCryptoGetCert = "crypto-getcert"; |
| const char* const kCryptoGetPrivkey = "crypto-getprivkey"; |
| const char* const kCryptoSign = "crypto-sign"; |
| |
| // nacl -> js |
| const char* const kStatus = "status"; |
| const char* const kIp = "ip"; |
| const char* const kAuthForm = "auth_form"; |
| const char* const kPeerCert = "peer_cert"; |
| const char* const kAbort = "abort"; |
| |
| // other parameters |
| const char* const kUrl = "url"; |
| |
| // ip_info fields |
| const char* const kGatewayIps = "gateway_ips"; |
| const char* const kIpv4Addr = "ipv4_addr"; |
| const char* const kIpv4Netmask = "ipv4_netmask"; |
| const char* const kIpv6Addr = "ipv6_addr"; |
| const char* const kIpv6Netmask = "ipv6_netmask"; |
| const char* const kDns = "dns"; |
| const char* const kNbns = "nbns"; |
| const char* const kDomain = "domain"; |
| const char* const kProxyPac = "proxy_pac"; |
| const char* const kMtu = "mtu"; |
| const char* const kSplitDns = "split_dns"; |
| const char* const kSplitIncludes = "split_includes"; |
| const char* const kSplitExcludes = "split_excludes"; |
| |
| // status values |
| const char* const kConnected = "connected"; |
| const char* const kReconnected = "reconnected"; |
| const char* const kDisconnected = "disconnected"; |
| |
| // oc_auth_form fields |
| const char* const kFormBanner = "banner"; |
| const char* const kFormMessage = "message"; |
| const char* const kFormError = "error"; |
| const char* const kFormAuthId = "auth_id"; |
| const char* const kFormMethod = "method"; |
| const char* const kFormAction = "action"; |
| const char* const kFormOpts = "opts"; |
| const char* const kFormAuthgroupSelection = "authgroup_selection"; |
| |
| // oc_form_opt and oc_form_opt_select fields |
| const char* const kOptType = "type"; |
| const char* const kOptName = "name"; |
| const char* const kOptLabel = "label"; |
| const char* const kOptFlags = "flags"; |
| const char* const kOptChoices = "choices"; |
| |
| // OC_FORM_OPT_* enums |
| const char* const kOptTypeText = "text"; |
| const char* const kOptTypePassword = "password"; |
| const char* const kOptTypeSelect = "select"; |
| const char* const kOptTypeHidden = "hidden"; |
| const char* const kOptTypeToken = "token"; |
| const char* const kOptTypeUnknown = "unknown"; |
| const char* const kOptFlagIgnore = "ignore"; |
| const char* const kOptFlagNumeric = "numeric"; |
| |
| // oc_choice fields |
| const char* const kChoiceName = "name"; |
| const char* const kChoiceLabel = "label"; |
| const char* const kChoiceAuthType = "auth_type"; |
| const char* const kChoiceOverrideName = "override_name"; |
| const char* const kChoiceOverrideLabel = "override_label"; |
| |
| // js -> nacl auth_form results |
| const char* const kResult = "result"; |
| const char* const kSubmit = "submit"; |
| const char* const kCancel = "cancel"; |
| const char* const kNewgroup = "newgroup"; |
| |
| // peer cert validation |
| const char* const kReason = "reason"; |
| const char* const kHostname = "hostname"; |
| const char* const kCertChain = "cert_chain"; |
| const char* const kCertHash = "cert_hash"; |
| const char* const kAccept = "accept"; |
| |
| // crypto fields |
| const char* const kHash = "hash"; |
| const char* const kClientCert = "client_cert"; |
| const char* const kSuccess = "success"; |
| const char* const kPrivkeyType = "privkey_type"; |
| |
| // misc constants |
| const int kReconnectTimeout = 180; |
| const int kReconnectInterval = 5; |
| |
| } // namespace |
| |
| namespace vpn_nacl { |
| |
| VpnInstance::VpnInstance(PP_Instance instance) : |
| pp::Instance(instance) { |
| nacl_io_init_ppapi(instance, pp::Module::Get()->get_browser_interface()); |
| core_ = pp::Module::Get()->core(); |
| debug_enabled_ = false; |
| InitBackgroundThreads(); |
| |
| pthread_mutex_init(&lib_mutex_, NULL); |
| |
| pthread_cond_init(&auth_result_ready_, NULL); |
| pthread_mutex_init(&auth_result_mutex_, NULL); |
| |
| pthread_cond_init(&crypto_result_ready_, NULL); |
| pthread_mutex_init(&crypto_result_mutex_, NULL); |
| |
| crypto_ = new Crypto(this); |
| } |
| |
| void VpnInstance::HandleMessage(const pp::Var& var_message) { |
| // data from kernel |
| if (var_message.is_array_buffer()) { |
| pp::VarArrayBuffer* pkt = new pp::VarArrayBuffer(var_message); |
| |
| pthread_mutex_lock(&rx_data_mutex_); |
| rx_queue_.push(pkt); |
| pthread_cond_signal(&rx_data_ready_); |
| pthread_mutex_unlock(&rx_data_mutex_); |
| |
| return; |
| } |
| |
| if (!var_message.is_dictionary()) { |
| Log(kFatal, "malformed message: not a dictionary"); |
| return; |
| } |
| |
| pp::VarDictionary* dict = new pp::VarDictionary(var_message); |
| std::string cmd = dict->Get(kCmd).AsString(); |
| |
| if (cmd == kConnect) { |
| SetDesiredState(kStateRunning); |
| // dict is passed to ConnectionThread as connect_options_ |
| Connect(dict); |
| return; |
| } else if (cmd == kAuthForm || cmd == kPeerCert) { |
| // stored in auth_result_ |
| SendAuthResult(dict); |
| return; |
| } else if (cmd == kCryptoGetCert || cmd == kCryptoGetPrivkey || |
| cmd == kCryptoSign) { |
| // stored in crypto_result_ |
| SendCryptoResult(dict); |
| return; |
| } else if (cmd == kDisconnect) { |
| SetDesiredState(kStateDisconnected); |
| SendCommand(OC_CMD_CANCEL); |
| } else if (cmd == kPause) { |
| SetDesiredState(kStatePaused); |
| SendCommand(OC_CMD_PAUSE); |
| } else if (cmd == kResume) { |
| SetDesiredState(kStateRunning); |
| } else if (cmd == kReconnect) { |
| // i.e. drop connection, then immediate reinstate it |
| SetDesiredState(kStateRunning); |
| SendCommand(OC_CMD_PAUSE); |
| } else if (cmd == kCmdDebug) { |
| debug_enabled_ = dict->Get(kData).AsBool(); |
| } else { |
| Log(kError, "unrecognized command '%s'", cmd.c_str()); |
| } |
| |
| delete dict; |
| } |
| |
| int VpnInstance::CryptoGetCert(std::string& sha256, |
| void** cert_der, |
| size_t* cert_der_len) { |
| pp::VarDictionary cmd_dict; |
| |
| cmd_dict.Set(kCmd, kCryptoGetCert); |
| cmd_dict.Set(kHash, sha256); |
| |
| pthread_mutex_lock(&crypto_result_mutex_); |
| PostMessage(cmd_dict); |
| pthread_cond_wait(&crypto_result_ready_, &crypto_result_mutex_); |
| |
| *cert_der = nullptr; |
| pp::Var cert_der_var = crypto_result_->Get(kData); |
| if (cert_der_var.is_array_buffer()) { |
| pp::VarArrayBuffer buf(cert_der_var); |
| *cert_der_len = buf.ByteLength(); |
| *cert_der = malloc(*cert_der_len); |
| if (*cert_der) { |
| memcpy(*cert_der, buf.Map(), *cert_der_len); |
| } |
| } |
| |
| delete crypto_result_; |
| crypto_result_ = nullptr; |
| pthread_mutex_unlock(&crypto_result_mutex_); |
| |
| return *cert_der ? 0 : -1; |
| } |
| |
| int VpnInstance::CryptoGetPrivkey(std::string& sha256, |
| std::string* pk_algorithm, |
| std::string* sign_algorithm) { |
| pp::VarDictionary cmd_dict; |
| |
| cmd_dict.Set(kCmd, kCryptoGetPrivkey); |
| cmd_dict.Set(kHash, sha256); |
| |
| pthread_mutex_lock(&crypto_result_mutex_); |
| PostMessage(cmd_dict); |
| pthread_cond_wait(&crypto_result_ready_, &crypto_result_mutex_); |
| |
| int ret = -1; |
| if (crypto_result_->Get(kSuccess).AsBool()) { |
| *pk_algorithm = crypto_result_->Get(kPrivkeyType).AsString(); |
| ret = 0; |
| } |
| |
| delete crypto_result_; |
| crypto_result_ = nullptr; |
| pthread_mutex_unlock(&crypto_result_mutex_); |
| |
| return ret; |
| } |
| |
| int VpnInstance::CryptoSign(std::string& sha256, |
| void* raw_data, |
| size_t raw_data_len, |
| void** signature, |
| size_t* signature_len) { |
| pp::VarDictionary cmd_dict; |
| |
| cmd_dict.Set(kCmd, kCryptoSign); |
| cmd_dict.Set(kHash, sha256); |
| |
| pp::VarArrayBuffer raw_data_var(raw_data_len); |
| memcpy(raw_data_var.Map(), raw_data, raw_data_len); |
| cmd_dict.Set(kData, raw_data_var); |
| |
| pthread_mutex_lock(&crypto_result_mutex_); |
| PostMessage(cmd_dict); |
| pthread_cond_wait(&crypto_result_ready_, &crypto_result_mutex_); |
| |
| *signature = nullptr; |
| pp::Var sig_var = crypto_result_->Get(kData); |
| if (sig_var.is_array_buffer()) { |
| pp::VarArrayBuffer buf(sig_var); |
| *signature_len = buf.ByteLength(); |
| *signature = malloc(*signature_len); |
| if (*signature) |
| memcpy(*signature, buf.Map(), *signature_len); |
| } |
| |
| delete crypto_result_; |
| crypto_result_ = nullptr; |
| pthread_mutex_unlock(&crypto_result_mutex_); |
| |
| return *signature ? 0 : -1; |
| } |
| |
| void VpnInstance::SendCryptoResult(pp::VarDictionary* dict) { |
| pthread_mutex_lock(&crypto_result_mutex_); |
| if (crypto_result_) { |
| Log(kError, "crypto_result_ is already set"); |
| delete crypto_result_; |
| } |
| crypto_result_ = dict; |
| pthread_cond_signal(&crypto_result_ready_); |
| pthread_mutex_unlock(&crypto_result_mutex_); |
| } |
| |
| void VpnInstance::SimpleMessage(const char* const cmd, |
| const char* const data) { |
| pp::VarDictionary dict; |
| |
| dict.Set(kCmd, cmd); |
| if (data) |
| dict.Set(kData, data); |
| |
| PostMessage(dict); |
| } |
| |
| void VpnInstance::VLog(VpnLogLevel level, const char* fmt, va_list ap) { |
| PP_LogLevel pp_level; |
| |
| switch (level) { |
| case kDebug: |
| if (!debug_enabled_) |
| return; |
| // fall through |
| case kVerbose: |
| pp_level = PP_LOGLEVEL_TIP; |
| break; |
| case kInfo: |
| pp_level = PP_LOGLEVEL_LOG; |
| break; |
| case kWarning: |
| pp_level = PP_LOGLEVEL_WARNING; |
| break; |
| default: |
| pp_level = PP_LOGLEVEL_ERROR; |
| } |
| |
| char msg[kMaxMsg]; |
| vsnprintf(msg, sizeof(msg), fmt, ap); |
| LogToConsole(pp_level, msg); |
| |
| // Asks JS to kill the NaCl process (eventually...) |
| if (level == kFatal) |
| SimpleMessage(kAbort, nullptr); |
| } |
| |
| void VpnInstance::Log(VpnLogLevel level, const char* fmt, ...) { |
| va_list ap; |
| |
| va_start(ap, fmt); |
| VLog(level, fmt, ap); |
| va_end(ap); |
| } |
| |
| void VpnInstance::CryptoAbort(const char* fmt, va_list ap) { |
| VLog(kFatal, fmt, ap); |
| } |
| |
| void VpnInstance::InitBackgroundThreads() { |
| int fd_pair[2]; |
| if (socketpair(AF_UNIX, SOCK_DGRAM, 0, fd_pair) < 0) |
| Log(kFatal, "socketpair() failed: %d", errno); |
| |
| tun_fd_ = fd_pair[0]; |
| tun_lib_fd_ = fd_pair[1]; |
| |
| pthread_cond_init(&rx_data_ready_, NULL); |
| pthread_mutex_init(&rx_data_mutex_, NULL); |
| |
| pthread_cond_init(&desired_state_ready_, NULL); |
| pthread_mutex_init(&desired_state_mutex_, NULL); |
| |
| pthread_create(&rx_thread_, NULL, &RxThread, this); |
| pthread_create(&tx_thread_, NULL, &TxThread, this); |
| } |
| |
| void* VpnInstance::RxThread() { |
| pthread_mutex_lock(&rx_data_mutex_); |
| while (1) { |
| pthread_cond_wait(&rx_data_ready_, &rx_data_mutex_); |
| while (!rx_queue_.empty()) { |
| pp::VarArrayBuffer* pkt = rx_queue_.front(); |
| rx_queue_.pop(); |
| pthread_mutex_unlock(&rx_data_mutex_); |
| |
| void* data = pkt->Map(); |
| int len = pkt->ByteLength(); |
| if (write(tun_fd_, data, len) != len) |
| rx_dropped_++; |
| pkt->Unmap(); |
| delete pkt; |
| |
| pthread_mutex_lock(&rx_data_mutex_); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void* VpnInstance::RxThread(void* data) { |
| VpnInstance* self = static_cast<VpnInstance*>(data); |
| return self->RxThread(); |
| } |
| |
| void* VpnInstance::TxThread() { |
| int ret; |
| char pkt[kMaxPkt]; |
| |
| while (1) { |
| ret = read(tun_fd_, pkt, sizeof(pkt)); |
| if (ret <= 0) |
| break; |
| |
| pp::VarArrayBuffer out(ret); |
| char* data = static_cast<char*>(out.Map()); |
| memcpy(data, pkt, ret); |
| PostMessage(out); |
| } |
| return NULL; |
| } |
| |
| void* VpnInstance::TxThread(void* data) { |
| VpnInstance* self = static_cast<VpnInstance*>(data); |
| return self->TxThread(); |
| } |
| |
| void VpnInstance::SetupTunCb() { |
| const struct oc_ip_info* ip_info; |
| |
| if (openconnect_get_ip_info(oc_, &ip_info, NULL, NULL) < 0) { |
| Log(kFatal, "Error retrieving connection parameters"); |
| return; |
| } |
| SendIpInfo(ip_info); |
| |
| if (openconnect_setup_tun_fd(oc_, tun_lib_fd_) < 0) { |
| Log(kFatal, "Error setting up tunnel"); |
| return; |
| } |
| } |
| |
| void VpnInstance::SetupTunCb(void* privdata) { |
| VpnInstance* self = static_cast<VpnInstance*>(privdata); |
| return self->SetupTunCb(); |
| } |
| |
| void VpnInstance::ReconnectedCb() { |
| SimpleMessage(kStatus, kReconnected); |
| } |
| |
| void VpnInstance::ReconnectedCb(void* privdata) { |
| VpnInstance* self = static_cast<VpnInstance*>(privdata); |
| return self->ReconnectedCb(); |
| } |
| |
| int VpnInstance::PeerCertCb(const char* reason) { |
| pp::VarDictionary cmd_dict; |
| |
| cmd_dict.Set(kCmd, kPeerCert); |
| cmd_dict.Set(kReason, reason); |
| |
| cmd_dict.Set(kHostname, openconnect_get_dnsname(oc_)); |
| |
| pp::VarArray certs; |
| struct oc_cert *chain; |
| int ncerts = openconnect_get_peer_cert_chain(oc_, &chain); |
| if (ncerts <= 0) { |
| Log(kWarning, "error retrieving cert chain"); |
| } else { |
| for (int i = 0; i < ncerts; i++) { |
| pp::VarArrayBuffer jscert(chain[i].der_len); |
| void* buf = jscert.Map(); |
| memcpy(buf, chain[i].der_data, chain[i].der_len); |
| jscert.Unmap(); |
| certs.Set(i, jscert); |
| } |
| openconnect_free_peer_cert_chain(oc_, chain); |
| } |
| |
| cmd_dict.Set(kCertChain, certs); |
| cmd_dict.Set(kCertHash, openconnect_get_peer_cert_hash(oc_)); |
| |
| pthread_mutex_lock(&auth_result_mutex_); |
| PostMessage(cmd_dict); |
| pthread_cond_wait(&auth_result_ready_, &auth_result_mutex_); |
| |
| std::string result = auth_result_->Get(kResult).AsString(); |
| int ret = -1; |
| if (result == kAccept) |
| ret = 0; |
| |
| delete auth_result_; |
| auth_result_ = nullptr; |
| pthread_mutex_unlock(&auth_result_mutex_); |
| |
| return ret; |
| } |
| |
| int VpnInstance::PeerCertCb(void* privdata, const char* reason) { |
| VpnInstance* self = static_cast<VpnInstance*>(privdata); |
| return self->PeerCertCb(reason); |
| } |
| |
| int VpnInstance::NewConfigCb(const char* buf, int buflen) { |
| Log(kInfo, "%s", __FUNCTION__); |
| return 0; |
| } |
| |
| int VpnInstance::NewConfigCb(void* privdata, const char* buf, int buflen) { |
| VpnInstance* self = static_cast<VpnInstance*>(privdata); |
| return self->NewConfigCb(buf, buflen); |
| } |
| |
| void VpnInstance::TranslateSelectOpt(struct oc_form_opt_select* opt, |
| pp::VarDictionary& opt_dict) { |
| pp::VarArray choices; |
| |
| for (int i = 0; i < opt->nr_choices; i++) { |
| struct oc_choice* in = opt->choices[i]; |
| pp::VarDictionary out; |
| |
| out.Set(kChoiceName, in->name); |
| if (in->label) |
| out.Set(kChoiceLabel, in->label); |
| if (in->auth_type) |
| out.Set(kChoiceAuthType, in->auth_type); |
| if (in->override_name) |
| out.Set(kChoiceOverrideName, in->override_name); |
| if (in->override_label) |
| out.Set(kChoiceOverrideLabel, in->override_label); |
| |
| choices.Set(i, out); |
| } |
| opt_dict.Set(kOptChoices, choices); |
| } |
| |
| void VpnInstance::TranslateOpt(struct oc_form_opt* opt, |
| pp::VarDictionary& opt_dict) { |
| opt_dict.Set(kOptName, opt->name); |
| if (opt->label) |
| opt_dict.Set(kOptLabel, opt->label); |
| |
| pp::VarDictionary opt_flags; |
| opt_flags.Set(kOptFlagIgnore, !!(opt->flags & OC_FORM_OPT_IGNORE)); |
| opt_flags.Set(kOptFlagNumeric, !!(opt->flags & OC_FORM_OPT_NUMERIC)); |
| opt_dict.Set(kOptFlags, opt_flags); |
| |
| switch (opt->type) { |
| case OC_FORM_OPT_TEXT: |
| opt_dict.Set(kOptType, kOptTypeText); |
| break; |
| case OC_FORM_OPT_PASSWORD: |
| opt_dict.Set(kOptType, kOptTypePassword); |
| break; |
| case OC_FORM_OPT_SELECT: |
| opt_dict.Set(kOptType, kOptTypeSelect); |
| TranslateSelectOpt(reinterpret_cast<struct oc_form_opt_select*>(opt), |
| opt_dict); |
| break; |
| case OC_FORM_OPT_HIDDEN: |
| opt_dict.Set(kOptType, kOptTypeHidden); |
| break; |
| case OC_FORM_OPT_TOKEN: |
| opt_dict.Set(kOptType, kOptTypeToken); |
| break; |
| default: |
| opt_dict.Set(kOptType, kOptTypeUnknown); |
| } |
| } |
| |
| int VpnInstance::TranslateAuthResult(pp::VarDictionary* dict, |
| struct oc_auth_form* form) { |
| pp::VarDictionary opts(dict->Get(kFormOpts)); |
| |
| if (!opts.is_undefined()) { |
| struct oc_form_opt* opt; |
| for (opt = form->opts; opt; opt = opt->next) { |
| pp::Var value = opts.Get(opt->name); |
| if (!value.is_undefined()) { |
| std::string str = value.AsString(); |
| openconnect_set_option_value(opt, strdup(str.c_str())); |
| } |
| } |
| } |
| |
| std::string result = dict->Get(kResult).AsString(); |
| if (result == kSubmit) |
| return OC_FORM_RESULT_OK; |
| else if (result == kCancel) |
| return OC_FORM_RESULT_CANCELLED; |
| else if (result == kNewgroup) |
| return OC_FORM_RESULT_NEWGROUP; |
| else |
| return OC_FORM_RESULT_ERR; |
| } |
| |
| int VpnInstance::AuthFormCb(struct oc_auth_form* form) { |
| pp::VarDictionary cmd_dict; |
| |
| cmd_dict.Set(kCmd, kAuthForm); |
| |
| // top-level fields |
| if (form->banner) |
| cmd_dict.Set(kFormBanner, form->banner); |
| if (form->message) |
| cmd_dict.Set(kFormMessage, form->message); |
| if (form->error) |
| cmd_dict.Set(kFormError, form->error); |
| if (form->auth_id) |
| cmd_dict.Set(kFormAuthId, form->auth_id); |
| if (form->method) |
| cmd_dict.Set(kFormMethod, form->method); |
| if (form->action) |
| cmd_dict.Set(kFormAction, form->action); |
| |
| // individual options |
| int id; |
| struct oc_form_opt* opt; |
| pp::VarArray opt_array; |
| |
| for (opt = form->opts, id = 0; opt; opt = opt->next, id++) { |
| pp::VarDictionary opt_dict; |
| TranslateOpt(opt, opt_dict); |
| if (opt == reinterpret_cast<struct oc_form_opt*>(form->authgroup_opt)) |
| opt_dict.Set(kFormAuthgroupSelection, form->authgroup_selection); |
| opt_array.Set(id, opt_dict); |
| } |
| |
| cmd_dict.Set(kFormOpts, opt_array); |
| |
| pthread_mutex_lock(&auth_result_mutex_); |
| PostMessage(cmd_dict); |
| pthread_cond_wait(&auth_result_ready_, &auth_result_mutex_); |
| |
| int ret = TranslateAuthResult(auth_result_, form); |
| |
| delete auth_result_; |
| auth_result_ = nullptr; |
| pthread_mutex_unlock(&auth_result_mutex_); |
| |
| return ret; |
| } |
| |
| void VpnInstance::SendAuthResult(pp::VarDictionary* dict) { |
| pthread_mutex_lock(&auth_result_mutex_); |
| if (auth_result_) { |
| Log(kError, "auth_result_ is already set"); |
| delete auth_result_; |
| } |
| auth_result_ = dict; |
| pthread_cond_signal(&auth_result_ready_); |
| pthread_mutex_unlock(&auth_result_mutex_); |
| } |
| |
| int VpnInstance::AuthFormCb(void* privdata, struct oc_auth_form* form) { |
| VpnInstance* self = static_cast<VpnInstance*>(privdata); |
| return self->AuthFormCb(form); |
| } |
| |
| void VpnInstance::ProgressCb(int level, const char* fmt, va_list ap) { |
| VpnLogLevel mapping; |
| |
| switch (level) { |
| case PRG_TRACE: |
| mapping = kDebug; |
| break; |
| case PRG_DEBUG: |
| mapping = kVerbose; |
| break; |
| case PRG_INFO: |
| mapping = kInfo; |
| break; |
| default: |
| mapping = kError; |
| } |
| VLog(mapping, fmt, ap); |
| } |
| |
| void VpnInstance::ProgressCb(void* privdata, int level, const char* fmt, ...) { |
| va_list ap; |
| va_start(ap, fmt); |
| VpnInstance* self = static_cast<VpnInstance*>(privdata); |
| self->ProgressCb(level, fmt, ap); |
| va_end(ap); |
| } |
| |
| void VpnInstance::SendIpInfo(const struct oc_ip_info* ip_info) |
| { |
| pp::VarDictionary dict; |
| |
| dict.Set(kCmd, kIp); |
| |
| if (ip_info->addr) |
| dict.Set(kIpv4Addr, ip_info->addr); |
| if (ip_info->netmask) |
| dict.Set(kIpv4Netmask, ip_info->netmask); |
| if (ip_info->addr6) |
| dict.Set(kIpv6Addr, ip_info->addr6); |
| if (ip_info->netmask6) |
| dict.Set(kIpv6Netmask, ip_info->netmask6); |
| if (ip_info->domain) |
| dict.Set(kDomain, ip_info->domain); |
| if (ip_info->proxy_pac) |
| dict.Set(kProxyPac, ip_info->proxy_pac); |
| dict.Set(kMtu, ip_info->mtu); |
| |
| int i; |
| pp::VarArray dns; |
| for (i = 0; i < 3; i++) { |
| if (ip_info->dns[i]) |
| dns.Set(i, ip_info->dns[i]); |
| } |
| dict.Set(kDns, dns); |
| |
| pp::VarArray nbns; |
| for (i = 0; i < 3; i++) { |
| if (ip_info->nbns[i]) |
| dns.Set(i, ip_info->nbns[i]); |
| } |
| dict.Set(kNbns, nbns); |
| |
| struct oc_split_include* entry; |
| pp::VarArray split_dns; |
| for (entry = ip_info->split_dns, i = 0; entry; entry = entry->next) |
| split_dns.Set(i++, entry->route); |
| dict.Set(kSplitDns, split_dns); |
| |
| pp::VarArray split_includes; |
| for (entry = ip_info->split_includes, i = 0; entry; entry = entry->next) |
| split_includes.Set(i++, entry->route); |
| dict.Set(kSplitIncludes, split_includes); |
| |
| pp::VarArray split_excludes; |
| for (entry = ip_info->split_excludes, i = 0; entry; entry = entry->next) |
| split_excludes.Set(i++, entry->route); |
| dict.Set(kSplitExcludes, split_excludes); |
| |
| pp::VarArray gateway_ips; |
| gateway_ips.Set(0, ip_info->gateway_addr); |
| dict.Set(kGatewayIps, gateway_ips); |
| |
| PostMessage(dict); |
| SimpleMessage(kStatus, kConnected); |
| } |
| |
| void VpnInstance::ConnectionThread() { |
| Log(kInfo, "starting connection thread..."); |
| |
| std::string url = connect_options_->Get(kUrl).AsString(); |
| if (openconnect_parse_url(oc_, url.c_str()) < 0) { |
| Log(kFatal, "Can't parse URL '%s'", url.c_str()); |
| return; |
| } |
| |
| pp::Var cert_var = connect_options_->Get(kClientCert); |
| if (cert_var.is_string()) { |
| const char* cert_url = cert_var.AsString().c_str(); |
| if (openconnect_set_client_cert(oc_, cert_url, cert_url)) { |
| Log(kFatal, "Error setting cert URL '%s'", cert_url); |
| } |
| } |
| |
| openconnect_set_system_trust(oc_, 0); |
| if (openconnect_obtain_cookie(oc_) < 0) { |
| Log(kFatal, "Error logging in to gateway"); |
| return; |
| } |
| if (openconnect_make_cstp_connection(oc_) < 0) { |
| Log(kFatal, "Error connecting to gateway"); |
| return; |
| } |
| |
| if (openconnect_setup_dtls(oc_, 60)) |
| Log(kWarning, "Could not configure DTLS"); |
| |
| cmd_fd_ = openconnect_setup_cmd_pipe(oc_); |
| if (cmd_fd_ < 0) { |
| Log(kFatal, "Error setting up command interface"); |
| return; |
| } |
| if (fcntl(cmd_fd_, F_SETFL, O_NONBLOCK) < 0) { |
| Log(kFatal, "Error setting nonblocking mode on cmd_fd: %d", errno); |
| return; |
| } |
| |
| openconnect_set_setup_tun_handler(oc_, SetupTunCb); |
| openconnect_set_reconnected_handler(oc_, ReconnectedCb); |
| |
| while (1) { |
| pthread_mutex_lock(&desired_state_mutex_); |
| if (desired_state_ == kStateDisconnected) { |
| pthread_mutex_unlock(&desired_state_mutex_); |
| break; |
| } else if (desired_state_ == kStatePaused) { |
| pthread_cond_wait(&desired_state_ready_, &desired_state_mutex_); |
| pthread_mutex_unlock(&desired_state_mutex_); |
| } else if (desired_state_ == kStateRunning) { |
| pthread_mutex_unlock(&desired_state_mutex_); |
| if (openconnect_mainloop(oc_, kReconnectTimeout, kReconnectInterval)) |
| break; |
| } |
| } |
| |
| SimpleMessage(kStatus, kDisconnected); |
| |
| pthread_mutex_lock(&lib_mutex_); |
| delete connect_options_; |
| connect_options_ = nullptr; |
| openconnect_vpninfo_free(oc_); |
| cmd_fd_ = 0; |
| oc_ = nullptr; |
| pthread_mutex_unlock(&lib_mutex_); |
| } |
| |
| void* VpnInstance::ConnectionThread(void* privdata) { |
| VpnInstance* self = static_cast<VpnInstance*>(privdata); |
| self->ConnectionThread(); |
| return NULL; |
| } |
| |
| void VpnInstance::Connect(pp::VarDictionary* dict) { |
| pthread_mutex_lock(&lib_mutex_); |
| if (oc_) { |
| Log(kError, "called Connect() with another session in progress"); |
| pthread_mutex_unlock(&lib_mutex_); |
| return; |
| } |
| |
| openconnect_init_ssl(); |
| oc_ = openconnect_vpninfo_new("CrOS", PeerCertCb, NewConfigCb, |
| AuthFormCb, ProgressCb, this); |
| if (!oc_) { |
| Log(kFatal, "unable to create library instance"); |
| return; |
| } |
| |
| connect_options_ = dict; |
| gateway_ips_.clear(); |
| rx_dropped_ = 0; |
| |
| pthread_create(&connection_thread_, NULL, &ConnectionThread, this); |
| pthread_mutex_unlock(&lib_mutex_); |
| } |
| |
| void VpnInstance::SendCommand(char cmd) { |
| if (!cmd_fd_ || write(cmd_fd_, &cmd, 1) != 1) |
| Log(kWarning, "error writing cmd: %d", errno); |
| } |
| |
| void VpnInstance::SetDesiredState(MainloopState new_state) { |
| pthread_mutex_lock(&desired_state_mutex_); |
| desired_state_ = new_state; |
| pthread_cond_signal(&desired_state_ready_); |
| pthread_mutex_unlock(&desired_state_mutex_); |
| } |
| |
| } // namespace vpn_nacl |