Created
December 19, 2009 13:58
-
-
Save billywhizz/260085 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2009 Ryan Dahl <[email protected]> | |
#include <node_net.h> | |
#include <assert.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <strings.h> | |
#include <errno.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netdb.h> | |
#include <arpa/inet.h> /* inet_ntop */ | |
#include <netinet/in.h> /* sockaddr_in, sockaddr_in6 */ | |
using namespace v8; | |
namespace node { | |
#define UTF8_SYMBOL String::NewSymbol("utf8") | |
#define BINARY_SYMBOL String::NewSymbol("binary") | |
#define ASCII_SYMBOL String::NewSymbol("ascii") | |
#define SERVER_SYMBOL String::NewSymbol("server") | |
#define REMOTE_ADDRESS_SYMBOL String::NewSymbol("remoteAddress") | |
#define FD_SYMBOL String::NewSymbol("fd") | |
#define READY_STATE_SYMBOL String::NewSymbol("readyState") | |
#define OPEN_SYMBOL String::NewSymbol("open") | |
#define OPENING_SYMBOL String::NewSymbol("opening") | |
#define READ_ONLY_SYMBOL String::NewSymbol("readOnly") | |
#define WRITE_ONLY_SYMBOL String::NewSymbol("writeOnly") | |
#define CLOSING_SYMBOL String::NewSymbol("closing") | |
#define CLOSED_SYMBOL String::NewSymbol("closed") | |
static const struct addrinfo server_tcp_hints = | |
/* ai_flags */ { AI_PASSIVE | |
/* ai_family */ , AF_UNSPEC | |
/* ai_socktype */ , SOCK_STREAM | |
, 0 | |
}; | |
static const struct addrinfo client_tcp_hints = | |
/* ai_flags */ { 0 | |
/* ai_family */ , AF_UNSPEC | |
/* ai_socktype */ , SOCK_STREAM | |
, 0 | |
}; | |
Persistent<FunctionTemplate> Connection::constructor_template; | |
// Initialize the tcp.Connection object. | |
void Connection::Initialize(v8::Handle<v8::Object> target) { | |
HandleScope scope; | |
Local<FunctionTemplate> t = FunctionTemplate::New(New); | |
constructor_template = Persistent<FunctionTemplate>::New(t); | |
// Inherits from node.EventEmitter | |
constructor_template->Inherit(EventEmitter::constructor_template); | |
// All native node objects have 1 internal field (required by ObjectWrap) | |
constructor_template->InstanceTemplate()->SetInternalFieldCount(1); | |
// Set class name for debugging output | |
constructor_template->SetClassName(String::NewSymbol("Connection")); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "connect", Connect); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "send", Send); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "forceClose", ForceClose); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setEncoding", SetEncoding); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "readPause", ReadPause); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "readResume", ReadResume); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setTimeout", SetTimeout); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setNoDelay", SetNoDelay); | |
#if EVCOM_HAVE_GNUTLS | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setSecure", SetSecure); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "verifyPeer", VerifyPeer); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "getPeerCertificate", | |
GetPeerCertificate); | |
gnutls_global_init(); | |
#endif | |
// Getter for connection.readyState | |
constructor_template->PrototypeTemplate()->SetAccessor( | |
READY_STATE_SYMBOL, | |
ReadyStateGetter); | |
// Getter for connection.readyState | |
constructor_template->PrototypeTemplate()->SetAccessor( | |
FD_SYMBOL, | |
FDGetter); | |
// Assign class to its place as tcp.Connection | |
target->Set(String::NewSymbol("Connection"), | |
constructor_template->GetFunction()); | |
} | |
Handle<Value> Connection::ReadyStateGetter(Local<String> property, | |
const AccessorInfo& info) { | |
// Unwrap the javascript object to get the C++ object | |
Connection *connection = ObjectWrap::Unwrap<Connection>(info.This()); | |
assert(connection); | |
HandleScope scope; | |
assert(property == READY_STATE_SYMBOL); | |
// Resolving is not done in evcom, it's done in this file. Thus we add | |
// this "opening" symbol to the native EVCOM ready states. "opening" | |
// really means "resolving". | |
if (connection->resolving_) return scope.Close(OPENING_SYMBOL); | |
// Map between the evcom enum and V8 strings: | |
switch (evcom_stream_state(&connection->stream_)) { | |
case EVCOM_INITIALIZED: return scope.Close(CLOSED_SYMBOL); | |
case EVCOM_CONNECTING: return scope.Close(OPENING_SYMBOL); | |
case EVCOM_CONNECTED_RW: return scope.Close(OPEN_SYMBOL); | |
case EVCOM_CONNECTED_RO: return scope.Close(READ_ONLY_SYMBOL); | |
case EVCOM_CONNECTED_WO: return scope.Close(WRITE_ONLY_SYMBOL); | |
case EVCOM_CLOSING: return scope.Close(CLOSING_SYMBOL); | |
case EVCOM_CLOSED: return scope.Close(CLOSED_SYMBOL); | |
} | |
assert(0 && "This shouldnt happen"); | |
return ThrowException(Exception::Error( | |
String::New("This shouldn't happen."))); | |
} | |
Handle<Value> Connection::FDGetter(Local<String> property, | |
const AccessorInfo& info) { | |
// Unwrap the javascript object to get the C++ object | |
Connection *connection = ObjectWrap::Unwrap<Connection>(info.This()); | |
assert(connection); | |
HandleScope scope; | |
assert(property == FD_SYMBOL); | |
Local<Integer> fd = Integer::New(connection->stream_.recvfd); | |
return scope.Close(fd); | |
} | |
// Constructor - these actions are not taken in the normal constructor | |
// (Connection::Connection) because sometimes the Connection needs to be | |
// reinitialized without destroying the object. | |
void Connection::Init() { | |
resolving_ = false; | |
secure_ = false; | |
evcom_stream_init(&stream_); | |
stream_.on_connect = Connection::on_connect; | |
stream_.on_read = Connection::on_read; | |
stream_.on_close = Connection::on_close; | |
stream_.on_timeout = Connection::on_timeout; | |
stream_.on_drain = Connection::on_drain; | |
stream_.data = this; | |
} | |
Connection::~Connection() { | |
assert(stream_.recvfd < 0 && "garbage collecting open Connection"); | |
assert(stream_.sendfd < 0 && "garbage collecting open Connection"); | |
} | |
// V8 contstructor | |
Handle<Value> Connection::New(const Arguments& args) { | |
HandleScope scope; | |
// All constructors in node look similar to this. | |
// allocate the C++ object | |
Connection *connection = new Connection(); | |
// Use ObjectWrap::Wrap to assign it to the internal field in the V8 | |
// object. | |
connection->Wrap(args.This()); | |
return args.This(); | |
} | |
// Open a connection. Starts resolving the hostname in the libeio | |
// thread pool, when that completes in Connection::AfterResolve, actually | |
// open the connection. | |
Handle<Value> Connection::Connect(const Arguments& args) { | |
// Unwrap V8 object into C++ object | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.Holder()); | |
assert(connection); | |
HandleScope scope; | |
// If the connection is closed, reinitialize it. | |
if (connection->ReadyState() == EVCOM_CLOSED) { | |
connection->Init(); // in case we're reusing the socket | |
assert(connection->ReadyState() == EVCOM_INITIALIZED); | |
} | |
// If connect() is called on an open connection, raise an error. | |
if (connection->ReadyState() != EVCOM_INITIALIZED) { | |
Local<Value> exception = Exception::Error( | |
String::New("Socket is not in CLOSED state.")); | |
return ThrowException(exception); | |
} | |
assert(connection->stream_.recvfd < 0); | |
assert(connection->stream_.sendfd < 0); | |
// Make sure we have at least one argument (a port) | |
if (args.Length() == 0) { | |
Local<Value> exception = Exception::TypeError( | |
String::New("First argument must be a port number")); | |
return ThrowException(exception); | |
} | |
// We need the port as a string for getaddrinfo(). | |
// Thus cast it to a string. The strdup()'d value will be freed in | |
// Resolve(). | |
String::AsciiValue port_sv(args[0]->ToString()); | |
assert(connection->port_ == NULL); | |
connection->port_ = strdup(*port_sv); | |
// Get the host, if any is present. | |
assert(connection->host_ == NULL); | |
if (args.Length() > 1 && args[1]->IsString()) { | |
String::Utf8Value host_sv(args[1]->ToString()); | |
connection->host_ = strdup(*host_sv); | |
} | |
connection->resolving_ = true; // This is done so that readyState can return | |
// "opening" while getaddinfo() runs. | |
// There will not be any active watchers from this object on the event | |
// loop while getaddrinfo() runs. If the only thing happening in the | |
// script was this hostname resolution, then the event loop would drop | |
// out. Thus we need to add ev_ref() until AfterResolve(). | |
ev_ref(EV_DEFAULT_UC); | |
// Attach the object so it doesn't get garbage collected. | |
connection->Attach(); | |
// For the moment I will do DNS lookups in the eio thread pool. This is | |
// sub-optimal and cannot handle massive numbers of requests. | |
// In the future I will move to a system using adns or udns: | |
// http://lists.schmorp.de/pipermail/libev/2009q1/000632.html | |
eio_custom(Connection::Resolve, EIO_PRI_DEFAULT, Connection::AfterResolve, | |
connection); | |
return Undefined(); | |
} | |
// This function is executed in the thread pool. It cannot modify any | |
// members of the connection object. | |
int Connection::Resolve(eio_req *req) { | |
Connection *connection = static_cast<Connection*> (req->data); | |
struct addrinfo *address = NULL; | |
assert(connection->attached_); | |
assert(connection->resolving_); | |
req->result = getaddrinfo(connection->host_, connection->port_, | |
&client_tcp_hints, &address); | |
req->ptr2 = address; | |
free(connection->port_); | |
connection->port_ = NULL; | |
return 0; | |
} | |
static struct addrinfo * AddressDefaultToIPv4(struct addrinfo *address_list) { | |
struct addrinfo *address = NULL; | |
for (address = address_list; address != NULL; address = address->ai_next) { | |
if (address->ai_addr->sa_family == AF_INET) break; | |
} | |
return address == NULL ? address_list : address; | |
} | |
int Connection::AfterResolve(eio_req *req) { | |
ev_unref(EV_DEFAULT_UC); | |
Connection *connection = static_cast<Connection*> (req->data); | |
assert(connection->resolving_); | |
assert(connection->attached_); | |
struct addrinfo *address = NULL, | |
*address_list = static_cast<struct addrinfo *>(req->ptr2); | |
address = AddressDefaultToIPv4(address_list); | |
connection->resolving_ = false; | |
int r = 0; | |
if (req->result == 0) r = connection->Connect(address->ai_addr); | |
if (address_list) freeaddrinfo(address_list); | |
// no error. return. | |
if (req->result == 0) { | |
evcom_stream_attach(EV_DEFAULT_UC_ &connection->stream_); | |
goto out; | |
} | |
/* RESOLVE ERROR */ | |
/* TODO: the whole resolve process should be moved into evcom_stream. | |
* The fact that I'm modifying a read-only variable here should be | |
* good evidence of this. | |
*/ | |
connection->stream_.errorno = req->result; | |
connection->OnClose(); | |
connection->Detach(); | |
out: | |
return 0; | |
} | |
Handle<Value> Connection::SetEncoding(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This()); | |
assert(connection); | |
if (!args[0]->IsString()) { | |
connection->encoding_ = BINARY; | |
return scope.Close(BINARY_SYMBOL); | |
} | |
switch (ParseEncoding(args[0])) { | |
case ASCII: | |
connection->encoding_ = ASCII; | |
return scope.Close(ASCII_SYMBOL); | |
case UTF8: | |
connection->encoding_ = UTF8; | |
return scope.Close(UTF8_SYMBOL); | |
case BINARY: | |
connection->encoding_ = BINARY; | |
return scope.Close(BINARY_SYMBOL); | |
} | |
assert(0 && "this shouldn't happen"); | |
return ThrowException(Exception::Error( | |
String::New("Could not parse encoding. This is a Node bug."))); | |
} | |
#if EVCOM_HAVE_GNUTLS | |
Handle<Value> Connection::SetSecure(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This()); | |
assert(connection); | |
int r; | |
connection->secure_ = true; | |
// Create credentials | |
gnutls_certificate_allocate_credentials(&connection->credentials); | |
if (args[1]->IsString()) { | |
String::Utf8Value caString(args[1]->ToString()); | |
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*caString) | |
, caString.length() | |
}; | |
r = gnutls_certificate_set_x509_trust_mem(connection->credentials, | |
&datum, GNUTLS_X509_FMT_PEM); | |
} | |
if (args[2]->IsString()) { | |
String::Utf8Value crlString(args[2]->ToString()); | |
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*crlString) | |
, crlString.length() | |
}; | |
r = gnutls_certificate_set_x509_crl_mem(connection->credentials, | |
&datum, GNUTLS_X509_FMT_PEM); | |
} | |
if (args[3]->IsString() && args[4]->IsString()) { | |
String::Utf8Value keyString(args[3]->ToString()); | |
String::Utf8Value certString(args[4]->ToString()); | |
gnutls_datum_t datum_key = { reinterpret_cast<unsigned char*>(*keyString) | |
, keyString.length() | |
}; | |
gnutls_datum_t datum_cert = { reinterpret_cast<unsigned char*>(*certString) | |
, certString.length() | |
}; | |
r = gnutls_certificate_set_x509_key_mem(connection->credentials, | |
&datum_cert, &datum_key, | |
GNUTLS_X509_FMT_PEM); | |
} | |
// Create the session object | |
init_tls_session(&connection->stream_, | |
connection->credentials, | |
GNUTLS_CLIENT); | |
return Undefined(); | |
} | |
Handle<Value> Connection::VerifyPeer(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This()); | |
assert(connection); | |
const gnutls_datum_t * cert_chain; | |
uint cert_chain_length; | |
gnutls_x509_crl_t *crl_list; | |
uint crl_list_size; | |
gnutls_x509_crt_t *ca_list; | |
uint ca_list_size; | |
int r; | |
if (!connection->secure_) { | |
return Undefined(); | |
} | |
cert_chain = gnutls_certificate_get_peers(connection->stream_.session, | |
&cert_chain_length); | |
gnutls_certificate_get_x509_crls(connection->credentials, | |
&crl_list, | |
&crl_list_size); | |
gnutls_certificate_get_x509_cas(connection->credentials, | |
&ca_list, | |
&ca_list_size); | |
r = verify_certificate_chain(connection->stream_.session, | |
connection->host_, | |
cert_chain, | |
cert_chain_length, | |
crl_list, | |
crl_list_size, | |
ca_list, | |
ca_list_size); | |
return scope.Close(Integer::New(r)); | |
} | |
Handle<Value> Connection::GetPeerCertificate(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This()); | |
assert(connection); | |
if (!connection->secure_) { | |
return Undefined(); | |
} | |
const gnutls_datum_t * cert_chain; | |
uint cert_chain_length; | |
char *name; | |
size_t name_size; | |
gnutls_x509_crt_t cert; | |
cert_chain = gnutls_certificate_get_peers(connection->stream_.session, | |
&cert_chain_length); | |
if ( (cert_chain_length == 0) || (cert_chain == NULL) ) { | |
return Undefined(); | |
} | |
gnutls_x509_crt_init(&cert); | |
gnutls_x509_crt_import(cert, &cert_chain[0], GNUTLS_X509_FMT_DER); | |
gnutls_x509_crt_get_dn(cert, NULL, &name_size); | |
name = (char *)malloc(name_size); | |
gnutls_x509_crt_get_dn(cert, name, &name_size); | |
Local<String> dnString = String::New(name); | |
free(name); | |
gnutls_x509_crt_deinit(cert); | |
return scope.Close(dnString); | |
} | |
#endif | |
Handle<Value> Connection::ReadPause(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This()); | |
assert(connection); | |
connection->ReadPause(); | |
return Undefined(); | |
} | |
Handle<Value> Connection::ReadResume(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This()); | |
assert(connection); | |
connection->ReadResume(); | |
return Undefined(); | |
} | |
Handle<Value> Connection::SetTimeout(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.This()); | |
assert(connection); | |
float timeout = NODE_V8_UNIXTIME(args[0]); | |
connection->SetTimeout(timeout); | |
return Undefined(); | |
} | |
Handle<Value> Connection::Close(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.Holder()); | |
assert(connection); | |
connection->Close(); | |
if (connection->host_ != NULL) { | |
free(connection->host_); | |
connection->host_ = NULL; | |
} | |
return Undefined(); | |
} | |
Handle<Value> Connection::ForceClose(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.Holder()); | |
assert(connection); | |
connection->ForceClose(); | |
connection->Detach(); | |
return Undefined(); | |
} | |
Handle<Value> Connection::SetNoDelay(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.Holder()); | |
bool no_delay = true; | |
if (args.Length() > 0) { | |
no_delay = args[0]->IsTrue(); | |
} | |
connection->SetNoDelay(no_delay); | |
return Undefined(); | |
} | |
Handle<Value> Connection::Send(const Arguments& args) { | |
HandleScope scope; | |
Connection *connection = ObjectWrap::Unwrap<Connection>(args.Holder()); | |
assert(connection); | |
if (connection->ReadyState() != EVCOM_CONNECTED_RW && | |
connection->ReadyState() != EVCOM_CONNECTED_WO) { | |
Local<Value> exception = Exception::Error( | |
String::New("Socket is not open for writing")); | |
return ThrowException(exception); | |
} | |
enum encoding enc = ParseEncoding(args[1]); | |
ssize_t len = DecodeBytes(args[0], enc); | |
if (len < 0) { | |
Local<Value> exception = Exception::TypeError(String::New("Bad argument")); | |
return ThrowException(exception); | |
} | |
char * buf = new char[len]; | |
ssize_t written = DecodeWrite(buf, len, args[0], enc); | |
assert(written == len); | |
connection->Send(buf, written); | |
delete [] buf; | |
return scope.Close(Integer::New(written)); | |
} | |
void Connection::OnReceive(const void *buf, size_t len) { | |
HandleScope scope; | |
Local<Value> data = Encode(buf, len, encoding_); | |
Emit("receive", 1, &data); | |
} | |
void Connection::OnClose() { | |
HandleScope scope; | |
Handle<Value> argv[2] = { | |
stream_.errorno == 0 ? False() : True(), | |
String::New(strerror(stream_.errorno)) | |
}; | |
Emit("close", 2, argv); | |
} | |
void Connection::OnConnect() { | |
HandleScope scope; | |
if (stream_.server) { | |
Server *server = static_cast<Server*>(stream_.server->data); | |
Local<Value> value = Local<Value>::New(handle_); | |
server->Emit("connection", 1, &value); | |
} | |
Emit("connect", 0, NULL); | |
} | |
void Connection::OnTimeout() { | |
HandleScope scope; | |
Emit("timeout", 0, NULL); | |
} | |
void Connection::OnDrain() { | |
HandleScope scope; | |
Emit("drain", 0, NULL); | |
} | |
void Connection::OnEOF() { | |
HandleScope scope; | |
Emit("eof", 0, NULL); | |
} | |
Persistent<FunctionTemplate> Server::constructor_template; | |
void Server::Initialize(Handle<Object> target) { | |
HandleScope scope; | |
Local<FunctionTemplate> t = FunctionTemplate::New(New); | |
constructor_template = Persistent<FunctionTemplate>::New(t); | |
constructor_template->Inherit(EventEmitter::constructor_template); | |
constructor_template->InstanceTemplate()->SetInternalFieldCount(1); | |
constructor_template->SetClassName(String::NewSymbol("Server")); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "listen", Listen); | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "close", Close); | |
#if EVCOM_HAVE_GNUTLS | |
NODE_SET_PROTOTYPE_METHOD(constructor_template, "setSecure", SetSecure); | |
#endif | |
target->Set(String::NewSymbol("Server"), constructor_template->GetFunction()); | |
} | |
static Local<String> GetAddressString(struct sockaddr *addr) { | |
HandleScope scope; | |
char ip[INET6_ADDRSTRLEN]; | |
Local<String> remote_address; | |
if (addr->sa_family == AF_INET) { | |
struct sockaddr_in *sa = reinterpret_cast<struct sockaddr_in*>(addr); | |
inet_ntop(AF_INET, &(sa->sin_addr), ip, INET6_ADDRSTRLEN); | |
remote_address = String::New(ip); | |
} else if (addr->sa_family == AF_INET6) { | |
struct sockaddr_in6 *sa6 = reinterpret_cast<struct sockaddr_in6*>(addr); | |
inet_ntop(AF_INET6, &(sa6->sin6_addr), ip, INET6_ADDRSTRLEN); | |
remote_address = String::New(ip); | |
} else { | |
assert(0 && "received a bad sa_family"); | |
} | |
return scope.Close(remote_address); | |
} | |
Handle<FunctionTemplate> Server::GetConnectionTemplate() { | |
return Connection::constructor_template; | |
} | |
Connection* Server::UnwrapConnection(Local<Object> connection) { | |
HandleScope scope; | |
return ObjectWrap::Unwrap<Connection>(connection); | |
} | |
Connection* Server::OnConnection(struct sockaddr *addr) { | |
HandleScope scope; | |
TryCatch try_catch; | |
Local<Object> js_connection = | |
GetConnectionTemplate()->GetFunction()->NewInstance(0, NULL); | |
if (js_connection.IsEmpty()) { | |
FatalException(try_catch); | |
return NULL; | |
} | |
Local<String> remote_address = GetAddressString(addr); | |
js_connection->Set(REMOTE_ADDRESS_SYMBOL, remote_address); | |
js_connection->Set(SERVER_SYMBOL, handle_); | |
Connection *connection = UnwrapConnection(js_connection); | |
if (!connection) return NULL; | |
#if EVCOM_HAVE_GNUTLS | |
if (secure_) { | |
connection->secure_ = true; | |
connection->credentials = credentials; | |
init_tls_session(&connection->stream_, | |
connection->credentials, | |
GNUTLS_SERVER); | |
} | |
#endif | |
connection->Attach(); | |
return connection; | |
} | |
void Server::OnClose(int errorno) { | |
HandleScope scope; | |
Handle<Value> argv[1] = { Integer::New(errorno) }; | |
Emit("close", 1, argv); | |
} | |
Handle<Value> Server::New(const Arguments& args) { | |
HandleScope scope; | |
Server *server = new Server(); | |
server->Wrap(args.This()); | |
return args.This(); | |
} | |
Handle<Value> Server::Listen(const Arguments& args) { | |
Server *server = ObjectWrap::Unwrap<Server>(args.Holder()); | |
assert(server); | |
HandleScope scope; | |
if (args.Length() == 0) { | |
Local<Value> exception = Exception::TypeError( | |
String::New("First argument must be a port number")); | |
return ThrowException(exception); | |
} | |
String::AsciiValue port(args[0]->ToString()); | |
#ifndef DNS_MAXNAME | |
# define DNS_MAXNAME 1024 | |
#endif | |
char host[DNS_MAXNAME+1] = "\0"; | |
int backlog = 128; | |
if (args.Length() == 2) { | |
if (args[1]->IsInt32()) { | |
backlog = args[1]->Int32Value(); | |
} else if (args[1]->IsString()) { | |
args[1]->ToString()->WriteAscii(host, 0, DNS_MAXNAME+1); | |
} | |
} else if (args.Length() > 2) { | |
if (args[1]->IsString()) { | |
args[1]->ToString()->WriteAscii(host, 0, DNS_MAXNAME+1); | |
} | |
if (!args[2]->IsInt32()) { | |
return ThrowException( | |
Exception::TypeError(String::New("backlog must be an integer"))); | |
} | |
backlog = args[2]->Int32Value(); | |
} | |
// For servers call getaddrinfo inline. This is blocking but it shouldn't | |
// matter much. If someone actually complains then simply swap it out | |
// with a libeio call. | |
struct addrinfo * address = NULL, | |
* address_list = NULL; | |
int r = getaddrinfo(strlen(host) ? | |
host : NULL, *port, &server_tcp_hints, &address_list); | |
if (r != 0) { | |
Local<Value> exception = Exception::Error(String::New(strerror(errno))); | |
return ThrowException(exception); | |
} | |
address = AddressDefaultToIPv4(address_list); | |
int iport = server->Listen(address->ai_addr, backlog); | |
if (address_list) freeaddrinfo(address_list); | |
Handle<Value> argv[1] = { Integer::New(iport) }; | |
server->Emit("listening", 1, argv); | |
return Undefined(); | |
} | |
#if EVCOM_HAVE_GNUTLS | |
Handle<Value> Server::SetSecure(const Arguments& args) { | |
Server *server = ObjectWrap::Unwrap<Server>(args.Holder()); | |
assert(server); | |
int r; | |
server->secure_ = true; | |
gnutls_certificate_allocate_credentials(&server->credentials); | |
if (args[1]->IsString()) { | |
String::Utf8Value caString(args[1]->ToString()); | |
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*caString) | |
, caString.length() | |
}; | |
r = gnutls_certificate_set_x509_trust_mem(server->credentials, | |
&datum, GNUTLS_X509_FMT_PEM); | |
} | |
if (args[2]->IsString()) { | |
String::Utf8Value crlString(args[2]->ToString()); | |
gnutls_datum_t datum = { reinterpret_cast<unsigned char*>(*crlString) | |
, crlString.length() | |
}; | |
r = gnutls_certificate_set_x509_crl_mem(server->credentials, | |
&datum, GNUTLS_X509_FMT_PEM); | |
} | |
if (args[3]->IsString() && args[4]->IsString()) { | |
String::Utf8Value keyString(args[3]->ToString()); | |
String::Utf8Value certString(args[4]->ToString()); | |
gnutls_datum_t datum_key = { reinterpret_cast<unsigned char*>(*keyString) | |
, keyString.length() | |
}; | |
gnutls_datum_t datum_cert = { reinterpret_cast<unsigned char*>(*certString) | |
, certString.length() | |
}; | |
r = gnutls_certificate_set_x509_key_mem(server->credentials, | |
&datum_cert, &datum_key, | |
GNUTLS_X509_FMT_PEM); | |
} | |
return Undefined(); | |
} | |
#endif | |
Handle<Value> Server::Close(const Arguments& args) { | |
Server *server = ObjectWrap::Unwrap<Server>(args.Holder()); | |
assert(server); | |
server->Close(); | |
return Undefined(); | |
} | |
} // namespace node | |
#if EVCOM_HAVE_GNUTLS | |
void init_tls_session(evcom_stream* stream_, | |
gnutls_certificate_credentials_t credentials, | |
gnutls_connection_end_t session_type) { | |
gnutls_init(&stream_->session, | |
session_type); | |
if (session_type == GNUTLS_SERVER) { | |
gnutls_certificate_server_set_request(stream_->session, | |
GNUTLS_CERT_REQUEST); | |
} | |
gnutls_set_default_priority(stream_->session); | |
const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; | |
const int proto_type_priority[] = { GNUTLS_TLS1_0, | |
GNUTLS_TLS1_1, | |
GNUTLS_SSL3, | |
0}; | |
gnutls_certificate_type_set_priority(stream_->session, | |
cert_type_priority); | |
gnutls_protocol_set_priority(stream_->session, | |
proto_type_priority); | |
gnutls_credentials_set(stream_->session, | |
GNUTLS_CRD_CERTIFICATE, | |
credentials); | |
evcom_stream_set_secure_session(stream_, | |
stream_->session); | |
} | |
/* This function will try to verify the peer's certificate chain, and | |
* also check if the hostname matches, and the activation, expiration dates. | |
*/ | |
int verify_certificate_chain(gnutls_session_t session, | |
const char *hostname, | |
const gnutls_datum_t * cert_chain, | |
int cert_chain_length, | |
gnutls_x509_crl_t *crl_list, | |
int crl_list_size, | |
gnutls_x509_crt_t *ca_list, | |
int ca_list_size) { | |
int r = 0; | |
int i; | |
int ss = 0; | |
gnutls_x509_crt_t *cert; | |
if ((cert_chain_length == 0) || (cert_chain == NULL)) { | |
return JS_GNUTLS_CERT_UNDEFINED; | |
} | |
cert = (gnutls_x509_crt_t *)malloc(sizeof(*cert) * cert_chain_length); | |
/* Import all the certificates in the chain to | |
* native certificate format. | |
*/ | |
for (i = 0; i < cert_chain_length; i++) { | |
gnutls_x509_crt_init(&cert[i]); | |
gnutls_x509_crt_import(cert[i], &cert_chain[i], GNUTLS_X509_FMT_DER); | |
} | |
/* If the last certificate in the chain is self signed ignore it. | |
* That is because we want to check against our trusted certificate | |
* list. | |
*/ | |
if (gnutls_x509_crt_check_issuer(cert[cert_chain_length - 1], | |
cert[cert_chain_length - 1]) > 0 | |
&& cert_chain_length > 0) { | |
cert_chain_length--; | |
ss = 1; | |
} | |
/* Now verify the certificates against their issuers | |
* in the chain. | |
*/ | |
for (i = 1; i < cert_chain_length; i++) { | |
r = verify_cert2(cert[i - 1], cert[i], crl_list, crl_list_size); | |
if (r < 0) goto out; | |
} | |
/* Here we must verify the last certificate in the chain against | |
* our trusted CA list. | |
*/ | |
if (cert_chain_length>0) { | |
r = verify_last_cert(cert[cert_chain_length - 1], ca_list, ca_list_size, | |
crl_list, crl_list_size); | |
if (r < 0) goto out; | |
} else { | |
r = verify_last_cert(cert[0], ca_list, ca_list_size, | |
crl_list, crl_list_size); | |
if (r < 0) goto out; | |
} | |
/* Check if the name in the first certificate matches our destination! | |
*/ | |
if (hostname != NULL) { | |
if (!gnutls_x509_crt_check_hostname(cert[0], hostname)) { | |
r = JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME; | |
} | |
} | |
out: | |
for (i = 0; i < cert_chain_length+ss; i++) { | |
gnutls_x509_crt_deinit(cert[i]); | |
} | |
return r; | |
} | |
/* Verifies a certificate against an other certificate | |
* which is supposed to be it's issuer. Also checks the | |
* crl_list if the certificate is revoked. | |
*/ | |
int verify_cert2(gnutls_x509_crt_t crt, | |
gnutls_x509_crt_t issuer, | |
gnutls_x509_crl_t * crl_list, | |
int crl_list_size) { | |
unsigned int output; | |
int ret; | |
time_t now = time(0); | |
gnutls_x509_crt_verify(crt, &issuer, 1, 0, &output); | |
if (output & GNUTLS_CERT_INVALID) { | |
if (output & GNUTLS_CERT_SIGNER_NOT_FOUND) { | |
return JS_GNUTLS_CERT_SIGNER_NOT_FOUND; | |
} | |
if (output & GNUTLS_CERT_SIGNER_NOT_CA) { | |
return JS_GNUTLS_CERT_SIGNER_NOT_CA; | |
} | |
return JS_GNUTLS_CERT_SIGNER_NOT_CA; | |
} | |
/* Now check the expiration dates. | |
*/ | |
if (gnutls_x509_crt_get_activation_time(crt) > now) { | |
return JS_GNUTLS_CERT_NOT_ACTIVATED; | |
} | |
if (gnutls_x509_crt_get_expiration_time(crt) < now) { | |
return JS_GNUTLS_CERT_EXPIRED; | |
} | |
/* Check if the certificate is revoked. | |
*/ | |
ret = gnutls_x509_crt_check_revocation(crt, crl_list, crl_list_size); | |
if (ret == 1) { | |
return JS_GNUTLS_CERT_REVOKED; | |
} | |
return JS_GNUTLS_CERT_VALIDATED; | |
} | |
/* Verifies a certificate against our trusted CA list. | |
* Also checks the crl_list if the certificate is revoked. | |
*/ | |
int verify_last_cert(gnutls_x509_crt_t crt, | |
gnutls_x509_crt_t * ca_list, | |
int ca_list_size, | |
gnutls_x509_crl_t * crl_list, | |
int crl_list_size) { | |
unsigned int output; | |
int ret; | |
time_t now = time(0); | |
gnutls_x509_crt_verify(crt, ca_list, ca_list_size, | |
GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT, &output); | |
if (output & GNUTLS_CERT_INVALID) { | |
if (output & GNUTLS_CERT_SIGNER_NOT_CA) { | |
return JS_GNUTLS_CERT_SIGNER_NOT_CA; | |
} | |
return JS_GNUTLS_CERT_INVALID; | |
} | |
/* Now check the expiration dates. | |
*/ | |
if (gnutls_x509_crt_get_activation_time(crt) > now) { | |
return JS_GNUTLS_CERT_NOT_ACTIVATED; | |
} | |
if (gnutls_x509_crt_get_expiration_time(crt) < now) { | |
return JS_GNUTLS_CERT_EXPIRED; | |
} | |
/* Check if the certificate is revoked. | |
*/ | |
ret = gnutls_x509_crt_check_revocation(crt, crl_list, crl_list_size); | |
if (ret == 1) { | |
return JS_GNUTLS_CERT_REVOKED; | |
} | |
return JS_GNUTLS_CERT_VALIDATED; | |
} | |
#endif // EVCOM_HAVE_GNUTLS | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2009 Ryan Dahl <[email protected]> | |
#ifndef SRC_NET_H_ | |
#define SRC_NET_H_ | |
#include <node.h> | |
#include <node_events.h> | |
#include <v8.h> | |
#include <evcom.h> | |
#if EVCOM_HAVE_GNUTLS | |
#include <gnutls/gnutls.h> | |
#include <gnutls/x509.h> | |
#endif | |
namespace node { | |
class Server; | |
class Connection : public EventEmitter { | |
public: | |
static void Initialize(v8::Handle<v8::Object> target); | |
protected: | |
/* v8 interface */ | |
static v8::Persistent<v8::FunctionTemplate> constructor_template; | |
static v8::Handle<v8::Value> New(const v8::Arguments& args); | |
static v8::Handle<v8::Value> Connect(const v8::Arguments& args); | |
static v8::Handle<v8::Value> Send(const v8::Arguments& args); | |
static v8::Handle<v8::Value> SendUtf8(const v8::Arguments& args); | |
static v8::Handle<v8::Value> Close(const v8::Arguments& args); | |
static v8::Handle<v8::Value> ForceClose(const v8::Arguments& args); | |
static v8::Handle<v8::Value> SetEncoding(const v8::Arguments& args); | |
static v8::Handle<v8::Value> ReadPause(const v8::Arguments& args); | |
static v8::Handle<v8::Value> ReadResume(const v8::Arguments& args); | |
static v8::Handle<v8::Value> SetTimeout(const v8::Arguments& args); | |
static v8::Handle<v8::Value> SetNoDelay(const v8::Arguments& args); | |
static v8::Handle<v8::Value> ReadyStateGetter(v8::Local<v8::String> _, | |
const v8::AccessorInfo& info); | |
static v8::Handle<v8::Value> FDGetter(v8::Local<v8::String> _, | |
const v8::AccessorInfo& info); | |
#if EVCOM_HAVE_GNUTLS | |
static v8::Handle<v8::Value> SetSecure(const v8::Arguments& args); | |
static v8::Handle<v8::Value> VerifyPeer(const v8::Arguments& args); | |
static v8::Handle<v8::Value> GetPeerCertificate(const v8::Arguments& args); | |
#endif | |
Connection() : EventEmitter() { | |
encoding_ = BINARY; | |
host_ = NULL; | |
port_ = NULL; | |
Init(); | |
} | |
virtual ~Connection(); | |
int Connect(struct sockaddr *address) { | |
return evcom_stream_connect(&stream_, address); | |
} | |
void Send(const char *buf, size_t len) { | |
evcom_stream_write(&stream_, buf, len); | |
} | |
void Close() { | |
evcom_stream_close(&stream_); | |
} | |
void ForceClose() { | |
evcom_stream_force_close(&stream_); | |
} | |
void ReadPause() { | |
evcom_stream_read_pause(&stream_); | |
} | |
void ReadResume() { | |
evcom_stream_read_resume(&stream_); | |
} | |
void SetTimeout(float timeout) { | |
evcom_stream_reset_timeout(&stream_, timeout); | |
} | |
void SetNoDelay(bool no_delay) { | |
evcom_stream_set_no_delay(&stream_, no_delay); | |
} | |
virtual void OnConnect(); | |
virtual void OnReceive(const void *buf, size_t len); | |
virtual void OnEOF(); | |
virtual void OnClose(); | |
virtual void OnTimeout(); | |
virtual void OnDrain(); | |
v8::Local<v8::Object> GetProtocol(); | |
enum evcom_stream_state ReadyState() { | |
return evcom_stream_state(&stream_); | |
} | |
enum encoding encoding_; | |
bool resolving_; | |
bool secure_; | |
#if EVCOM_HAVE_GNUTLS | |
gnutls_certificate_credentials_t credentials; | |
#endif | |
private: | |
/* liboi callbacks */ | |
static void on_connect(evcom_stream *s) { | |
Connection *connection = static_cast<Connection*>(s->data); | |
connection->OnConnect(); | |
} | |
static void on_read(evcom_stream *s, const void *buf, size_t len) { | |
Connection *connection = static_cast<Connection*>(s->data); | |
assert(connection->attached_); | |
if (len == 0) | |
connection->OnEOF(); | |
else | |
connection->OnReceive(buf, len); | |
} | |
static void on_close(evcom_stream *s) { | |
Connection *connection = static_cast<Connection*>(s->data); | |
evcom_stream_detach(s); | |
assert(connection->stream_.recvfd < 0); | |
assert(connection->stream_.sendfd < 0); | |
#if EVCOM_HAVE_GNUTLS | |
if (connection->secure_) { | |
if (connection->stream_.session) { | |
gnutls_deinit(connection->stream_.session); | |
connection->stream_.session = NULL; | |
} | |
if (!connection->stream_.server && connection->credentials) { | |
gnutls_certificate_free_credentials(connection->credentials); | |
connection->credentials = NULL; | |
} | |
} | |
#endif | |
connection->OnClose(); | |
assert(connection->attached_); | |
connection->Detach(); | |
} | |
static void on_timeout(evcom_stream *s) { | |
Connection *connection = static_cast<Connection*>(s->data); | |
connection->OnTimeout(); | |
} | |
static void on_drain(evcom_stream *s) { | |
Connection *connection = static_cast<Connection*>(s->data); | |
connection->OnDrain(); | |
} | |
void Init(); // constructor helper. | |
static int Resolve(eio_req *req); | |
static int AfterResolve(eio_req *req); | |
char *host_; | |
char *port_; | |
evcom_stream stream_; | |
friend class Server; | |
}; | |
class Server : public EventEmitter { | |
public: | |
static void Initialize(v8::Handle<v8::Object> target); | |
protected: | |
static v8::Persistent<v8::FunctionTemplate> constructor_template; | |
static v8::Handle<v8::Value> New(const v8::Arguments& args); | |
static v8::Handle<v8::Value> Listen(const v8::Arguments& args); | |
static v8::Handle<v8::Value> Close(const v8::Arguments& args); | |
#if EVCOM_HAVE_GNUTLS | |
static v8::Handle<v8::Value> SetSecure(const v8::Arguments& args); | |
#endif | |
Server() : EventEmitter() { | |
evcom_server_init(&server_); | |
server_.on_connection = Server::on_connection; | |
server_.on_close = Server::on_close; | |
server_.data = this; | |
secure_ = false; | |
} | |
virtual ~Server() { | |
assert(server_.fd >= 0); | |
} | |
int Listen(struct sockaddr *address, int backlog) { | |
struct sockaddr_in socket_addr; | |
socklen_t socket_addr_size = sizeof(socket_addr); | |
int r = evcom_server_listen(&server_, address, backlog); | |
if (r != 0) return r; | |
evcom_server_attach(EV_DEFAULT_ &server_); | |
Attach(); | |
if (getsockname(server_.fd,(struct sockaddr *)&socket_addr,&socket_addr_size) < 0){ | |
return -1; | |
} | |
int iport = ntohs(socket_addr.sin_port); | |
return iport; | |
} | |
void Close() { | |
evcom_server_close(&server_); | |
} | |
virtual v8::Handle<v8::FunctionTemplate> GetConnectionTemplate(); | |
virtual Connection* UnwrapConnection(v8::Local<v8::Object> connection); | |
private: | |
Connection* OnConnection(struct sockaddr *addr); | |
static evcom_stream* on_connection(evcom_server *s, struct sockaddr *addr) { | |
Server *server = static_cast<Server*>(s->data); | |
Connection *connection = server->OnConnection(addr); | |
return &connection->stream_; | |
} | |
void OnClose(int errorno); | |
static void on_close(evcom_server *s) { | |
Server *server = static_cast<Server*>(s->data); | |
evcom_server_detach(s); | |
server->OnClose(s->errorno); | |
server->Detach(); | |
} | |
evcom_server server_; | |
#if EVCOM_HAVE_GNUTLS | |
gnutls_certificate_credentials_t credentials; | |
#endif | |
bool secure_; | |
}; | |
} // namespace node | |
#endif // SRC_NET_H_ | |
#if EVCOM_HAVE_GNUTLS | |
void init_tls_session(evcom_stream* stream_, | |
gnutls_certificate_credentials_t credentials, | |
gnutls_connection_end_t session_type); | |
int verify_certificate_chain(gnutls_session_t session, | |
const char *hostname, | |
const gnutls_datum_t * cert_chain, | |
int cert_chain_length, | |
gnutls_x509_crl_t *crl_list, | |
int crl_list_size, | |
gnutls_x509_crt_t *ca_list, | |
int ca_list_size); | |
int verify_cert2(gnutls_x509_crt_t crt, | |
gnutls_x509_crt_t issuer, | |
gnutls_x509_crl_t * crl_list, | |
int crl_list_size); | |
int verify_last_cert(gnutls_x509_crt_t crt, | |
gnutls_x509_crt_t * ca_list, | |
int ca_list_size, | |
gnutls_x509_crl_t * crl_list, | |
int crl_list_size); | |
#define JS_GNUTLS_CERT_VALIDATED 1 | |
#define JS_GNUTLS_CERT_UNDEFINED 0 | |
#define JS_GNUTLS_CERT_SIGNER_NOT_FOUND -100 | |
#define JS_GNUTLS_CERT_SIGNER_NOT_CA -101 | |
#define JS_GNUTLS_CERT_INVALID -102 | |
#define JS_GNUTLS_CERT_NOT_ACTIVATED -103 | |
#define JS_GNUTLS_CERT_EXPIRED -104 | |
#define JS_GNUTLS_CERT_REVOKED -105 | |
#define JS_GNUTLS_CERT_DOES_NOT_MATCH_HOSTNAME -106 | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment