Initial commit

This commit is contained in:
Allan Odgaard
2012-08-09 16:25:56 +02:00
commit 9894969e67
1103 changed files with 215580 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
#include "constants.h"
std::string const kHTTPSignatureHeader = "x-amz-meta-x-signature";
std::string const kHTTPSigneeHeader = "x-amz-meta-x-signee";

View File

@@ -0,0 +1,9 @@
#ifndef NETWORK_CONSTANTS_H_R7DVBN2M
#define NETWORK_CONSTANTS_H_R7DVBN2M
#include <oak/misc.h>
PUBLIC extern std::string const kHTTPSignatureHeader;
PUBLIC extern std::string const kHTTPSigneeHeader;
#endif /* end of include guard: NETWORK_CONSTANTS_H_R7DVBN2M */

View File

@@ -0,0 +1,220 @@
#include "download.h"
#include "proxy.h"
#include "user_agent.h"
#include <cf/cf.h>
#include <text/format.h>
#include <text/case.h>
#include <text/hexdump.h>
#include <io/path.h>
#include <oak/debug.h>
OAK_DEBUG_VAR(Network_Download);
namespace network
{
static const char kCRLF[] = "\r\n";
// =============
// = request_t =
// =============
request_t::request_t (std::string const& url, filter_t* firstFilter, ...) : _url(url)
{
va_list ap;
va_start(ap, firstFilter);
for(; firstFilter; firstFilter = va_arg(ap, filter_t*))
_filters.push_back(firstFilter);
va_end(ap);
}
request_t& request_t::add_filter (filter_t* filter) { _filters.push_back(filter); return *this; }
request_t& request_t::set_user_agent (std::string const& user_agent) { _user_agent = user_agent; return *this; }
request_t& request_t::set_entity_tag (std::string const& entity_tag) { _entity_tag = entity_tag; return *this; }
request_t& request_t::watch_stop_flag (bool const* stopFlag) { _stop_flag = stopFlag; return *this; }
request_t& request_t::update_progress_variable (double* percentDone, double min, double max)
{
_progress = percentDone;
_progress_min = min;
_progress_max = max;
return *this;
}
// ============
// = Download =
// ============
namespace
{
struct user_data_t
{
user_data_t (request_t const& request) : request(request) { }
request_t const& request;
bool receiving_body = false;
std::string error = NULL_STR;
};
}
int receive_progress (void* udata, double dltotal, double dlnow, double ultotal, double ulnow)
{
user_data_t& userData = *((user_data_t*)udata);
if(userData.request._progress && userData.receiving_body)
*userData.request._progress = userData.request._progress_min + (userData.request._progress_max - userData.request._progress_min) * (dltotal ? dlnow / dltotal : 0);
return userData.request._stop_flag ? *userData.request._stop_flag : false;
}
size_t receive_header (void* ptr, size_t size, size_t nmemb, void* udata)
{
user_data_t& userData = *((user_data_t*)udata);
char const* bytes = (char const*)ptr;
size_t len = nmemb * size;
if(len > 5 && strncmp("HTTP/", bytes, 5) == 0 && std::find(bytes, bytes + len, ':') == bytes + len)
{
D(DBF_Network_Download, bug("New Response: %.*s", (int)len, bytes););
if(len > 12 && strncmp("HTTP/1", bytes, 6) == 0 && bytes[9] == '2')
userData.receiving_body = true;
char const* first = (const char*)ptr;
char const* last = std::search(first, first + size * nmemb, &kCRLF[0], &kCRLF[2]);
iterate(filter, userData.request._filters)
{
if(!(*filter)->receive_status(std::string(first, last)))
{
userData.error = text::format("%s: receiving status", (*filter)->name().c_str());
return 0;
}
}
}
else if(len == 2 && strncmp("\r\n", bytes, 2) == 0)
{
D(DBF_Network_Download, bug("End of Response\n"););
}
else
{
while(len && (bytes[len-1] == '\r' || bytes[len-1] == '\n'))
--len;
size_t keyLast = std::find(bytes, bytes + len, ':') - bytes;
size_t valueFirst = keyLast;
while(valueFirst < len && bytes[++valueFirst] == ' ')
;
if(valueFirst != len)
{
iterate(filter, userData.request._filters)
{
if(!(*filter)->receive_header(text::lowercase(std::string(bytes, bytes + keyLast)), std::string(bytes + valueFirst, bytes + len)))
{
userData.error = text::format("%s: receiving header", (*filter)->name().c_str());
return 0;
}
}
}
}
return size * nmemb;
}
size_t receive_data (void* ptr, size_t size, size_t nmemb, void* udata)
{
user_data_t& userData = *((user_data_t*)udata);
iterate(filter, userData.request._filters)
{
if(!(*filter)->receive_data((const char*)ptr, size * nmemb))
{
userData.error = text::format("%s: receiving data", (*filter)->name().c_str());
return 0;
}
}
return size * nmemb;
}
long download (request_t const& request, std::string* error)
{
iterate(filter, request._filters)
{
if(!(*filter)->setup())
{
if(error)
*error = text::format("%s: setup", (*filter)->name().c_str());
return 0;
}
}
long res = 0;
if(CURL* handle = curl_easy_init())
{
curl_easy_setopt(handle, CURLOPT_URL, request._url.c_str());
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(handle, CURLOPT_FAILONERROR, true);
curl_easy_setopt(handle, CURLOPT_ENCODING, "");
std::string const userAgent = request._user_agent == NULL_STR ? create_agent_info_string() : request._user_agent;
curl_easy_setopt(handle, CURLOPT_USERAGENT, userAgent.c_str());
char errorbuf[CURL_ERROR_SIZE];
curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorbuf);
user_data_t userData(request);
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &receive_data);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &userData);
curl_easy_setopt(handle, CURLOPT_PROGRESSFUNCTION, &receive_progress);
curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, &userData);
curl_easy_setopt(handle, CURLOPT_NOPROGRESS, false);
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, &receive_header);
curl_easy_setopt(handle, CURLOPT_HEADERDATA, &userData);
struct curl_slist* headers = NULL;
if(request._entity_tag != NULL_STR)
{
headers = curl_slist_append(headers, text::format("If-None-Match: %.*s", (int)request._entity_tag.size(), request._entity_tag.data()).c_str());
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
}
if(auto proxySettings = get_proxy_settings())
{
curl_easy_setopt(handle, CURLOPT_PROXY, proxySettings.server.c_str());
curl_easy_setopt(handle, CURLOPT_PROXYPORT, proxySettings.port);
if(proxySettings.password != NULL_STR)
curl_easy_setopt(handle, CURLOPT_PROXYUSERPWD, (proxySettings.user + ":" + proxySettings.password).c_str());
}
CURLcode rc = curl_easy_perform(handle);
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res);
curl_easy_cleanup(handle);
curl_slist_free_all(headers);
if(rc == 0)
{
if(res == 304) // not modified so ignore filter errors
{
iterate(filter, request._filters)
(*filter)->receive_end(userData.error);
}
else
{
iterate(filter, request._filters)
{
if(!(*filter)->receive_end(userData.error))
{
if(error)
*error = userData.error;
return 0;
}
}
}
}
else
{
if(error)
*error = errorbuf;
}
}
return res;
}
} /* network */

