#include <errno.h>
#include <stdlib.h>
+#include <krb5/krb5.h>
+
#include <systemd/sd-daemon.h>
#include <systemd/sd-event.h>
#include <pakfire/string.h>
#include <pakfire/util.h>
+// It would be nice store the credentials cache in memory, but on Debian,
+// cURL is compiled with Heimdal which is incompatible with the KRB5 in-memory cache.
+// The on-disk format is however is compatible between two implementations.
+#define KRB5_CREDENTIALS_CACHE "FILE:/tmp/krb5cc_pakfire-daemon"
+
+// The default keytab
+#define KRB5_DEFAULT_KEYTAB "/etc/krb5.keytab"
+
#define MAX_JOBS 64
struct pakfire_daemon {
// Event Loop
sd_event* loop;
+ // Kerberos Authentication
+ struct {
+ // Context
+ krb5_context ctx;
+
+ // Credentials Cache
+ krb5_ccache ccache;
+
+ // Host Princial
+ krb5_principal principal;
+ } krb5;
+
+ // Authentication Timer
+ sd_event_source* auth_timer;
+
// The control connection
struct pakfire_xfer* control;
return 0;
}
+static int pakfire_daemon_setup_krb5(struct pakfire_daemon* daemon) {
+ char hostname[HOST_NAME_MAX];
+ const char* error = NULL;
+ char* name = NULL;
+ int r;
+
+ // Fetch our own hostname
+ r = gethostname(hostname, sizeof(hostname));
+ if (r < 0) {
+ ERROR(daemon->ctx, "Could not fetch hostname: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ // Setup a Kerberos context
+ r = krb5_init_context(&daemon->krb5.ctx);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not initialize Kerberos: %s\n", error);
+ goto ERROR;
+ }
+
+ // Make the principal
+ r = krb5_sname_to_principal(daemon->krb5.ctx,
+ hostname, "host", KRB5_NT_SRV_HST, &daemon->krb5.principal);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not generate the host principal: %s\n", error);
+ goto ERROR;
+ }
+
+ // Print the principal name
+ r = krb5_unparse_name(daemon->krb5.ctx, daemon->krb5.principal, &name);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not print the host principal: %s\n", error);
+ goto ERROR;
+ }
+
+ DEBUG(daemon->ctx, "Authenticating as: %s\n", name);
+
+ // Create a new credentials cache
+ r = krb5_cc_resolve(daemon->krb5.ctx, KRB5_CREDENTIALS_CACHE, &daemon->krb5.ccache);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not resolve the credentials cache: %s\n", error);
+ goto ERROR;
+ }
+
+ // Initialize the cache for the principal
+ r = krb5_cc_initialize(daemon->krb5.ctx, daemon->krb5.ccache, daemon->krb5.principal);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not initialize the credentials cache: %s\n", error);
+ goto ERROR;
+ }
+
+ // Set the credentials cache environment variable
+ r = setenv("KRB5CCNAME", KRB5_CREDENTIALS_CACHE, 1);
+ if (r < 0) {
+ ERROR(daemon->ctx, "Could not set KRB5CCNAME: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ERROR:
+ if (error)
+ krb5_free_error_message(daemon->krb5.ctx, error);
+ if (name)
+ krb5_free_unparsed_name(daemon->krb5.ctx, name);
+
+ return r;
+}
+
+static int pakfire_daemon_auth(sd_event_source* s, uint64_t usec, void* data) {
+ struct pakfire_daemon* daemon = data;
+ krb5_get_init_creds_opt* options = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_creds creds = {};
+ const char* error = NULL;
+ const char* path = NULL;
+ char time[128];
+ char* p = NULL;
+ int r;
+
+ DEBUG(daemon->ctx, "Authenticating...\n");
+
+ // XXX We should read this from the configuration
+ path = KRB5_DEFAULT_KEYTAB;
+
+ // Resolve the keytab
+ r = krb5_kt_resolve(daemon->krb5.ctx, path, &keytab);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not resolve the keytab: %s\n", error);
+ goto ERROR;
+ }
+
+ // Fetch the credentials using the keytab
+ r = krb5_get_init_creds_keytab(daemon->krb5.ctx, &creds, daemon->krb5.principal,
+ keytab, 0, NULL, options);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not fetch credentials: %s\n", error);
+ goto ERROR;
+ }
+
+ // Determine the end time
+ time_t t_end = creds.times.endtime;
+
+ // Format the expiry time
+ p = ctime_r(&t_end, time);
+ if (!p) {
+ ERROR(daemon->ctx, "Could not format the expiry time: %m\n");
+ r = -errno;
+ goto ERROR;
+ }
+
+ DEBUG(daemon->ctx, "Successfully fetched credentials\n");
+ DEBUG(daemon->ctx, " Expires: %s\n", time);
+
+ // Store the credentials in the cache
+ r = krb5_cc_store_cred(daemon->krb5.ctx, daemon->krb5.ccache, &creds);
+ if (r) {
+ error = krb5_get_error_message(daemon->krb5.ctx, r);
+
+ ERROR(daemon->ctx, "Could not store credentials: %s\n", error);
+ goto ERROR;
+ }
+
+ // Refresh one hour before the expiry time
+ uint64_t t_refresh = (t_end - 3600) * 1000000;
+
+ // Authenticate again just before the credentials expire
+ r = sd_event_source_set_time(daemon->auth_timer, t_refresh);
+ if (r < 0) {
+ ERROR(daemon->ctx, "Could not reset the auth timer: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ // Turn on the connection timer
+ r = sd_event_source_set_enabled(daemon->connect_timer, SD_EVENT_ONESHOT);
+ if (r < 0) {
+ ERROR(daemon->ctx, "Could not enable the connection timer: %s\n", strerror(-r));
+ goto ERROR;
+ }
+
+ /*
+ XXX This function needs some better error handling. In case the communication
+ with the Kerberos server fails, we should reschedule a call very soon (maybe
+ within a minute) and once the credentials have expired, we should stop the
+ (re-)connection timer.
+ */
+
+ERROR:
+ if (error)
+ krb5_free_error_message(daemon->krb5.ctx, error);
+ if (keytab)
+ krb5_kt_close(daemon->krb5.ctx, keytab);
+
+ return r;
+}
+
static int pakfire_daemon_setup_loop(struct pakfire_daemon* daemon) {
int r;
return r;
}
+ // Setup the authentication timer
+ r = sd_event_add_time_relative(daemon->loop, &daemon->auth_timer,
+ CLOCK_MONOTONIC, 0, 0, pakfire_daemon_auth, daemon);
+ if (r < 0) {
+ ERROR(daemon->ctx, "Could not register the authentication timer: %s\n", strerror(-r));
+ return r;
+ }
+
+ // Authenticate continuously
+ r = sd_event_source_set_enabled(daemon->auth_timer, SD_EVENT_ON);
+ if (r < 0) {
+ ERROR(daemon->ctx, "Could not activate the auth timer: %s\n", strerror(-r));
+ return r;
+ }
+
// Setup the reconnection timer
r = sd_event_add_time_relative(daemon->loop, &daemon->connect_timer,
CLOCK_MONOTONIC, 0, 0, pakfire_daemon_connect, daemon);
return r;
}
+ // Disable the reconnection timer until we are authenticated
+ r = sd_event_source_set_enabled(daemon->connect_timer, SD_EVENT_OFF);
+ if (r < 0) {
+ ERROR(daemon->ctx, "Could not disable the connection timer: %s\n", strerror(-r));
+ return r;
+ }
+
return 0;
}
}
static void pakfire_daemon_free(struct pakfire_daemon* daemon) {
+ if (daemon->krb5.principal)
+ krb5_free_principal(daemon->krb5.ctx, daemon->krb5.principal);
+ if (daemon->krb5.ccache)
+ krb5_cc_close(daemon->krb5.ctx, daemon->krb5.ccache);
+ if (daemon->krb5.ctx)
+ krb5_free_context(daemon->krb5.ctx);
+
+ if (daemon->auth_timer)
+ sd_event_source_unref(daemon->auth_timer);
if (daemon->connect_timer)
sd_event_source_unref(daemon->connect_timer);
if (daemon->stats_timer)
if (r)
goto ERROR;
+ // Setup Kerberos Authentication
+ r = pakfire_daemon_setup_krb5(d);
+ if (r)
+ goto ERROR;
+
// Create the cgroup
r = pakfire_cgroup_create(&d->cgroup, d->ctx, NULL, "pakfire-daemon", 0);
if (r < 0)