/*
 * Create a new connection cache.
 */
struct http_connection_cache *
_http_connection_cache_create(struct pevent_ctx *ctx,
	u_int max_num, u_int max_idle)
{
	struct http_connection_cache *cache;

	/* Sanity check */
	if (max_num == 0 || max_idle == 0) {
		errno = EINVAL;
		return (NULL);
	}

	/* Create new cache */
	if ((cache = MALLOC(MEM_TYPE_CACHE, sizeof(*cache))) == NULL) {
		alogf(LOG_ERR, "%s: %m", "malloc");
	    	return (NULL);
	}

	/* Initialize it */
	memset(cache, 0, sizeof(*cache));
	TAILQ_INIT(&cache->list);
	cache->ctx = ctx;
	cache->max_num = max_num;
	cache->max_idle = max_idle;

	/* Initialize mutex */
	if ((errno = pthread_mutex_init(&cache->mutex, NULL)) != 0) {
		alogf(LOG_ERR, "%s: %m", "pthread_mutex_init");
		FREE(MEM_TYPE_CACHE, cache);
	    	return (NULL);
	}

	/* Done */
	return (cache);
}
Exemple #2
0
static int reconnect(SLNPullRef const pull) {
	int rc;
	HTTPConnectionFree(&pull->conn);

	rc = HTTPConnectionCreateOutgoing(pull->host, 0, &pull->conn);
	if(rc < 0) {
		alogf("Pull couldn't connect to %s (%s)\n", pull->host, sln_strerror(rc));
		return rc;
	}

//	str_t path[URI_MAX];
//	str_t *query_encoded = NULL;
//	if(pull->query) query_encoded = QSEscape(pull->query, strlen(pull->query), true);
//	snprintf(path, sizeof(path), "/sln/query-obsolete?q=%s", query_encoded ?: "");
//	FREE(&query_encoded);
	HTTPConnectionWriteRequest(pull->conn, HTTP_GET, "/sln/all", pull->host);
	// TODO
	// - New API /sln/query and /sln/metafiles
	// - Pagination ?start=[last URI seen]
	// - Error handling
	// - Query string formatter
	HTTPConnectionWriteHeader(pull->conn, "Cookie", pull->cookie);
	HTTPConnectionBeginBody(pull->conn);
	rc = HTTPConnectionEnd(pull->conn);
	if(rc < 0) {
		alogf("Pull couldn't connect to %s (%s)\n", pull->host, sln_strerror(rc));
		return rc;
	}
	int const status = HTTPConnectionReadResponseStatus(pull->conn);
	if(status < 0) {
		alogf("Pull connection error: %s\n", sln_strerror(status));
		return status;
	}
	if(403 == status) {
		alogf("Pull connection authentication failed\n");
		return UV_EACCES;
	}
	if(status < 200 || status >= 300) {
		alogf("Pull connection error: %d\n", status);
		return UV_EPROTO;
	}

	// TODO: All this does is scan past the headers.
	// We don't actually use them...
	HTTPHeadersRef headers;
	rc = HTTPHeadersCreateFromConnection(pull->conn, &headers);
	assert(rc >= 0); // TODO
	HTTPHeadersFree(&headers);
/*	rc = HTTPConnectionReadHeaders(pull->conn, NULL, NULL, 0);
	if(rc < 0) {
		alogf("Pull connection error %s\n", sln_strerror(rc));
		return rc;
	}*/

	return 0;
}
Exemple #3
0
static int init_https(void) {
	if(!SERVER_PORT_TLS) return 0;
	struct tls_config *config = tls_config_new();
	if(!config) {
		alogf("TLS config error: %s\n", strerror(errno));
		return -1;
	}
	int rc = tls_config_set_ciphers(config, TLS_CIPHERS);
	if(0 != rc) {
		alogf("TLS ciphers error: %s\n", strerror(errno));
		tls_config_free(config); config = NULL;
		return -1;
	}
	tls_config_set_protocols(config, TLS_PROTOCOLS);
	str_t pemfile[PATH_MAX];
	snprintf(pemfile, sizeof(pemfile), "%s/key.pem", path);
	rc = tls_config_set_key_file(config, pemfile);
	if(0 != rc) {
		alogf("TLS key file error: %s\n", strerror(errno));
		tls_config_free(config); config = NULL;
		return -1;
	}
	snprintf(pemfile, sizeof(pemfile), "%s/crt.pem", path);
	rc = tls_config_set_cert_file(config, pemfile);
	if(0 != rc) {
		alogf("TLS crt file error: %s\n", strerror(errno));
		tls_config_free(config); config = NULL;
		return -1;
	}
	struct tls *tls = tls_server();
	if(!tls) {
		alogf("TLS engine error: %s\n", strerror(errno));
		tls_config_free(config); config = NULL;
		return -1;
	}
	rc = tls_configure(tls, config);
	tls_config_free(config); config = NULL;
	if(0 != rc) {
		alogf("TLS config error: %s\n", tls_error(tls));
		tls_free(tls); tls = NULL;
		return -1;
	}
	server_tls = HTTPServerCreate((HTTPListener)listener, blog);
	if(!server_tls) {
		alogf("HTTPS server could not be initialized\n");
		tls_free(tls); tls = NULL;
		return -1;
	}
	rc = HTTPServerListenSecure(server_tls, SERVER_ADDRESS, SERVER_PORT_TLS, &tls);
	tls_free(tls); tls = NULL;
	if(rc < 0) {
		alogf("HTTPS server could not be started: %s\n", sln_strerror(rc));
		return -1;
	}
	int const port = SERVER_PORT_TLS;
	alogf("StrongLink server running at https://localhost:%d/\n", port);
	return 0;
}
Exemple #4
0
static bool load_template(BlogRef const blog, strarg_t const name, TemplateRef *const out) {
	assert(out);
	assert(blog->dir);
	str_t path[PATH_MAX];
	int rc = snprintf(path, sizeof(path), "%s/template/%s", blog->dir, name);
	if(rc < 0 || rc >= sizeof(path)) return false;
	TemplateRef t = TemplateCreateFromPath(path);
	if(!t) {
		alogf("Couldn't load blog template at '%s'\n", path);
		alogf("(Try reinstalling the resource files.)\n");
		return false;
	}
	*out = t;
	return true;
}
int MultipartFormPeek(MultipartFormRef const form, MultipartEvent *const type, uv_buf_t *const buf) {
	if(!form) return UV_EINVAL;
	if(!type) return UV_EINVAL;
	if(!buf) return UV_EINVAL;
	uv_buf_t raw[1];
	HTTPEvent t;
	int rc;
	ssize_t len;
	for(;;) {
		if(MultipartNothing != form->type) break;
		rc = HTTPConnectionPeek(form->conn, &t, raw);
		if(rc < 0) return rc;
		if(HTTPMessageEnd == t) {
			*raw = uv_buf_init(NULL, 0);
			t = HTTPBody;
		}
		if(HTTPBody != t) {
			assertf(0, "Multipart unexpected HTTP event: %d\n", t);
			return UV_UNKNOWN;
		}
		len = multipart_parser_execute(form->parser, raw->base, raw->len);
		if(len < 0) {
			alogf("Multipart parse error\n");
			return -1;
		}
		HTTPConnectionPop(form->conn, len);
	}
	assertf(MultipartNothing != form->type, "MultipartFormPeek must return an event");
	*type = form->type;
	*buf = *form->out;
	return 0;
}
Exemple #6
0
static int init_http(void) {
    if(!SERVER_PORT_RAW) return 0;
    server_raw = HTTPServerCreate((HTTPListener)listener, blog);
    if(!server_raw) {
        alogf("HTTP server could not be initialized\n");
        return -1;
    }
    int rc = HTTPServerListen(server_raw, SERVER_ADDRESS, SERVER_PORT_RAW);
    if(rc < 0) {
        alogf("HTTP server could not be started: %s\n", sln_strerror(rc));
        return -1;
    }
    strarg_t const port = SERVER_PORT_RAW;
    alogf("StrongLink server running at http://localhost:%s/\n", port);
    return 0;
}
Exemple #7
0
// TODO: Basically identical to version in RSSServer.c.
static int load_template(BlogRef const blog, strarg_t const name, TemplateRef *const out) {
	assert(out);
	assert(blog->dir);
	str_t path[PATH_MAX];
	int rc = snprintf(path, sizeof(path), "%s/template/%s", blog->dir, name);
	if(rc < 0) return rc; // TODO: snprintf(3) error reporting?
	if(rc >= sizeof(path)) return UV_ENAMETOOLONG;
	TemplateRef t = NULL;
	rc = TemplateCreateFromPath(path, &t);
	if(rc < 0) {
		alogf("Error loading template at '%s': %s\n", path, uv_strerror(rc));
		if(UV_ENOENT == rc) alogf("(Try reinstalling the resource files.)\n");
		return rc;
	}
	*out = t; t = NULL;
	return 0;
}
Exemple #8
0
static void init(void *const unused) {
	int rc = async_random((byte_t *)&SLNSeed, sizeof(SLNSeed));
	if(rc < 0) {
		alogf("Random seed error\n");
		return;
	}

	uv_signal_init(async_loop, sigpipe);
	uv_signal_start(sigpipe, ignore, SIGPIPE);
	uv_unref((uv_handle_t *)sigpipe);

	str_t *tmp = strdup(path);
	strarg_t const reponame = basename(tmp); // TODO
	rc = SLNRepoCreate(path, reponame, &repo);
	FREE(&tmp);
	if(rc < 0) {
		alogf("Repository could not be opened: %s\n", sln_strerror(rc));
		return;
	}
	blog = BlogCreate(repo);
	if(!blog) {
		alogf("Blog server could not be initialized\n");
		return;
	}
	rc = RSSServerCreate(repo, &rss);
	if(rc < 0) {
		alogf("RSS server error: %s\n", sln_strerror(rc));
		return;
	}

	if(init_http() < 0 || init_https() < 0) {
		HTTPServerClose(server_raw);
		HTTPServerClose(server_tls);
		return;
	}

//	SLNRepoPullsStart(repo);

	uv_signal_init(async_loop, sigint);
	uv_signal_start(sigint, stop, SIGINT);
	uv_unref((uv_handle_t *)sigint);
}
Exemple #9
0
int main(int const argc, char const *const *const argv) {
    // Depending on how async_pool and async_fs are configured, we might be
    // using our own thread pool heavily or not. However, at the minimum,
    // uv_getaddrinfo uses the libuv thread pool, and it blocks on the
    // network, so don't set this number too low.
    if(!getenv("UV_THREADPOOL_SIZE")) putenv((char *)"UV_THREADPOOL_SIZE=4");

    raiserlimit();
    async_init();

    int rc = tls_init();
    if(rc < 0) {
        alogf("TLS initialization error: %s\n", strerror(errno));
        return 1;
    }

    if(2 != argc || '-' == argv[1][0]) {
        alogf("Usage:\n\t" "%s repo\n", argv[0]);
        return 1;
    }
    path = argv[1];

    // Even our init code wants to use async I/O.
    async_spawn(STACK_DEFAULT, init, NULL);
    uv_run(async_loop, UV_RUN_DEFAULT);

    async_spawn(STACK_DEFAULT, term, NULL);
    uv_run(async_loop, UV_RUN_DEFAULT);

    // cleanup is separate from term because connections might
    // still be active.
    async_spawn(STACK_DEFAULT, cleanup, NULL);
    uv_run(async_loop, UV_RUN_DEFAULT);

    async_destroy();

    // TODO: Windows?
    if(sig) raise(sig);

    return 0;
}
Exemple #10
0
int BlogDispatch(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) {
	int rc = -1;
	rc = rc >= 0 ? rc : GET_query(blog, session, conn, method, URI, headers);
	rc = rc >= 0 ? rc : GET_compose(blog, session, conn, method, URI, headers);
	rc = rc >= 0 ? rc : GET_upload(blog, session, conn, method, URI, headers);
	rc = rc >= 0 ? rc : POST_post(blog, session, conn, method, URI, headers);
	rc = rc >= 0 ? rc : GET_account(blog, session, conn, method, URI, headers);
	rc = rc >= 0 ? rc : POST_auth(blog, session, conn, method, URI, headers);

	if(403 == rc) {
		HTTPConnectionSendRedirect(conn, 303, "/account");
		return 0;
	}
	if(rc >= 0) return rc; // TODO: Pretty 404 pages, etc.

	if(HTTP_GET != method && HTTP_HEAD != method) return -1;

	str_t path[PATH_MAX];
	size_t const len = strlen(URI);
	if(0 == len) return 400;

	strarg_t const qs = strchr(URI, '?');
	size_t const pathlen = qs ? qs-URI : len;
	str_t *URI_raw = QSUnescape(URI, pathlen, false);
	if('/' == URI[len-1]) {
		str_t const index[] = "index.html";
		rc = snprintf(path, sizeof(path), "%s/static/%s%s", blog->dir, URI_raw, index);
	} else {
		rc = snprintf(path, sizeof(path), "%s/static/%s", blog->dir, URI_raw);
	}
	FREE(&URI_raw);
	if(rc >= sizeof(path)) return 414; // Request-URI Too Large
	if(rc < 0) return 500;

	if(strstr(path, "..")) return 403; // Security critical.

	strarg_t const ext = strrchr(path, '.');
	strarg_t const type = exttype(ext);
	rc = HTTPConnectionSendFile(conn, path, type, -1);
	if(UV_EISDIR == rc) {
		str_t location[URI_MAX];
		rc = snprintf(location, sizeof(location), "%s/", URI);
		if(rc >= sizeof(location)) return 414; // Request-URI Too Large
		if(rc < 0) return 500;
		HTTPConnectionSendRedirect(conn, 301, location);
		return 0;
	}
	if(rc < 0 && UV_EPIPE != rc) {
		alogf("Error sending file %s: %s\n", URI, uv_strerror(rc));
	}

	return 0;
}
Exemple #11
0
int SLNSubmissionEnd(SLNSubmissionRef const sub) {
	if(!sub) return 0;
	if(sub->size <= 0) return UV_EINVAL;
	assert(sub->tmppath);
	assert(sub->tmpfile >= 0);
	assert(sub->type);

	sub->URIs = SLNHasherEnd(sub->hasher);
	sub->internalHash = strdup(SLNHasherGetInternalHash(sub->hasher));
	SLNHasherFree(&sub->hasher);
	if(!sub->URIs || !sub->internalHash) return UV_ENOMEM;

	SLNRepoRef const repo = SLNSubmissionGetRepo(sub);
	str_t *internalPath = NULL;
	bool worker = false;
	int rc = 0;

	rc = verify(sub);
	if(rc < 0) goto cleanup;

	internalPath = SLNRepoCopyInternalPath(repo, sub->internalHash);
	if(!internalPath) rc = UV_ENOMEM;
	if(rc < 0) goto cleanup;

	async_pool_enter(NULL); worker = true;

	rc = async_fs_fdatasync(sub->tmpfile);
	if(rc < 0) goto cleanup;

	// We use link(2) rather than rename(2) because link gives an error
	// if there's a name collision, rather than overwriting. We want to
	// keep the oldest file for any given hash, rather than the newest.
	rc = async_fs_link_mkdirp(sub->tmppath, internalPath);
	if(UV_EEXIST == rc) {
		rc = 0;
		goto cleanup;
	}
	if(rc < 0) {
		alogf("SLNSubmission couldn't move '%s' to '%s' (%s)\n", sub->tmppath, internalPath, sln_strerror(rc));
		goto cleanup;
	}

	rc = async_fs_sync_dirname(internalPath);

cleanup:
	if(worker) { async_pool_leave(NULL); worker = false; }
	FREE(&internalPath);

	async_fs_unlink(sub->tmppath);
	FREE(&sub->tmppath);
	return rc;
}
Exemple #12
0
static int tls_poll(uv_stream_t *const stream, int const event) {
	int rc;
	if(TLS_READ_AGAIN == event) {
		uv_buf_t buf;
		rc = async_read(stream, 0, &buf);
		if(UV_ENOBUFS == rc) rc = 0;
		if(rc < 0) alogf("tls_poll read %s\n", uv_strerror(rc));
		rc = 0;
	} else if(TLS_WRITE_AGAIN == event) {
		// TODO: libuv provides NO WAY to wait until a stream is
		// writable! Even our zero-length write hack doesn't work.
		// uv_poll can't be used on uv's own stream fds.
		rc = async_sleep(50);
//		uv_buf_t buf = uv_buf_init(NULL, 0);
//		rc = async_write(stream, &buf, 1);
		if(rc < 0) alogf("tls_poll write %s\n", uv_strerror(rc));
		rc = 0;
	} else {
		rc = -errno; // TODO: Might have problems on Windows?
		if(rc >= 0) rc = UV_EOF; // Most common case, is this guaranteed?
	}
	return rc;
}
Exemple #13
0
static int listener0(void *ctx, HTTPServerRef const server, HTTPConnectionRef const conn) {
    HTTPMethod method;
    str_t URI[URI_MAX];
    ssize_t len = HTTPConnectionReadRequest(conn, &method, URI, sizeof(URI));
    if(UV_EOF == len) return 0;
    if(UV_ECONNRESET == len) return 0;
    if(UV_EMSGSIZE == len) return 414; // Request-URI Too Large
    if(len < 0) {
        alogf("Request error: %s\n", uv_strerror(len));
        return 500;
    }

    HTTPHeadersRef headers;
    int rc = HTTPHeadersCreateFromConnection(conn, &headers);
    if(UV_EMSGSIZE == rc) return 431; // Request Header Fields Too Large
    if(rc < 0) return 500;

    strarg_t const host = HTTPHeadersGet(headers, "host");
    str_t domain[1023+1];
    domain[0] = '\0';
    if(host) sscanf(host, "%1023[^:]", domain);
    // TODO: Verify Host header to prevent DNS rebinding.

    if(SERVER_PORT_TLS && server == server_raw) {
        // Redirect from HTTP to HTTPS
        if('\0' == domain[0]) return 400;
        strarg_t const port = SERVER_PORT_TLS;
        str_t loc[URI_MAX];
        rc = snprintf(loc, sizeof(loc), "https://%s:%s%s", domain, port, URI);
        if(rc >= sizeof(loc)) 414; // Request-URI Too Large
        if(rc < 0) return 500;
        HTTPConnectionSendRedirect(conn, 301, loc);
        return 0;
    }

    strarg_t const cookie = HTTPHeadersGet(headers, "cookie");
    SLNSessionCacheRef const cache = SLNRepoGetSessionCache(repo);
    SLNSessionRef session = NULL;
    rc = SLNSessionCacheCopyActiveSession(cache, cookie, &session);
    if(rc < 0) return 500;
    // Note: null session is valid (zero permissions).

    rc = -1;
    rc = rc >= 0 ? rc : SLNServerDispatch(repo, session, conn, method, URI, headers);
    rc = rc >= 0 ? rc : BlogDispatch(blog, session, conn, method, URI, headers);

    SLNSessionRelease(&session);
    HTTPHeadersFree(&headers);
    return rc;
}
Exemple #14
0
int SLNSubmissionStore(SLNSubmissionRef const sub, DB_txn *const txn) {
	assert(sub);
	assert(txn);
	assert(!sub->tmppath);
	// Session permissions were already checked when the sub was created.

	int64_t fileID = db_next_id(SLNFileByID, txn);
	int rc;

	DB_val dupFileID_val[1];
	SLNFileIDByInfoValPack(dupFileID_val, txn, fileID);

	DB_val fileInfo_key[1];
	SLNFileIDByInfoKeyPack(fileInfo_key, txn, sub->internalHash, sub->type);
	rc = db_put(txn, fileInfo_key, dupFileID_val, DB_NOOVERWRITE);
	if(rc >= 0) {
		DB_val fileID_key[1];
		SLNFileByIDKeyPack(fileID_key, txn, fileID);
		DB_val file_val[1];
		SLNFileByIDValPack(file_val, txn, sub->internalHash, sub->type, sub->size);
		rc = db_put(txn, fileID_key, file_val, DB_NOOVERWRITE_FAST);
		if(rc < 0) return rc;
	} else if(DB_KEYEXIST == rc) {
		fileID = db_read_uint64(dupFileID_val);
	} else return rc;

	for(size_t i = 0; sub->URIs[i]; ++i) {
		strarg_t const URI = sub->URIs[i];
		DB_val null = { 0, NULL };

		DB_val fwd[1];
		SLNFileIDAndURIKeyPack(fwd, txn, fileID, URI);
		rc = db_put(txn, fwd, &null, DB_NOOVERWRITE_FAST);
		if(rc < 0 && DB_KEYEXIST != rc) return rc;

		DB_val rev[1];
		SLNURIAndFileIDKeyPack(rev, txn, URI, fileID);
		rc = db_put(txn, rev, &null, DB_NOOVERWRITE_FAST);
		if(rc < 0 && DB_KEYEXIST != rc) return rc;
	}

	rc = SLNSubmissionParseMetaFile(sub, fileID, txn, &sub->metaFileID);
	if(rc < 0) {
		alogf("Submission meta-file error: %s\n", sln_strerror(rc));
		return rc;
	}

	return 0;
}
Exemple #15
0
int SLNSubmissionWrite(SLNSubmissionRef const sub, byte_t const *const buf, size_t const len) {
	if(!sub) return 0;
	assert(sub->tmpfile >= 0);

	uv_buf_t parts[] = { uv_buf_init((char *)buf, len) };
	int rc = async_fs_writeall(sub->tmpfile, parts, numberof(parts), -1);
	if(rc < 0) {
		alogf("SLNSubmission write error: %s\n", sln_strerror(rc));
		return rc;
	}

	sub->size += len;
	SLNHasherWrite(sub->hasher, buf, len);
	return 0;
}
Exemple #16
0
static void term(void *const unused) {
    fprintf(stderr, "\n");
    alogf("Stopping StrongLink server...\n");

    uv_ref((uv_handle_t *)sigint);
    uv_signal_stop(sigint);
    async_close((uv_handle_t *)sigint);

    SLNRepoPullsStop(repo);
    HTTPServerClose(server_raw);
    HTTPServerClose(server_tls);

    uv_ref((uv_handle_t *)sigpipe);
    uv_signal_stop(sigpipe);
    uv_close((uv_handle_t *)sigpipe, NULL);
}
/*
 * Start the idle timer based on the oldest item in the cache.
 *
 * This assumes the cache is locked.
 */