View File

@@ -0,0 +1,49 @@
#ifndef DOWNLOAD_H_S5YBYD2
#define DOWNLOAD_H_S5YBYD2
#include <oak/oak.h>
struct PUBLIC filter_t
{
virtual ~filter_t () { }
virtual bool setup () { return true; }
virtual bool receive_status (std::string const& statusString) { return true; }
virtual bool receive_header (std::string const& header, std::string const& value) { return true; }
virtual bool receive_data (char const* bytes, size_t len) { return true; }
virtual bool receive_end (std::string& error) { return true; }
virtual std::string name () { return "filter"; }
};
namespace network
{
struct PUBLIC request_t
{
request_t (std::string const& url, filter_t* firstFilter = NULL, ...);
request_t& add_filter (filter_t* filter);
request_t& set_user_agent (std::string const& user_agent);
request_t& set_entity_tag (std::string const& entity_tag);
request_t& watch_stop_flag (bool const* stopFlag);
request_t& update_progress_variable (double* percentDone, double min = 0, double max = 1);
private:
friend long download (request_t const& request, std::string* error);
friend int receive_progress (void* udata, double dltotal, double dlnow, double ultotal, double ulnow);
friend size_t receive_header (void* ptr, size_t size, size_t nmemb, void* udata);
friend size_t receive_data (void* ptr, size_t size, size_t nmemb, void* udata);
std::string _url;
std::vector<filter_t*> _filters;
std::string _user_agent = NULL_STR;
std::string _entity_tag = NULL_STR;
bool const* _stop_flag = NULL;
double* _progress = NULL;
double _progress_min = 0;
double _progress_max = 0;
};
PUBLIC long download (request_t const& request, std::string* error);
} /* net */
#endif /* end of include guard: DOWNLOAD_NG_H_S5YBYD2 */

View File

