#include <json.h>
-#include <krb5/krb5.h>
-
#include <systemd/sd-bus.h>
#include <systemd/sd-daemon.h>
#include <systemd/sd-event.h>
#define PAKFIRE_STATS_WHEN_BUSY S_TO_US(15)
#define PAKFIRE_STATS_WHEN_IDLE S_TO_US(60)
-// Store all Kerberos credentials in memory
-#define KRB5_CREDENTIALS_CACHE "MEMORY:"
-
-// The default keytab
-#define KRB5_DEFAULT_KEYTAB "/etc/krb5.keytab"
-
#define MAX_JOBS 64
struct pakfire_daemon {
// URL
char url[PATH_MAX];
- // Kerberos Keytab
- char keytab[PATH_MAX];
-
// Event Loop
sd_event* loop;
sd_bus* bus;
int inhibitfd;
- // 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;
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);
-
- // 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;
- char time[128];
- char* p = NULL;
- int r;
DEBUG(daemon->ctx, "Authenticating...\n");
- // Resolve the keytab
- r = krb5_kt_resolve(daemon->krb5.ctx, daemon->keytab, &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) {
- switch (r) {
- // If we could not reach the KDC, we will try again after one minute
- case KRB5_KDC_UNREACH:
- DEBUG(daemon->ctx, "Failed to contact the KDC. Retrying in 60 seconds...\n");
-
- // Reset the timer
- r = sd_event_source_set_time_relative(daemon->auth_timer, S_TO_US(60));
- if (r < 0) {
- ERROR(daemon->ctx, "Could not reset the auth timer: %s\n", strerror(-r));
- goto ERROR;
- }
-
- // Reset r
- r = 0;
- goto ERROR;
-
- // Fail for everything else
- default:
- 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);
-
- // Destroy the content of the cache
- if (daemon->krb5.ccache) {
- r = krb5_cc_destroy(daemon->krb5.ctx, daemon->krb5.ccache);
- if (r) {
- error = krb5_get_error_message(daemon->krb5.ctx, r);
-
- ERROR(daemon->ctx, "Failed to destroy the credentials cache: %s\n", error);
- r = -ENOTSUP;
- goto ERROR;
- }
- }
-
- // 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);
- r = -EINVAL;
- 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);
- r = -EINVAL;
- goto ERROR;
- }
-
- // 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);
- r = -EINVAL;
- goto ERROR;
- }
-
- // Refresh one hour before the expiry time
- uint64_t t_refresh = S_TO_US(t_end - 3600);
-
- // 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;
- }
-
-ERROR:
- if (error)
- krb5_free_error_message(daemon->krb5.ctx, error);
- if (keytab)
- krb5_kt_close(daemon->krb5.ctx, keytab);
-
- return r;
+ return 0;
}
static int pakfire_daemon_prepare_for_shutdown(
static int pakfire_daemon_configure(struct pakfire_daemon* daemon) {
struct pakfire_config* config = NULL;
- const char* keytab = NULL;
const char* url = NULL;
int r;
if (r < 0)
goto ERROR;
- // Fetch the keytab
- keytab = pakfire_config_get(config, "daemon", "keytab", KRB5_DEFAULT_KEYTAB);
-
- // Store the keytab
- r = pakfire_string_set(daemon->keytab, keytab);
- if (r < 0)
- goto ERROR;
-
ERROR:
if (config)
pakfire_config_unref(config);
// Release shutdown inhibition
pakfire_daemon_release_inhibit_shutdown(daemon);
- if (daemon->krb5.principal)
- krb5_free_principal(daemon->krb5.ctx, daemon->krb5.principal);
- if (daemon->krb5.ccache)
- krb5_cc_destroy(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)
if (r < 0)
goto ERROR;
- // Setup Kerberos Authentication
- r = pakfire_daemon_setup_krb5(d);
- if (r)
- goto ERROR;
-
// Connect to the build service
r = pakfire_client_create(&d->client, d->ctx, d->url);
if (r < 0)