static void
http_connection_cache_start_timer(struct http_connection_cache *cache)
{
	struct cached_connection *const oldest = TAILQ_FIRST(&cache->list);
	const time_t now = time(NULL);
	int timeout;

	/* Don't start timer when not appropriate */
	if (cache->num == 0
	    || cache->max_idle == 0
	    || cache->timer != NULL)
		return;

	/* Get time until oldest connection expires */
	timeout = (now < oldest->expiry) ? (oldest->expiry - now) * 1000 : 0;

	/* Start timer */
	if (pevent_register(cache->ctx, &cache->timer, 0,
	    &cache->mutex, http_connection_cache_timeout, cache,
	    PEVENT_TIME, timeout) == -1)
		alogf(LOG_ERR, "%s: %m", "pevent_register");
}
/*
 * Same as http_xml_send_xmlrpc() but with user-defined URL path.
 */
int
http_xml_send_xmlrpc2(struct http_client *client,
	struct in_addr ip, u_int16_t port, int https,
	const char *username, const char *password, const char *urlpath,
	const char *methodName, u_int nparams,
	const struct structs_type **ptypes, const void **pdatas,
	const struct structs_type *rep_type, void *rep,
	struct xmlrpc_compact_fault *faultp, structs_xmllog_t *rlogger)
{
	const struct structs_type *const xreq_type
	    = &structs_type_xmlrpc_request;
	const struct structs_type *const xrep_type
	    = &structs_type_xmlrpc_response;
	const struct structs_type *const ftype
	    = &structs_type_xmlrpc_compact_fault;
	struct http_xml_send_xmlrpc_ctx *ctx;
	struct xmlrpc_compact_fault fault;
	char ebuf[128];
	int ret = -1;
	int r;