@@ -0,0 +1,233 @@
#include "download_tbz.h"
#include "filter_check_signature.h"
#include "constants.h"
#include "user_agent.h"
#include "proxy.h"
#include "tbz.h"
#include <io/path.h>
#include <io/swap_file_data.h>
#include <io/move_path.h>
#include <text/case.h>
#include <text/decode.h>
#include <text/format.h>
namespace network
{
static std::string const kHTTPEntityTagAttribute = "org.w3.http.etag";
namespace
{
struct user_data_t
{
user_data_t (key_chain_t const& keychain, double* progress, double start_progress, double stop_progress, bool const* stop_flag, int tbz_fd, int tmp_fd) : progress(progress), start_progress(start_progress), stop_progress(stop_progress), stop_flag(stop_flag), tbz_fd(tbz_fd), tmp_fd(tmp_fd), verify_signature(keychain, kHTTPSigneeHeader, kHTTPSignatureHeader)
{
verify_signature.setup();
}
void receive (size_t len)
{
received += len;
if(progress)
*progress = start_progress + (stop_progress - start_progress) * (total ? received / (double)total : 0);
}
bool should_stop () const
{
return stop_flag && *stop_flag;
}
bool modified = true;
double* progress;
double start_progress;
double stop_progress;
bool const* stop_flag;
std::string etag = NULL_STR;
int tbz_fd;
int tmp_fd;
network::check_signature_t verify_signature;
size_t received = 0;
size_t total = 0;
};
static size_t receive_header (void* ptr, size_t size, size_t nmemb, void* udata)
{
user_data_t& data = *((user_data_t*)udata);
char const* bytes = (char const*)ptr;
size_t len = nmemb * size;
if(len > 7 && strncmp("HTTP/1.", bytes, 7) == 0 && std::find(bytes, bytes + len, ':') == bytes + len)
{
static const char kCRLF[] = "\r\n";
char const* last = std::search(bytes, bytes + size * nmemb, &kCRLF[0], &kCRLF[2]);
if(std::string(bytes, last).find("HTTP/1.1 304") == 0)
data.modified = false;
}
else if(len != 2 || strncmp("\r\n", bytes, 2) != 0)
{
while(len && (bytes[len-1] == '\r' || bytes[len-1] == '\n'))
--len;
size_t keyLast = std::find(bytes, bytes + len, ':') - bytes;
size_t valueFirst = keyLast;
while(valueFirst < len && bytes[++valueFirst] == ' ')
;
if(valueFirst != len)
{
std::string header = text::lowercase(std::string(bytes, bytes + keyLast));
std::string value(bytes + valueFirst, bytes + len);
if(header == "etag")
data.etag = value;
else if(header == "content-length")
data.total = strtol(value.c_str(), NULL, 10);
else if(header == kHTTPSigneeHeader || header == kHTTPSignatureHeader)
data.verify_signature.receive_header(header, value);
}
}
return data.should_stop() ? 0 : size * nmemb;
}
static size_t receive_data (void* ptr, size_t size, size_t nmemb, void* udata)
{
user_data_t& data = *((user_data_t*)udata);
write(data.tbz_fd, ptr, size * nmemb);
write(data.tmp_fd, ptr, size * nmemb);
data.verify_signature.receive_data((char const*)ptr, size * nmemb);
data.receive(size * nmemb);
return data.should_stop() ? 0 : size * nmemb;
}
}
std::string download_tbz (std::string const& url, key_chain_t const& keyChain, std::string const& destination, std::string& error, double* progress, double progressStart, double progressStop, bool const* stopFlag)
{
std::string res = NULL_STR;
if(CURL* handle = curl_easy_init())
{
std::string tbzDestination = path::temp("dl_archive_contents");
mkdir(tbzDestination.c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH);
int tbzInput, tbzOutput;
pid_t tbzPid = launch_tbz(tbzDestination, tbzInput, tbzOutput, error);
std::string tmpPath = path::temp("dl_bytes");
int tmpInput = open(tmpPath.c_str(), O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH);
// ========
// = Curl =
// ========
user_data_t data(keyChain, progress, progressStart, progressStop, stopFlag, tbzInput, tmpInput);
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(handle, CURLOPT_FAILONERROR, true);
curl_easy_setopt(handle, CURLOPT_ENCODING, "");
curl_easy_setopt(handle, CURLOPT_USERAGENT, create_agent_info_string().c_str());
char errorbuf[CURL_ERROR_SIZE];
curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorbuf);
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &receive_data);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &data);
curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, &receive_header);
curl_easy_setopt(handle, CURLOPT_HEADERDATA, &data);
struct curl_slist* headers = NULL;
std::string const etag = path::get_attr(destination, kHTTPEntityTagAttribute);
if(etag != NULL_STR)
{
headers = curl_slist_append(headers, text::format("If-None-Match: %.*s", (int)etag.size(), etag.data()).c_str());
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
}
if(auto proxySettings = get_proxy_settings())
{
curl_easy_setopt(handle, CURLOPT_PROXY, proxySettings.server.c_str());
curl_easy_setopt(handle, CURLOPT_PROXYPORT, proxySettings.port);
if(proxySettings.password != NULL_STR)
curl_easy_setopt(handle, CURLOPT_PROXYUSERPWD, (proxySettings.user + ":" + proxySettings.password).c_str());
}
long serverReply = 0;
switch(curl_easy_perform(handle))
{
case 0: curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &serverReply); break;
case CURLE_WRITE_ERROR: error = (stopFlag && *stopFlag ? "Download stopped." : errorbuf); break;
default: error = errorbuf; break;
}
curl_easy_cleanup(handle);
curl_slist_free_all(headers);
// =============
// = Post Curl =
// =============
close(tmpInput);
bool goodSignature = false;
if(serverReply == 200)
{
if(data.verify_signature.receive_end(error))
{
goodSignature = true;
if(path::swap_and_unlink(tmpPath, destination))
{
path::set_attr(destination, kHTTPEntityTagAttribute, data.etag);
path::set_attr(destination, kHTTPSigneeHeader, data.verify_signature.signee());
path::set_attr(destination, kHTTPSignatureHeader, data.verify_signature.signature());
}
else
{
fprintf(stderr, "error with swap_and_unlink: %s → %s\n", tmpPath.c_str(), destination.c_str());
}
}
}
else if(serverReply == 304)
{
struct stat buf;
int fd = open(destination.c_str(), O_RDONLY);
if(fd != -1 && fstat(fd, &buf) != -1)
{
char bytes[4096];
data.total = buf.st_size;
while(data.received < data.total && !data.should_stop())
{
ssize_t len = read(fd, bytes, sizeof(bytes));
if(len == -1)
break;
write(tbzInput, bytes, len);
data.receive(len);
}
close(fd);
}
}
else if(serverReply != 0)
{
error = text::format("Unexpected server reply (%ld).", serverReply);
}
unlink(tmpPath.c_str());
if(finish_tbz(tbzPid, tbzInput, tbzOutput, error))
{
if(serverReply == 304 || goodSignature)
res = tbzDestination;
}
if(res == NULL_STR)
path::remove(tbzDestination);
}
return res;
}
} /* network */

View File

