diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 40 | ||||
-rw-r--r-- | avahi/paccache.service | 15 | ||||
-rw-r--r-- | pacredir.c | 316 | ||||
-rw-r--r-- | systemd/pacredir.service | 9 | ||||
-rw-r--r-- | systemd/pacserve.service | 9 |
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 |