	/* Get context */
	if ((ctx = MALLOC(MEM_TYPE, sizeof(*ctx))) == NULL) {
		alogf(LOG_ERR, "%s: %m", "malloc");
		return (-1);
	}
	memset(ctx, 0, sizeof(*ctx));
	pthread_cleanup_push(http_xml_send_xmlrpc_cleanup, ctx);

	/* Build XML-RPC request and reply */
	if ((ctx->xreq = structs_xmlrpc_build_request(MEM_TYPE,
	    methodName, nparams, ptypes, pdatas)) == NULL) {
		alogf(LOG_ERR, "%s: %m", "structs_xmlrpc_build_request");
		goto fail;
	}
	if ((ctx->xrep = MALLOC(MEM_TYPE, xrep_type->size)) == NULL) {
		alogf(LOG_ERR, "%s: %m", "malloc");
		goto fail;
	}
	if (structs_init(xrep_type, NULL, ctx->xrep) == -1) {
		alogf(LOG_ERR, "%s: %m", "structs_init");
		FREE(MEM_TYPE, ctx->xrep);
		ctx->xrep = NULL;
		goto fail;
	}

#ifdef XML_RPC_DEBUG
	printf("%s: sending this XML-RPC request:\n", __FUNCTION__);
	(void)structs_xml_output(xreq_type, XML_RPC_REQUEST_TAG,
	    NULL, ctx->xreq, stdout, NULL, 0);
#endif