@@ -0,0 +1,13 @@
#ifndef NETWORK_DOWNLOAD_TBZ_H_MYCOASC1
#define NETWORK_DOWNLOAD_TBZ_H_MYCOASC1
#include "key_chain.h"
#include <oak/misc.h>
namespace network
{
PUBLIC std::string download_tbz (std::string const& url, key_chain_t const& keyChain, std::string const& destination, std::string& error, double* progress, double progressStart = 0, double progressStop = 1, bool const* stopFlag = NULL);
} /* network */
#endif /* end of include guard: NETWORK_DOWNLOAD_TBZ_H_MYCOASC1 */

View File

@@ -0,0 +1,59 @@
#include "filter_check_signature.h"
#include <text/decode.h>
#include <text/format.h>
#include <oak/debug.h>
namespace network
{
check_signature_t::check_signature_t (key_chain_t const& keyChain, std::string const& signeeHeader, std::string const& signatureHeader) : _key_chain(keyChain), _signee_header(signeeHeader), _signature_header(signatureHeader)
{
}
bool check_signature_t::setup ()
{
return EVP_VerifyInit(&ctx, EVP_dss1()) == 1;
}
bool check_signature_t::receive_header (std::string const& header, std::string const& value)
{
if(header == _signee_header)
_signee = value;
else if(header == _signature_header)
_signature = value;
return true;
}
bool check_signature_t::receive_data (char const* buf, size_t len)
{
return EVP_VerifyUpdate(&ctx, buf, len) == 1;
}
bool check_signature_t::receive_end (std::string& error)
{
if(_signee == NULL_STR)
return (error = "Missing signee."), false;
if(_signature == NULL_STR)
return (error = "Missing signature."), false;
if(key_chain_t::key_ptr key = _key_chain.find(_signee))
{
std::string signature = decode::base64(_signature);
if(EVP_VerifyFinal(&ctx, (unsigned char*)&signature[0], signature.size(), *key) == 1)
return true;
error = text::format("Bad signature.");
}
else
{
error = text::format("Unknown signee: %s.", _signee.c_str());
}
return false;
}
std::string check_signature_t::name ()
{
return "signature";
}
} /* network */

View File

@@ -0,0 +1,40 @@
#ifndef PUBKEY_H_VC2ABIZU
#define PUBKEY_H_VC2ABIZU
#include "download.h" // filter_t
#include "key_chain.h"
// ======================
// = Validate signature =
// ======================
namespace network
{
struct PUBLIC check_signature_t : filter_t
{
check_signature_t (key_chain_t const& keyChain, std::string const& signeeHeader, std::string const& signatureHeader);
bool setup ();
bool receive_header (std::string const& header, std::string const& value);
bool receive_data (char const* buf, size_t len);
bool receive_end (std::string& error);
std::string name ();
std::string const& signee () const { return _signee_header; }
std::string const& signature () const { return _signature_header; }
private:
key_chain_t const _key_chain;
std::string const _signee_header;
std::string const _signature_header;
EVP_MD_CTX ctx;
std::string _signee = NULL_STR;
std::string _signature = NULL_STR;
};
} /* network */
#endif /* end of include guard: PUBKEY_H_VC2ABIZU */

View File

@@ -0,0 +1,18 @@
#ifndef FILTER_ETAG_H_TKYP6FQQ
#define FILTER_ETAG_H_TKYP6FQQ
#include "download.h" // filter_t
struct etag_t : filter_t
{
bool receive_header (std::string const& header, std::string const& value)
{
if(header == "etag")
etag = value;
return true;
}
std::string etag = NULL_STR;
};
#endif /* end of include guard: FILTER_ETAG_H_TKYP6FQQ */

View File

@@ -0,0 +1,58 @@
#ifndef ARCHIVE_H_XHCVW91K
#define ARCHIVE_H_XHCVW91K
#include "download.h" // filter_t
#include <io/io.h>
#include <OakSystem/application.h>
#include <text/format.h>
namespace network
{
struct save_t : filter_t
{
save_t (bool cleanup = true) : _cleanup(cleanup)
{
path = path::temp("dl_save_filter");
}
~save_t ()
{
if(_fp)
fclose(_fp);
if(_cleanup)
path::remove(path);
}
bool setup ()
{
return _fp = fopen(path.c_str(), "w");
}
bool receive_data (char const* bytes, size_t len)
{
return fwrite(bytes, 1, len, _fp) == len;
}
bool receive_end (std::string& error)
{
bool res = fclose(_fp) == 0;
_fp = NULL;
return res;
}
std::string name ()
{
return "i/o";
}
std::string path;
private:
FILE* _fp = NULL;
bool _cleanup;
};
} /* network */
#endif /* end of include guard: ARCHIVE_H_XHCVW91K */

View File

