summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--Makefile40
-rw-r--r--avahi/paccache.service15
-rw-r--r--pacredir.c316
-rw-r--r--systemd/pacredir.service9
-rw-r--r--systemd/pacserve.service9
6 files changed, 393 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e21dcd7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*~
+*.o
+pacredir
+README.html
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8ee2e14
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+# pacserve - serve pacman cache and redirect via avahi service
+
+CC := gcc
+MD := markdown
+INSTALL := install
+RM := rm
+CFLAGS += -O2 -Wall -Werror
+CFLAGS += $(shell pkg-config --libs --cflags libcurl)
+CFLAGS += $(shell pkg-config --libs --cflags avahi-client)
+CFLAGS += $(shell pkg-config --libs --cflags libmicrohttpd)
+VERSION := $(shell git describe --tags --long 2>/dev/null)
+# this is just a fallback in case you do not use git but downloaded
+# a release tarball...
+ifeq ($(VERSION),)
+VERSION := 0.0.1
+endif
+
+all: pacredir README.html
+
+pacredir: pacredir.c
+ $(CC) $(CFLAGS) -o pacredir pacredir.c \
+ -DVERSION="\"$(VERSION)\""
+
+README.html: README.md
+ $(MD) README.md > README.html
+
+install: install-bin install-doc
+
+install-bin: pacredir
+ $(INSTALL) -D -m0755 pacredir $(DESTDIR)/usr/bin/pacredir
+ $(INSTALL) -D -m0755 avahi/pacserve.service $(DESTDIR)/etc/avahi/services/pacserve.service
+ $(INSTALL) -D -m0755 systemd/pacserve.service $(DESTDIR)/usr/lib/systemd/system/pacserve.service
+ $(INSTALL) -D -m0755 systemd/pacredir.service $(DESTDIR)/usr/lib/systemd/system/pacredir.service
+
+install-doc: README.html
+ $(INSTALL) -D -m0644 README.md $(DESTDIR)/usr/share/doc/pacserve/README.md
+ $(INSTALL) -D -m0644 README.html $(DESTDIR)/usr/share/doc/pacserve/README.html
+
+clean:
+ $(RM) -f *.o *~ README.html pacredir
diff --git a/avahi/paccache.service b/avahi/paccache.service
new file mode 100644
index 0000000..568d08d
--- /dev/null
+++ b/avahi/paccache.service
@@ -0,0 +1,15 @@
+<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
+<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
+
+<!-- See avahi.service(5) for more information about this configuration file -->
+
+<service-group>
+
+ <name replace-wildcards="yes">%h</name>
+
+ <service>
+ <type>_pacserve._tcp</type>
+ <port>7078</port>
+ </service>
+
+</service-group>
diff --git a/pacredir.c b/pacredir.c
new file mode 100644
index 0000000..2025f04
--- /dev/null
+++ b/pacredir.c
@@ -0,0 +1,316 @@
+/*
+ * (C) 2013 by Christian Hesse <mail@eworm.de>
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU General Public License, incorporated herein by reference.
+ *
+ * This is an example code skeleton provided by vim-skeleton.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <avahi-client/client.h>
+#include <avahi-client/lookup.h>
+#include <avahi-common/simple-watch.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/error.h>
+
+#include <curl/curl.h>
+#include <microhttpd.h>
+
+#define PAGE307 "<html><head><title>307 temporary redirect</title>" \
+ "</head><body>307 temporary redirect: " \
+ "<a href=\"%s\">%s</a></body></html>"
+#define PAGE404 "<html><head><title>404 Not Found</title>" \
+ "</head><body>404 Not Found: %s</body></html>"
+#define PORT 7077
+#define SERVICE "_pacserve._tcp"
+
+/* is there any other way than storing things in global struct? */
+struct hosts {
+ char * host;
+ uint16_t port;
+ struct hosts * next;
+};
+
+/* global variables */
+struct hosts * hosts = NULL;
+static AvahiSimplePoll *simple_poll = NULL;
+
+static void resolve_callback(AvahiServiceResolver *r, AVAHI_GCC_UNUSED AvahiIfIndex interface, AVAHI_GCC_UNUSED AvahiProtocol protocol,
+ AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host, const AvahiAddress *address,
+ AVAHI_GCC_UNUSED uint16_t port, AVAHI_GCC_UNUSED AvahiStringList *txt, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
+ AVAHI_GCC_UNUSED void* userdata) {
+ struct hosts * tmphosts = hosts;
+
+ assert(r);
+
+ /* Called whenever a service has been resolved successfully or timed out */
+
+ switch (event) {
+ case AVAHI_RESOLVER_FAILURE:
+ fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n",
+ name, type, domain, avahi_strerror(avahi_client_errno(avahi_service_resolver_get_client(r))));
+ break;
+
+ case AVAHI_RESOLVER_FOUND: {
+ // char a[AVAHI_ADDRESS_STR_MAX];
+
+ // avahi_address_snprint(a, sizeof(a), address);
+ // fprintf(stderr, "%s %s\n", host, a);
+
+ while (tmphosts->host != NULL) {
+ if (strcmp(tmphosts->host, host) == 0) {
+# if defined DEBUG
+ printf("Host is already in the list: %s\n", host);
+# endif
+ goto out;
+ }
+ tmphosts = tmphosts->next;
+ }
+ printf("Adding host: %s, port %d\n", host, port);
+ tmphosts->host = strdup(host);
+ tmphosts->port = 15678;
+ tmphosts->next = realloc(tmphosts->next, sizeof(struct hosts));
+ tmphosts = tmphosts->next;
+ tmphosts->host = NULL;
+ tmphosts->next = NULL;
+ }
+ }
+
+out:
+ avahi_service_resolver_free(r);
+}
+
+static void browse_callback(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name,
+ const char *type, const char *domain, AVAHI_GCC_UNUSED AvahiLookupResultFlags flags, void* userdata) {
+ AvahiClient *c = userdata;
+
+ assert(b);
+
+ /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
+
+ switch (event) {
+ case AVAHI_BROWSER_FAILURE:
+
+ fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
+ avahi_simple_poll_quit(simple_poll);
+ return;
+
+ case AVAHI_BROWSER_NEW:
+ // fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
+
+ /* We ignore the returned resolver object. In the callback
+ * function we free it. If the server is terminated before
+ * the callback function is called the server will free
+ * the resolver for us. */
+
+ if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c)))
+ fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
+
+ break;
+
+ case AVAHI_BROWSER_REMOVE:
+ // fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
+
+ /* TODO: remove host */
+ // if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c)))
+ // fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_client_errno(c)));
+
+ break;
+
+ case AVAHI_BROWSER_ALL_FOR_NOW:
+ case AVAHI_BROWSER_CACHE_EXHAUSTED:
+ // fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
+ break;
+ }
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
+ assert(c);
+
+ /* Called whenever the client or server state changes */
+
+ if (state == AVAHI_CLIENT_FAILURE) {
+ fprintf(stderr, "Server connection failure: %s\n", avahi_strerror(avahi_client_errno(c)));
+ avahi_simple_poll_quit(simple_poll);
+ }
+}
+
+int get_content(const char * host, const uint16_t port, const char * url) {
+ CURL *curl;
+ CURLcode res;
+ unsigned int http_code;
+
+ curl_global_init(CURL_GLOBAL_ALL);
+
+ if ((curl = curl_easy_init()) != NULL) {
+ curl_easy_setopt(curl, CURLOPT_URL, url);
+ /* example.com is redirected, so we tell libcurl to follow redirection */
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+ /* set user agent */
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, "pacredir/" VERSION);
+ /* do not receive body */
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
+
+ /* get it! */
+ if (curl_easy_perform(curl) != CURLE_OK) {
+ fprintf(stderr, "Could not connect to server %s on port %d.\n", host, port);
+ /* TODO remove or disable */
+ return -1;
+ }
+
+ if ((res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code)) != CURLE_OK) {
+ fprintf(stderr, "curl_easy_getinfo() failed: %s\n", curl_easy_strerror(res));
+ return -1;
+ }
+
+ // printf("%s: %d\n", url, http_code);
+
+ /* always cleanup */
+ curl_easy_cleanup(curl);
+ }
+
+ /* we're done with libcurl, so clean it up */
+ curl_global_cleanup();
+
+ return http_code;
+}
+
+static int ahc_echo(void * cls, struct MHD_Connection * connection, const char * uri, const char * method,
+ const char * version, const char * upload_data, size_t * upload_data_size, void ** ptr) {
+ static int dummy;
+ struct MHD_Response * response;
+ int ret;
+ struct hosts * tmphosts = hosts;
+
+ char * url = NULL, * page;
+ const char * basename;
+ int http_code = 0;
+
+ /* we want to filename, not the path */
+ basename = uri;
+ while (strstr(basename, "/") != NULL)
+ basename = strstr(basename, "/") + 1;
+
+ if (strcmp(method, "GET") != 0)
+ return MHD_NO; /* unexpected method */
+ if (&dummy != *ptr) {
+ /* The first time only the headers are valid,
+ * do not respond in the first round... */
+ *ptr = &dummy;
+ return MHD_YES;
+ }
+ if (*upload_data_size != 0)
+ return MHD_NO; /* upload data in a GET!? */
+ *ptr = NULL; /* clear context pointer */
+
+ /* try to find a server */
+ while (tmphosts->host != NULL) {
+ url = malloc(10 + strlen(tmphosts->host) + 5 + strlen(basename));
+ sprintf(url, "http://%s:%d/%s", tmphosts->host, tmphosts->port, basename);
+
+ printf("Trying %s\n", url);
+ if ((http_code = get_content(tmphosts->host, tmphosts->port, url)) == MHD_HTTP_OK) {
+ break;
+ }
+
+ tmphosts = tmphosts->next;
+ }
+
+ /* give response */
+ if (http_code == MHD_HTTP_OK) {
+ printf("Redirecting to %s\n", url);
+ page = malloc(strlen(PAGE307) + strlen(url) + strlen(basename) + 1);
+ sprintf(page, PAGE307, url, basename);
+ response = MHD_create_response_from_data(strlen(url), (void*) url, MHD_NO, MHD_NO);
+ ret = MHD_add_response_header(response, "Location", url);
+ ret = MHD_queue_response(connection, MHD_HTTP_TEMPORARY_REDIRECT, response);
+ } else {
+ printf("File %s Not found, giving up.\n", basename);
+ page = malloc(strlen(PAGE404) + strlen(basename) + 1);
+ sprintf(page, PAGE404, basename);
+ response = MHD_create_response_from_data(strlen(page), (void*) page, MHD_NO, MHD_NO);
+ ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
+ }
+ MHD_destroy_response(response);
+
+ free(page);
+ if (url != NULL)
+ free(url);
+
+ return ret;
+}
+
+int main(int argc, char ** argv) {
+ AvahiClient *client = NULL;
+ AvahiServiceBrowser *sb = NULL;
+ int error;
+ int ret = 1;
+ struct MHD_Daemon * mhd;
+
+ printf("Starting pacredir/" VERSION "\n");
+
+ /* allocate first struct element as dummy */
+ hosts = malloc(sizeof(struct hosts));
+ hosts->host = NULL;
+ hosts->port = 0;
+ hosts->next = NULL;
+
+ /* Allocate main loop object */
+ if (!(simple_poll = avahi_simple_poll_new())) {
+ fprintf(stderr, "Failed to create simple poll object.\n");
+ goto fail;
+ }
+
+ /* Allocate a new client */
+ client = avahi_client_new(avahi_simple_poll_get(simple_poll), 0, client_callback, NULL, &error);
+
+ /* Check wether creating the client object succeeded */
+ if (!client) {
+ fprintf(stderr, "Failed to create client: %s\n", avahi_strerror(error));
+ goto fail;
+ }
+
+ /* Create the service browser */
+ if (!(sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, SERVICE, NULL, 0, browse_callback, client))) {
+ fprintf(stderr, "Failed to create service browser: %s\n", avahi_strerror(avahi_client_errno(client)));
+ goto fail;
+ }
+
+ if ((mhd = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL, &ahc_echo, NULL, MHD_OPTION_END)) == NULL) {
+ fprintf(stderr, "Could not start daemon on port %d.\n", PORT);
+ return EXIT_FAILURE;
+ }
+
+ /* Run the main loop */
+ avahi_simple_poll_loop(simple_poll);
+
+ MHD_stop_daemon(mhd);
+
+ ret = EXIT_SUCCESS;
+
+fail:
+
+ /* Cleanup things */
+ if (sb)
+ avahi_service_browser_free(sb);
+
+ if (client)
+ avahi_client_free(client);
+
+ if (simple_poll)
+ avahi_simple_poll_free(simple_poll);
+
+ return ret;
+}
+
+// vim: set syntax=c:
diff --git a/systemd/pacredir.service b/systemd/pacredir.service
new file mode 100644
index 0000000..fb4feba
--- /dev/null
+++ b/systemd/pacredir.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Darkhttpd to serve pacman cache
+After=avahi-daemon.service
+
+[Service]
+ExecStart=/usr/bin/pacredir
+
+[Install]
+WantedBy=multi-user.target
diff --git a/systemd/pacserve.service b/systemd/pacserve.service
new file mode 100644
index 0000000..76bdc77
--- /dev/null
+++ b/systemd/pacserve.service
@@ -0,0 +1,9 @@
+[Unit]
+Description=Darkhttpd to serve pacman cache
+After=network.target
+
+[Service]
+ExecStart=/usr/bin/darkhttpd /var/cache/pacman/pkg/ --port 7078
+
+[Install]
+WantedBy=multi-user.target