	/* Send request and get reply; note: we could get canceled here. */
	r = http_xml_send(client, ip, port, https, urlpath,
	    username, password, XML_RPC_REQUEST_TAG, NULL, xreq_type,
	    ctx->xreq, STRUCTS_XML_FULL, XML_RPC_REPLY_TAG, NULL, NULL,
	    xrep_type, ctx->xrep, 0, rlogger);

#ifdef XML_RPC_DEBUG
	printf("%s: got this XML-RPC reply (error=%s):\n",
	    __FUNCTION__, r == -1 ? strerror(errno) : "none");
	(void)structs_xml_output(xrep_type, XML_RPC_REPLY_TAG,
	    NULL, ctx->xrep, stdout, NULL, 0);
#endif

	/* Check error */
	if (r == -1)
		goto fail;

	/* Check for fault */
	if (structs_init(ftype, NULL, &fault) == -1) {
		alogf(LOG_ERR, "%s: %m", "structs_init");
		goto fail;
	}
	if (structs_xmlrpc2struct(xrep_type, ctx->xrep,
	    "fault.value", ftype, &fault, NULL, NULL, 0) == 0) {
		if (faultp != NULL)
			*faultp = fault;
		else
			structs_free(ftype, NULL, &fault);
		ret = -2;			/* -2 indicates fault */
		goto fail;
	}
	structs_free(ftype, NULL, &fault);