@@ -0,0 +1,115 @@
#ifndef EXTRACT_H_L6SJUZHG
#define EXTRACT_H_L6SJUZHG
#include "download.h" // filter_t
#include <io/io.h>
#include <OakSystem/application.h>
#include <OakSystem/process.h>
#include <text/format.h>
#include <text/trim.h>
// =======================
// = Extract tbz archive =
// =======================
namespace network
{
struct tbz_t : filter_t
{
bool setup ()
{
path = path::temp("dl_tbz_filter");
if(mkdir(path.c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH) == 0)
return launch_tar();
return false;
}
bool receive_data (char const* buf, size_t len)
{
return write(input[1], buf, len) == len;
}
bool receive_end (std::string& error)
{
close(input[1]);
std::string tarOutput;
ssize_t len;
char buf[512];
while((len = read(output[0], buf, sizeof(buf))) > 0)
tarOutput.insert(tarOutput.end(), buf, buf + len);
close(output[0]);
int status = 0;
if(waitpid(pid, &status, 0) == pid && WIFEXITED(status))
{
if(WEXITSTATUS(status) == 0 && tarOutput.empty())
return true;
error = text::format("Corrupt archive.");
fprintf(stderr, "tar exit status %d\n%s\n", WEXITSTATUS(status), text::trim(tarOutput).c_str());
}
else
{
error = text::format("Abnormal exit from tar (%d).", status);
}
return false;
}
std::string name ()
{
return "tar";
}
std::string path = NULL_STR;
private:
static char const* strip_components_flag ()
{
SInt32 osVersion = 0;
Gestalt(gestaltSystemVersion, &osVersion);
return osVersion >= 0x1050 ? "--strip-components" : "--strip-path";
}
bool launch_tar ()
{
signal(SIGPIPE, SIG_IGN);
pipe(&input[0]);
pipe(&output[0]);
char const* argv[] = { "/usr/bin/tar", "-jxmkC", path.c_str(), strip_components_flag(), "1", NULL };
oak::c_array env(oak::basic_environment());
pid = vfork();
if(pid == 0)
{
close(0); close(1); close(2);
dup(input[0]); dup(output[1]); dup(output[1]);
close(input[0]); close(input[1]); close(output[0]); close(output[1]);
signal(SIGPIPE, SIG_DFL);
execve(argv[0], (char* const*)argv, env);
_exit(-1);
}
else if(pid == -1)
{
return false;
}
else
{
close(input[0]);
close(output[1]);
fcntl(input[1], F_SETFD, 1);
fcntl(output[0], F_SETFD, 1);
}
return true;
}
int input[2];
int output[2];
pid_t pid;
};
} /* network */
#endif /* end of include guard: EXTRACT_H_L6SJUZHG */

View File

@@ -0,0 +1,137 @@
#include "key_chain.h"
#include <oak/oak.h>
#include <plist/plist.h>
key_chain_t::key_t::key_t (std::string const& identity, std::string const& name, std::string const& key_data) : _identity(identity), _name(name), _key_data(key_data)
{
init();
}
key_chain_t::key_t::key_t (key_t const& rhs) : _identity(rhs._identity), _name(rhs._name), _key_data(rhs._key_data)
{
init();
}
key_chain_t::key_t::~key_t ()
{
cleanup();
}
void key_chain_t::key_t::init () const
{
_ssl_key = NULL;
_ssl_bio = NULL;
_ssl_data = NULL;
}
bool key_chain_t::key_t::setup () const
{
if(_ssl_key)
return true;
bool res = false;
if(_ssl_key = EVP_PKEY_new())
{
if(_ssl_bio = BIO_new_mem_buf((char*)_key_data.data(), _key_data.size()))
{
if(_ssl_data = PEM_read_bio_DSA_PUBKEY(_ssl_bio, NULL, NULL, NULL))
{
if(res = EVP_PKEY_assign_DSA(_ssl_key, _ssl_data) == 1)
_ssl_data = NULL;
}
else
{
fprintf(stderr, "*** error reading key\n");
}
}
else
{
fprintf(stderr, "*** error creating BIO\n");
}
}
else
{
fprintf(stderr, "*** error creating PKEY\n");
}
if(!res)
cleanup();
return res;
}
void key_chain_t::key_t::cleanup () const
{
if(_ssl_data)
DSA_free(_ssl_data);
if(_ssl_bio)
BIO_free(_ssl_bio);
if(_ssl_key)
EVP_PKEY_free(_ssl_key);
init();
}
// =============
// = Key Chain =
// =============
void key_chain_t::load (std::string const& path)
{
keys.clear();
plist::dictionary_t identities;
if(plist::get_key_path(plist::load(path), "identities", identities))
{
iterate(identity, identities)
{
std::string name, keyData;
if(plist::get_key_path(identity->second, "name", name) && plist::get_key_path(identity->second, "key", keyData))
{
keys.push_back(key_ptr(new key_t(identity->first, name, keyData)));
}
else
{
fprintf(stderr, "no name/key in entry:\n%s", to_s(identity->second).c_str());
}
}
}
else
{
fprintf(stderr, "no identities in %s\n", path.c_str());
}
}
void key_chain_t::save (std::string const& path) const
{
plist::dictionary_t identities;
iterate(key, keys)
{
plist::dictionary_t entry;
entry.insert(std::make_pair("name", (*key)->name()));
entry.insert(std::make_pair("key", (*key)->_key_data));
identities.insert(std::make_pair((*key)->identity(), entry));
}
plist::dictionary_t plist;
plist.insert(std::make_pair("identities", identities));
plist::save(path, plist);
}
void key_chain_t::add (key_t const& key)
{
keys.push_back(key_ptr(new key_t(key.identity(), key.name(), key._key_data)));
}
key_chain_t::key_ptr key_chain_t::find (std::string const& identity) const
{
iterate(key, keys)
{
if((*key)->identity() == identity && (*key)->setup())
return *key;
}
return key_ptr();
}

View File

@@ -0,0 +1,52 @@
#ifndef KEY_CHAIN_H_VALL5FR2
#define KEY_CHAIN_H_VALL5FR2
#include <oak/misc.h>
#include <oak/debug.h>
struct PUBLIC key_chain_t
{
WATCH_LEAKS(key_chain_t);
struct PUBLIC key_t
{
WATCH_LEAKS(key_chain_t::key_t);
key_t (std::string const& identity, std::string const& name, std::string const& key_data);
key_t (key_t const& rhs);
key_t& operator= (key_t const& rhs) = delete;
~key_t ();
std::string const& identity () const { return _identity; }
std::string const& name () const { return _name; }
operator EVP_PKEY* () const { setup(); return _ssl_key; }
private:
friend struct key_chain_t;
std::string _identity;
std::string _name;
std::string _key_data;
mutable EVP_PKEY* _ssl_key;
mutable BIO* _ssl_bio;
mutable DSA* _ssl_data;
void init () const;
bool setup () const;
void cleanup () const;
};
typedef std::tr1::shared_ptr<key_t> key_ptr;
void load (std::string const& path);
void save (std::string const& path) const;
void add (key_t const& key);
key_ptr find (std::string const& identity) const;
private:
std::vector<key_ptr> keys;
};
#endif /* end of include guard: KEY_CHAIN_H_VALL5FR2 */

View File

@@ -0,0 +1,26 @@
#include "network.h"
namespace network
{
bool can_reach_host (char const* host)
{
#if !defined(MAC_OS_X_VERSION_10_6) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_6)
SCNetworkConnectionFlags flags;
return SCNetworkCheckReachabilityByName(host, &flags) && (flags & kSCNetworkFlagsReachable);
#else
bool res = false;
if(SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, host))
{
SCNetworkReachabilityFlags flags;
if(SCNetworkReachabilityGetFlags(ref, &flags))
{
if(flags & kSCNetworkReachabilityFlagsReachable)
res = true;
}
CFRelease(ref);
}
return res;
#endif
}
} /* network */

View File

@@ -0,0 +1,18 @@
#ifndef NETWORK_H_L3XXH7J6
#define NETWORK_H_L3XXH7J6
#include "constants.h"
#include "download.h"
#include "filter_save.h"
#include "filter_tbz.h"
#include "filter_check_signature.h"
#include "filter_etag.h"
#include <oak/misc.h>
namespace network
{
PUBLIC bool can_reach_host (char const* host);
} /* network */
#endif /* end of include guard: NETWORK_H_L3XXH7J6 */

View File

@@ -0,0 +1,82 @@
#include "post.h"
#include "proxy.h"
#include "user_agent.h"
#include <text/encode.h>
#include <oak/oak.h>
// size_t receive_data (void* ptr, size_t size, size_t nmemb, void* udata)
// {
// std::string& buf = *(std::string*)udata;
// buf.insert(buf.size(), (const char*)ptr, size * nmemb);
// return size * nmemb;
// }
long post_to_server (std::string const& url, std::map<std::string, std::string> const& payload)
{
struct curl_httppost* formpost = NULL;
struct curl_httppost* lastptr = NULL;
iterate(pair, payload)
{
if(pair->second.size() < 2 || pair->second[0] != '@')
{
curl_formadd(&formpost, &lastptr,
CURLFORM_PTRNAME, pair->first.data(),
CURLFORM_NAMELENGTH, pair->first.size(),
CURLFORM_PTRCONTENTS, pair->second.data(),
CURLFORM_CONTENTSLENGTH, pair->second.size(),
CURLFORM_END);
}
else
{
curl_formadd(&formpost, &lastptr,
CURLFORM_PTRNAME, pair->first.data(),
CURLFORM_NAMELENGTH, pair->first.size(),
CURLFORM_FILE, pair->second.substr(1).c_str(),
CURLFORM_END);
}
}
long serverReply = 0;
if(CURL* handle = curl_easy_init())
{
std::string const userAgent = create_agent_info_string();
char errorbuf[CURL_ERROR_SIZE];
// std::string head = "", body = "";
curl_easy_setopt(handle, CURLOPT_URL, url.c_str());
curl_easy_setopt(handle, CURLOPT_HTTPPOST, formpost);
curl_easy_setopt(handle, CURLOPT_USERAGENT, userAgent.c_str());
curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorbuf);
curl_easy_setopt(handle, CURLOPT_FAILONERROR, true);
// curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, &receive_data);
// curl_easy_setopt(handle, CURLOPT_HEADERDATA, &head);
//
// curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, &receive_data);
// curl_easy_setopt(handle, CURLOPT_WRITEDATA, &body);
if(proxy_settings_t const& proxySettings = get_proxy_settings())
{
curl_easy_setopt(handle, CURLOPT_PROXY, proxySettings.server.c_str());
curl_easy_setopt(handle, CURLOPT_PROXYPORT, proxySettings.port);
if(proxySettings.password != NULL_STR)
curl_easy_setopt(handle, CURLOPT_PROXYUSERPWD, (proxySettings.user + ":" + proxySettings.password).c_str());
}
CURLcode rc = curl_easy_perform(handle);
if(rc == 0)
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &serverReply);
else fprintf(stderr, "curl error (%d): %s\n", rc, errorbuf);
// fprintf(stderr, "HEAD:\n%s\n", head.c_str());
// fprintf(stderr, "BODY:\n%s\n", body.c_str());
curl_easy_cleanup(handle);
}
curl_formfree(formpost);
return serverReply;
}

View File

@@ -0,0 +1,8 @@
#ifndef POST_H_HD2H7K9B
#define POST_H_HD2H7K9B
#include <oak/misc.h>
PUBLIC long post_to_server (std::string const& url, std::map<std::string, std::string> const& payload);
#endif /* end of include guard: POST_H_HD2H7K9B */

View File