	/* Extract response (if desired) */
	if (rep != NULL) {
		if (rep_type != NULL) {		/* return compact type */
			if (structs_xmlrpc2struct(xrep_type, ctx->xrep,
			    "params.0.value", rep_type, rep, NULL, ebuf,
			    sizeof(ebuf)) == -1) {
				(*rlogger)(LOG_ERR, "error decoding XML-RPC"
				    " response: %s", ebuf);
				goto fail;
			}
		} else {			/* return exploded type */
			if (structs_get(&structs_type_xmlrpc_value,
			    "params.0.value", ctx->xrep, rep) == -1) {
				alogf(LOG_ERR, "structs_get: %m%s", "");
				goto fail;
			}
		}
	}

	/* OK */
	ret = 0;

fail:;
	/* Done */
	pthread_cleanup_pop(1);
	return (ret);
}
Exemple #19
0
static int GET_query(BlogRef const blog, SLNSessionRef const session, HTTPConnectionRef const conn, HTTPMethod const method, strarg_t const URI, HTTPHeadersRef const headers) {
	if(HTTP_GET != method && HTTP_HEAD != method) return -1;
	strarg_t qs = NULL;
	if(0 != uripathcmp("/", URI, &qs)) return -1;

	if(HTTP_HEAD == method) return 501; // TODO

	// TODO: This is the most complicated function in the whole program.
	// It's unbearable.

	str_t *query = NULL;
	str_t *query_HTMLSafe = NULL;
	str_t *parsed_HTMLSafe = NULL;
	SLNFilterRef filter = NULL;
	int rc;

	static strarg_t const fields[] = {
		"q",
	};
	str_t *values[numberof(fields)] = {};
	QSValuesParse(qs, values, fields, numberof(fields));
	query = values[0]; values[0] = NULL;
	query_HTMLSafe = htmlenc(values[0]);
	rc = SLNUserFilterParse(session, query, &filter);
	QSValuesCleanup(values, numberof(values));
	if(DB_EACCES == rc) {
		FREE(&query);
		FREE(&query_HTMLSafe);
		return 403;
	}
//	SLNFilterPrintSexp(filter, stderr, 0); // DEBUG
	if(DB_EINVAL == rc) rc = SLNFilterCreate(session, SLNVisibleFilterType, &filter);
	if(rc < 0) {
		FREE(&query);
		FREE(&query_HTMLSafe);
		return 500;
	}

	str_t tmp[URI_MAX]; tmp[0] = '\0'; // fmemopen shim ignores mode.
	FILE *parsed = fmemopen(tmp, sizeof(tmp), "w");
	if(!parsed) {
		FREE(&query);
		FREE(&query_HTMLSafe);
		return 500;
	}
	SLNFilterPrintUser(filter, parsed, 0);
	fclose(parsed);
	tmp[sizeof(tmp)-1] = '\0'; // fmemopen(3) says this isn't guaranteed.
	parsed_HTMLSafe = htmlenc(tmp);

	SLNFilterPosition pos[1] = {{ .dir = -1 }};
	str_t *URIs[RESULTS_MAX];
	uint64_t max = numberof(URIs);
	int outdir = -1;
	SLNFilterParseOptions(qs, pos, &max, &outdir, NULL);
	if(max < 1) max = 1;
	if(max > numberof(URIs)) max = numberof(URIs);
	bool const has_start = !!pos->URI;

	uint64_t const t1 = uv_hrtime();

	ssize_t const count = SLNFilterCopyURIs(filter, session, pos, outdir, false, URIs, (size_t)max);
	SLNFilterPositionCleanup(pos);
	if(count < 0) {
		FREE(&query);
		FREE(&query_HTMLSafe);
		FREE(&parsed_HTMLSafe);
		SLNFilterFree(&filter);
		if(DB_NOTFOUND == count) {
			// Possibly a filter age-function bug.
			alogf("Invalid start parameter? %s\n", URI);
			return 500;
		}
		alogf("Filter error: %s\n", sln_strerror(count));
		return 500;
	}
	SLNFilterFree(&filter);

	uint64_t const t2 = uv_hrtime();

	str_t *reponame_HTMLSafe = htmlenc(SLNRepoGetName(blog->repo));

	snprintf(tmp, sizeof(tmp), "Queried in %.6f seconds", (t2-t1) / 1e9);
	str_t *querytime_HTMLSafe = htmlenc(tmp);

	str_t *account_HTMLSafe;
	if(0 == SLNSessionGetUserID(session)) {
		account_HTMLSafe = htmlenc("Log In");
	} else {
		strarg_t const user = SLNSessionGetUsername(session);
		snprintf(tmp, sizeof(tmp), "Account: %s", user);
		account_HTMLSafe = htmlenc(tmp);
	}


	// TODO: Write a real function for building query strings
	// Don't use ?: GNUism
	// Preserve other query parameters like `dir`
	str_t *query_encoded = !query ? NULL : QSEscape(query, strlen(query), true);
	FREE(&query);

	str_t *firstpage_HTMLSafe = NULL;
	str_t *prevpage_HTMLSafe = NULL;
	str_t *nextpage_HTMLSafe = NULL;
	str_t *lastpage_HTMLSafe = NULL;
	snprintf(tmp, sizeof(tmp), "?q=%s&start=-", query_encoded ?: "");
	firstpage_HTMLSafe = htmlenc(tmp);
	str_t *p = !count ? NULL : URIs[outdir > 0 ? 0 : count-1];
	str_t *n = !count ? NULL : URIs[outdir > 0 ? count-1 : 0];
	if(p) p = QSEscape(p, strlen(p), 1);
	if(n) n = QSEscape(n, strlen(n), 1);
	snprintf(tmp, sizeof(tmp), "?q=%s&start=%s", query_encoded ?: "", p ?: "");
	prevpage_HTMLSafe = htmlenc(tmp);
	snprintf(tmp, sizeof(tmp), "?q=%s&start=-%s", query_encoded ?: "", n ?: "");
	nextpage_HTMLSafe = htmlenc(tmp);
	snprintf(tmp, sizeof(tmp), "?q=%s", query_encoded ?: "");
	lastpage_HTMLSafe = htmlenc(tmp);

	FREE(&query_encoded);
	FREE(&p);
	FREE(&n);

	str_t *qs_HTMLSafe = htmlenc(qs);

	TemplateStaticArg const args[] = {
		{"reponame", reponame_HTMLSafe},
		{"querytime", querytime_HTMLSafe},
		{"account", account_HTMLSafe},
		{"query", query_HTMLSafe},
		{"parsed", parsed_HTMLSafe},
		{"firstpage", firstpage_HTMLSafe},
		{"prevpage", prevpage_HTMLSafe},
		{"nextpage", nextpage_HTMLSafe},
		{"lastpage", lastpage_HTMLSafe},
		{"qs", qs_HTMLSafe},
		{NULL, NULL},
	};

	if(count > 0) {
		HTTPConnectionWriteResponse(conn, 200, "OK");
	} else {
		HTTPConnectionWriteResponse(conn, 404, "Not Found");
	}
	HTTPConnectionWriteHeader(conn, "Content-Type", "text/html; charset=utf-8");
	HTTPConnectionWriteHeader(conn, "Transfer-Encoding", "chunked");
	if(0 == SLNSessionGetUserID(session)) {
		HTTPConnectionWriteHeader(conn, "Cache-Control", "no-cache, public");
	} else {
		HTTPConnectionWriteHeader(conn, "Cache-Control", "no-cache, private");
	}
	HTTPConnectionBeginBody(conn);
	TemplateWriteHTTPChunk(blog->header, &TemplateStaticCBs, args, conn);

	if(0 == count) {
		TemplateWriteHTTPChunk(blog->noresults, &TemplateStaticCBs, args, conn);
	}
	for(size_t i = 0; i < count; i++) {
		str_t algo[SLN_ALGO_SIZE]; // SLN_INTERNAL_ALGO
		str_t hash[SLN_HASH_SIZE];
		SLNParseURI(URIs[i], algo, hash);
		str_t *previewPath = BlogCopyPreviewPath(blog, hash);
		rc = send_preview(blog, conn, session, URIs[i], previewPath);
		FREE(&previewPath);
		if(rc < 0) break;
	}

	// TODO: HACK
	// Hide the pagination buttons when there are less than one full page of results.
	if(count >= max || has_start) {
		TemplateWriteHTTPChunk(blog->footer, &TemplateStaticCBs, args, conn);
	}

	FREE(&reponame_HTMLSafe);
	FREE(&querytime_HTMLSafe);
	FREE(&account_HTMLSafe);
	FREE(&query_HTMLSafe);
	FREE(&parsed_HTMLSafe);
	FREE(&firstpage_HTMLSafe);
	FREE(&prevpage_HTMLSafe);
	FREE(&nextpage_HTMLSafe);
	FREE(&lastpage_HTMLSafe);
	FREE(&qs_HTMLSafe);

	HTTPConnectionWriteChunkEnd(conn);
	HTTPConnectionEnd(conn);

	for(size_t i = 0; i < count; i++) FREE(&URIs[i]);
	assert_zeroed(URIs, count);
	return 0;
}
Exemple #20
0
static int import(SLNPullRef const pull, strarg_t const URI, size_t const pos, HTTPConnectionRef *const conn) {
	if(!pull) return 0;

	// TODO: Even if there's nothing to do, we have to enqueue something to fill up our reserved slots. I guess it's better than doing a lot of work inside the connection lock, but there's got to be a better way.
	SLNSubmissionRef sub = NULL;
	HTTPHeadersRef headers = NULL;

	if(!URI) goto enqueue;

	str_t algo[SLN_ALGO_SIZE];
	str_t hash[SLN_HASH_SIZE];
	if(SLNParseURI(URI, algo, hash) < 0) goto enqueue;

	int rc = SLNSessionGetFileInfo(pull->session, URI, NULL);
	if(rc >= 0) goto enqueue;
	db_assertf(DB_NOTFOUND == rc, "Database error: %s", sln_strerror(rc));

	// TODO: We're logging out of order when we do it like this...
//	alogf("Pulling %s\n", URI);

	if(!*conn) {
		rc = HTTPConnectionCreateOutgoing(pull->host, 0, conn);
		if(rc < 0) {
			alogf("Pull import connection error: %s\n", sln_strerror(rc));
			goto fail;
		}
	}

	str_t *path = aasprintf("/sln/file/%s/%s", algo, hash);
	if(!path) {
		alogf("Pull aasprintf error\n");
		goto fail;
	}
	rc = HTTPConnectionWriteRequest(*conn, HTTP_GET, path, pull->host);
	assert(rc >= 0); // TODO
	FREE(&path);

	HTTPConnectionWriteHeader(*conn, "Cookie", pull->cookie);
	HTTPConnectionBeginBody(*conn);
	rc = HTTPConnectionEnd(*conn);
	if(rc < 0) {
		alogf("Pull import request error: %s\n", sln_strerror(rc));
		goto fail;
	}
	int const status = HTTPConnectionReadResponseStatus(*conn);
	if(status < 0) {
		alogf("Pull import response error: %s\n", sln_strerror(status));
		goto fail;
	}
	if(status < 200 || status >= 300) {
		alogf("Pull import status error: %d\n", status);
		goto fail;
	}

	rc = HTTPHeadersCreateFromConnection(*conn, &headers);
	assert(rc >= 0); // TODO
/*	if(rc < 0) {
		alogf("Pull import headers error %s\n", sln_strerror(rc));
		goto fail;
	}*/
	strarg_t const type = HTTPHeadersGet(headers, "content-type");

	rc = SLNSubmissionCreate(pull->session, URI, &sub);
	if(rc < 0) {
		alogf("Pull submission error: %s\n", sln_strerror(rc));
		goto fail;
	}
	rc = SLNSubmissionSetType(sub, type);
	if(rc < 0) {
		alogf("Pull submission type error: %s\n", sln_strerror(rc));
		goto fail;
	}
	for(;;) {
		if(pull->stop) goto fail;
		uv_buf_t buf[1] = {};
		rc = HTTPConnectionReadBody(*conn, buf);
		if(rc < 0) {
			alogf("Pull download error: %s\n", sln_strerror(rc));
			goto fail;
		}
		if(0 == buf->len) break;
		rc = SLNSubmissionWrite(sub, (byte_t *)buf->base, buf->len);
		if(rc < 0) {
			alogf("Pull write error\n");
			goto fail;
		}
	}
	rc = SLNSubmissionEnd(sub);
	if(rc < 0) {
		alogf("Pull submission error: %s\n", sln_strerror(rc));
		goto fail;
	}

enqueue:
	HTTPHeadersFree(&headers);
	async_mutex_lock(pull->mutex);
	pull->queue[pos] = sub; sub = NULL;
	pull->filled[pos] = true;
	async_cond_broadcast(pull->cond);
	async_mutex_unlock(pull->mutex);
	return 0;

fail:
	HTTPHeadersFree(&headers);
	SLNSubmissionFree(&sub);
	HTTPConnectionFree(conn);
	return -1;
}
Exemple #21
0
static void writer(SLNPullRef const pull) {
	SLNSubmissionRef queue[QUEUE_SIZE];
	size_t count = 0;
	size_t skipped = 0;
	double time = uv_now(async_loop) / 1000.0;
	for(;;) {
		if(pull->stop) goto stop;

		async_mutex_lock(pull->mutex);
		while(0 == count || (count < QUEUE_SIZE && pull->count > 0)) {
			size_t const pos = pull->cur;
			while(!pull->filled[pos]) {
				async_cond_wait(pull->cond, pull->mutex);
				if(pull->stop) {
					async_mutex_unlock(pull->mutex);
					goto stop;
				}
				if(!count) time = uv_now(async_loop) / 1000.0;
			}
			assert(pull->filled[pos]);
			// Skip any bubbles in the queue.
			if(pull->queue[pos]) queue[count++] = pull->queue[pos];
			else skipped++;
			pull->queue[pos] = NULL;
			pull->filled[pos] = false;
			pull->cur = (pull->cur + 1) % QUEUE_SIZE;
			pull->count--;
			async_cond_broadcast(pull->cond);
		}
		async_mutex_unlock(pull->mutex);
		assert(count <= QUEUE_SIZE);

		for(;;) {
			int rc = SLNSubmissionStoreBatch(queue, count);
			if(rc >= 0) break;
			alogf("Submission error: %s (%d)\n", sln_strerror(rc), rc);
			async_sleep(1000 * 5);
		}
		for(size_t i = 0; i < count; ++i) {
			SLNSubmissionFree(&queue[i]);
		}

		double const now = uv_now(async_loop) / 1000.0;
		alogf("Pulled %f files per second\n", count / (now - time));
		time = now;
		count = 0;
		skipped = 0;

	}

stop:
	for(size_t i = 0; i < count; ++i) {
		SLNSubmissionFree(&queue[i]);
	}
	assert_zeroed(queue, count);

	async_mutex_lock(pull->mutex);
	assertf(pull->stop, "Writer ended early");
	assert(pull->tasks > 0);
	pull->tasks--;
	async_cond_broadcast(pull->cond);
	async_mutex_unlock(pull->mutex);
}
/*
 * Send a copy of the message, wait for a reply, and return the reply.
 *
 * The "reply" should already be initialized.
 *
 * This properly handles the calling thread's being canceled.
 */