@@ -0,0 +1,146 @@
#include "proxy.h"
#include <plist/plist.h>
#include <oak/debug.h>
#include <cf/cf.h>
OAK_DEBUG_VAR(Proxy);
static proxy_settings_t user_pw_settings (std::string const& server, UInt32 port)
{
D(DBF_Proxy, bug("%s:%zu\n", server.c_str(), (size_t)port););
std::string user = NULL_STR, pw = NULL_STR;
FourCharCode protocol = kSecProtocolTypeHTTPProxy;
SecKeychainAttribute attrs[3] = {
{ kSecProtocolItemAttr, sizeof(protocol), &protocol, },
{ kSecPortItemAttr, sizeof(port), &port, },
{ kSecServerItemAttr, server.size(), (void*)server.data(), }
};
SecKeychainAttributeList attrList = { sizeofA(attrs), attrs };
SecKeychainSearchRef searchRef = NULL;
if(SecKeychainSearchCreateFromAttributes(NULL, kSecInternetPasswordItemClass, &attrList, &searchRef) == noErr)
{
SecKeychainItemRef item;
while(user == NULL_STR && SecKeychainSearchCopyNext(searchRef, &item) == noErr)
{
UInt32 tag = kSecAccountItemAttr;
UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
SecKeychainAttributeInfo info = { 1, &tag, &format };
void* data = NULL;
UInt32 dataLen = 0;
SecKeychainAttributeList* authAttrList = NULL;
if(SecKeychainItemCopyAttributesAndData(item, &info, NULL, &authAttrList, &dataLen, &data) == noErr)
{
ASSERT(authAttrList->count == 1 && authAttrList->attr->tag == kSecAccountItemAttr);
user = std::string((char const*)authAttrList->attr->data, ((char const*)authAttrList->attr->data) + authAttrList->attr->length);
pw = std::string((char const*)data, ((char const*)data) + dataLen);
SecKeychainItemFreeContent(authAttrList, data);
D(DBF_Proxy, bug("found user %s\n", user.c_str()););
}
else
{
D(DBF_Proxy, bug("unable to obtain attributes from key chain entry\n"););
}
CFRelease(item);
}
CFRelease(searchRef);
}
else
{
D(DBF_Proxy, bug("failed creating key chain search\n"););
}
return proxy_settings_t(true, server, port, user, pw);
}
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
static void pac_proxy_callback (void* client, CFArrayRef proxyList, CFErrorRef error)
{
proxy_settings_t& settings = *(proxy_settings_t*)client;
settings.enabled = true;
if(error)
{
CFStringRef str = CFErrorCopyDescription(error);
fprintf(stderr, "proxy error: %s\n", cf::to_s(str).c_str());
CFRelease(str);
return;
}
if(!proxyList)
return;
for(CFIndex i = 0; i < CFArrayGetCount(proxyList); ++i)
{
plist::dictionary_t dict = plist::convert((CFDictionaryRef)CFArrayGetValueAtIndex(proxyList, i));
D(DBF_Proxy, bug("%s\n", to_s(dict).c_str()););
int32_t port;
std::string type;
if(plist::get_key_path(dict, cf::to_s(kCFProxyTypeKey), type) && type == cf::to_s(kCFProxyTypeHTTP) && plist::get_key_path(dict, cf::to_s(kCFProxyHostNameKey), settings.server) && plist::get_key_path(dict, cf::to_s(kCFProxyPortNumberKey), port))
{
settings.port = port;
break;
}
}
}
#endif
proxy_settings_t get_proxy_settings ()
{
proxy_settings_t res(false);
CFDictionaryRef tmp = SCDynamicStoreCopyProxies(NULL);
plist::dictionary_t const& plist = plist::convert(tmp);
D(DBF_Proxy, bug("%s\n", to_s(plist).c_str()););
bool enabled = false;
if(plist::get_key_path(plist, cf::to_s(kSCPropNetProxiesHTTPEnable), enabled) && enabled)
{
D(DBF_Proxy, bug("proxy enabled: %s\n", BSTR(enabled)););
std::string host; int32_t port;
if(plist::get_key_path(plist, cf::to_s(kSCPropNetProxiesHTTPProxy), host) && plist::get_key_path(plist, cf::to_s(kSCPropNetProxiesHTTPPort), port))
res = user_pw_settings(host, port);
}
else if(plist::get_key_path(plist, cf::to_s(kSCPropNetProxiesProxyAutoConfigEnable), enabled) && enabled)
{
D(DBF_Proxy, bug("pac enabled: %s\n", BSTR(enabled)););
#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4
std::string pacString;
if(plist::get_key_path(plist, cf::to_s(kSCPropNetProxiesProxyAutoConfigURLString), pacString))
{
D(DBF_Proxy, bug("pac script: %s\n", pacString.c_str()););
CFStreamClientContext context = { 0, &res, NULL, NULL, NULL };
CFURLRef pacURL = CFURLCreateWithString(kCFAllocatorDefault, cf::wrap(pacString), NULL);
CFURLRef targetURL = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("http://macromates.com/"), NULL);
CFRunLoopSourceRef runLoopSource = CFNetworkExecuteProxyAutoConfigurationURL(pacURL, targetURL, &pac_proxy_callback, &context);
CFRelease(targetURL);
CFRelease(pacURL);
CFStringRef runLoopMode = CFSTR("OakRunPACScriptRunLoopMode");
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, runLoopMode);
while(!res.enabled)
CFRunLoopRunInMode(runLoopMode, 0.1, TRUE);
if(CFRunLoopSourceIsValid(runLoopSource))
CFRunLoopSourceInvalidate(runLoopSource);
CFRelease(runLoopSource);
if(res.server == NULL_STR)
res.enabled = false;
}
#endif
}
else if(plist::get_key_path(plist, cf::to_s(kSCPropNetProxiesProxyAutoDiscoveryEnable), enabled) && enabled)
{
D(DBF_Proxy, bug("auto discovery enabled: %s\n", BSTR(enabled)););
}
CFRelease(tmp);
return res;
}

View File

@@ -0,0 +1,20 @@
#ifndef PROXY_H_S8ZWZPU8
#define PROXY_H_S8ZWZPU8
#include <oak/misc.h>
struct proxy_settings_t
{
proxy_settings_t (bool enabled = false, std::string const& server = NULL_STR, long port = 0, std::string const& user = NULL_STR, std::string const& password = NULL_STR) : enabled(enabled), server(server), port(port), user(user), password(password) { }
EXPLICIT operator bool () const { return enabled; }
bool enabled;
std::string server;
long port;
std::string user;
std::string password;
};
PUBLIC proxy_settings_t get_proxy_settings ();
#endif /* end of include guard: PROXY_H_S8ZWZPU8 */

View File

@@ -0,0 +1,83 @@
#include "tbz.h"
#include <text/format.h>
#include <OakSystem/process.h>
namespace network
{
static char const* strip_components_flag ()
{
SInt32 osVersion = 0;
Gestalt(gestaltSystemVersion, &osVersion);
return osVersion >= 0x1050 ? "--strip-components" : "--strip-path";
}
pid_t launch_tbz (std::string const& dest, int& input, int& output, std::string& error)
{
signal(SIGPIPE, SIG_IGN);
int in[2], out[2];
pipe(&in[0]);
pipe(&out[0]);
char const* argv[] = { "/usr/bin/tar", "-jxmkC", dest.c_str(), strip_components_flag(), "1", NULL };
oak::c_array env(oak::basic_environment());
pid_t pid = vfork();
if(pid == 0)
{
close(0); close(1); close(2);
dup(in[0]); dup(out[1]); dup(out[1]);
close(in[0]); close(in[1]); close(out[0]); close(out[1]);
signal(SIGPIPE, SIG_DFL);
execve(argv[0], (char* const*)argv, env);
_exit(-1);
}
else
{
close(in[0]);
close(out[1]);
if(pid == -1)
{
close(in[1]);
close(out[0]);
error = text::format("Error launching tar: %s", strerror(errno));
}
else
{
fcntl(input = in[1], F_SETFD, 1);
fcntl(output = out[0], F_SETFD, 1);
}
}
return pid;
}
bool finish_tbz (pid_t pid, int& input, int& output, std::string& error)
{
close(input);
std::string tbzOut;
ssize_t len;
char bytes[512];
while((len = read(output, bytes, sizeof(bytes))) > 0)
tbzOut.insert(tbzOut.end(), bytes, bytes + len);
close(output);
int status = 0;
if(waitpid(pid, &status, 0) == pid && WIFEXITED(status))
{
if(WEXITSTATUS(status) == 0 && tbzOut.empty())
return true;
error = "Corrupt archive.";
// error = text::format("Unexpected exit code from tar (%d)\n%s\n", WEXITSTATUS(status), text::trim(tbzOut).c_str());
}
else
{
error = text::format("Abnormal exit from tar (%d).\n", status);
}
return false;
}
} /* network */

View File

@@ -0,0 +1,13 @@
#ifndef NETWORK_TBZ_H_NEU56OWR
#define NETWORK_TBZ_H_NEU56OWR
#include <oak/misc.h>
namespace network
{
PUBLIC pid_t launch_tbz (std::string const& dest, int& input, int& output, std::string& error);
PUBLIC bool finish_tbz (pid_t pid, int& input, int& output, std::string& error);
} /* network */
#endif /* end of include guard: NETWORK_TBZ_H_NEU56OWR */

View File

@@ -0,0 +1,43 @@
#include "user_agent.h"
#include <OakSystem/application.h>
#include <text/format.h>
#include <plist/plist.h>
#include <io/path.h>
static std::string hardware_info (int field, bool integer = false)
{
char buf[1024];
size_t bufSize = sizeof(buf);
int request[] = { CTL_HW, field };
if(sysctl(request, sizeofA(request), buf, &bufSize, NULL, 0) != -1)
{
if(integer && bufSize == 4)
return text::format("%d", *(int*)buf);
return std::string(buf, buf + bufSize - 1);
}
return "???";
}
static std::string user_uuid ()
{
oak::uuid_t uuid;
return plist::get_key_path(plist::load(path::join(path::home(), "Library/Preferences/com.apple.CrashReporter.plist")), "userUUID", uuid) ? to_s(uuid) : "???";
}
std::string create_agent_info_string ()
{
SInt32 osMajor, osMinor, osBugfix;
Gestalt(gestaltSystemVersionMajor, &osMajor);
Gestalt(gestaltSystemVersionMinor, &osMinor);
Gestalt(gestaltSystemVersionBugFix, &osBugfix);
return text::format("%s/%s/%s %zu.%zu.%zu/%s/%s/%s", oak::application_t::name().c_str(), oak::application_t::revision().c_str(), user_uuid().c_str(),
(size_t)osMajor, (size_t)osMinor, (size_t)osBugfix,
hardware_info(HW_MACHINE).c_str(),
hardware_info(HW_MODEL).c_str(),
hardware_info(HW_NCPU, true).c_str()
);
}

View File

@@ -0,0 +1,6 @@
#ifndef USER_AGENT_H_S2EYS361
#define USER_AGENT_H_S2EYS361
std::string create_agent_info_string ();
#endif /* end of include guard: USER_AGENT_H_S2EYS361 */