int
http_xml_send(struct http_client *client, struct in_addr ip,
	u_int16_t port, int https, const char *urlpath, const char *username,
	const char *password, const char *ptag, const char *pattrs,
	const struct structs_type *ptype, const void *payload, int pflags,
	const char *rtag, char **rattrsp, const char *rattrs_mtype,
	const struct structs_type *rtype, void *reply, int rflags,
	structs_xmllog_t *rlogger)
{
	struct http_client_connection *cc;
	struct http_request *req;
	struct http_response *resp;
	int ret = -1;
	u_int code;
	FILE *fp;

	/* Get HTTP connection */
	if ((cc = http_client_connect(client, ip, port, https)) == NULL) {
		alogf(LOG_ERR, "can't %s for %s:%u: %m",
		    "get HTTP client" _ inet_ntoa(ip) _ port);
		return -1;
	}

	/* Push cleanup hook */
	pthread_cleanup_push(http_xml_send_cleanup, cc);

	/* Set up request */
	req = http_client_get_request(cc);
	if (http_request_set_method(req,
	    payload != NULL ? HTTP_METHOD_POST : HTTP_METHOD_GET) == -1) {
		alogf(LOG_ERR, "can't %s for %s:%u: %m",
		    "set method" _ inet_ntoa(ip) _ port);
		goto fail;
	}
	if (http_request_set_path(req, urlpath) == -1) {
		alogf(LOG_ERR, "can't %s for %s:%u: %m",
		    "set path" _ inet_ntoa(ip) _ port);
		goto fail;
	}
	if (http_request_set_header(req, 0, "Content-Type", "text/xml") == -1) {
		alogf(LOG_ERR, "can't %s for %s:%u: %m",
		    "set content-type" _ inet_ntoa(ip) _ port);
		goto fail;
	}
	if (username != NULL && password != NULL) {
		char *auth;

		if ((auth = http_request_encode_basic_auth(TYPED_MEM_TEMP,
		    username, password)) == NULL) {
			alogf(LOG_ERR, "can't %s for %s:%u: %m",
			    "encode authorization" _ inet_ntoa(ip) _ port);
			goto fail;
		}
		if (http_request_set_header(req, 0, "Authorization",
		    "Basic %s", auth) == -1) {
			alogf(LOG_ERR, "can't %s for %s:%u: %m",
			    "set authorization header" _ inet_ntoa(ip) _ port);
			FREE(TYPED_MEM_TEMP, auth);
			goto fail;
		}
		FREE(TYPED_MEM_TEMP, auth);
	}

	/* Write XML data to HTTP client output stream */
	if (payload != NULL) {
		if ((fp = http_request_get_output(req, 1)) == NULL) {
			alogf(LOG_ERR, "can't %s for %s:%u: %m",
			    "get output" _ inet_ntoa(ip) _ port);
			goto fail;
		}
		if (structs_xml_output(ptype,
		    ptag, pattrs, payload, fp, NULL, pflags) == -1) {
			alogf(LOG_ERR, "can't %s for %s:%u: %m",
			    "write XML" _ inet_ntoa(ip) _ port);
			goto fail;
		}
	}

	/* Get response */
	if ((resp = http_client_get_response(cc)) == NULL) {
		alogf(LOG_ERR, "can't %s for %s:%u: %m",
		    "get response" _ inet_ntoa(ip) _ port);
		goto fail;
	}
	if ((code = http_response_get_code(resp)) != HTTP_STATUS_OK) {
		alogf(LOG_ERR, "rec'd HTTP error code %d from"
		      "http%s://%s:%u%s: %s", code _ https ? "s" : "" _
		      inet_ntoa(ip) _ port _ urlpath _
		      http_response_status_msg(code));
		goto fail;
	}

	/* Read XML reply from client input stream */
	if ((fp = http_response_get_input(resp)) == NULL) {
		alogf(LOG_ERR, "can't %s for %s:%u: %m",
		    "get input" _ inet_ntoa(ip) _ port);
		goto fail;
	}
	if (structs_xml_input(rtype, rtag, rattrsp,
	    rattrs_mtype, fp, reply, rflags, rlogger) == -1) {
		alogf(LOG_ERR, "can't %s for %s:%u: %m",
		    "read XML reply" _ inet_ntoa(ip) _ port);
		goto fail;
	}

	/* OK */
	ret = 0;

fail:;
	/* Done */
	pthread_cleanup_pop(1);
	return (ret);
}