diff options
author | fergus.henderson <fergushenderson@users.noreply.github.com> | 2009-07-24 16:59:25 +0000 |
---|---|---|
committer | fergus.henderson <fergushenderson@users.noreply.github.com> | 2009-07-24 16:59:25 +0000 |
commit | 6036a4211ca10e55545eac0df37f78c5a7e63a39 (patch) | |
tree | cd70163e266b7dcc0aaab0e74c3fe9f7a84ecc1f | |
parent | 8f268d30d81e54814d8036fbee59d25020682f60 (diff) | |
download | distcc-git-6036a4211ca10e55545eac0df37f78c5a7e63a39.tar.gz |
Apply patch from Ian.Baker@cern.ch:
Optional Black/Whitelist Functionality.
This patch is used to implement optional server-side access control
through a specified black or whitelist file. This option is specified
through a command line option.
Revised by me (Fergus Henderson):
fixed spelling error, added a comment.
-rw-r--r-- | man/distccd.1 | 14 | ||||
-rw-r--r-- | src/auth.h | 2 | ||||
-rw-r--r-- | src/auth_common.c | 2 | ||||
-rw-r--r-- | src/auth_distcc.c | 5 | ||||
-rw-r--r-- | src/auth_distccd.c | 242 | ||||
-rw-r--r-- | src/daemon.c | 11 | ||||
-rw-r--r-- | src/dopt.c | 41 | ||||
-rw-r--r-- | src/dopt.h | 7 | ||||
-rw-r--r-- | src/dparent.c | 4 | ||||
-rw-r--r-- | src/dsignal.c | 4 |
10 files changed, 322 insertions, 10 deletions
diff --git a/man/distccd.1 b/man/distccd.1 index ec1b007..e6b0342 100644 --- a/man/distccd.1 +++ b/man/distccd.1 @@ -228,6 +228,20 @@ Displays the name of the distccd security principal extracted from the environment. .B This option is only available if distccd was compiled with .B the --with-auth configure option. +.TP +.B --blacklist=FILE +Instruct distccd to reject connections from users whose principal names +are listed in FILE. +.B This option is only available if distccd was compiled with +.B the --with-auth configure option and if distccd is run with the +.B --auth option. +.TP +.B --whitelist=FILE +Instruct distccd to accept connections from users whose principal names +are listed in FILE. +.B This option is only available if distccd was compiled with +.B the --with-auth configure option and if distccd is run with the +.B --auth option. .SH "SEARCH PATHS" .PP distcc can pass either a relative or an absolute name for the compiler @@ -29,6 +29,8 @@ int dcc_gssapi_acquire_credentials(void); void dcc_gssapi_release_credentials(void); +int dcc_gssapi_obtain_list(int mode); +void dcc_gssapi_free_list(void); int dcc_gssapi_check_client(int to_net_fd, int from_net_fd); int dcc_gssapi_perform_requested_security(int to_net_fd, int from_net_fd); diff --git a/src/auth_common.c b/src/auth_common.c index 9d9d6ef..471691b 100644 --- a/src/auth_common.c +++ b/src/auth_common.c @@ -44,7 +44,7 @@ * * @param status_type. The type of the status code, either GSS_C_GSS_CODE * for a GSS-API status code or GSS_C_MECH_CODE - * for a mechanism specific status code. + * for a mechanism-specific status code. */ void dcc_gssapi_status_to_log(OM_uint32 status_code, int status_type) { gss_buffer_desc status_string = GSS_C_EMPTY_BUFFER; diff --git a/src/auth_distcc.c b/src/auth_distcc.c index ebd0d0e..4e3947c 100644 --- a/src/auth_distcc.c +++ b/src/auth_distcc.c @@ -353,8 +353,9 @@ static int dcc_gssapi_recv_notification(int sd) { } if (notification != ACCESS) { - rs_log_crit("Access denied by server."); - return EXIT_ACCESS_DENIED; + rs_log_crit("Access denied by server."); + rs_log_info("Your principal may be blacklisted or may not be whitelisted."); + return EXIT_ACCESS_DENIED; } rs_log_info("Access granted by server."); diff --git a/src/auth_distccd.c b/src/auth_distccd.c index 5c1639f..a26d168 100644 --- a/src/auth_distccd.c +++ b/src/auth_distccd.c @@ -24,6 +24,7 @@ #include <arpa/inet.h> #endif +#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -35,12 +36,21 @@ #include "netutil.h" #include "trace.h" +/*Maximum length of principal name in black/white list.*/ +#define MAX_NAME_LENGTH 50 +/*Key not found during binary search*/ +#define KEY_NOT_FOUND -1 + static int dcc_gssapi_accept_secure_context(int to_net_sd, int from_net_sd, OM_uint32 *ret_flags, char **principal); static int dcc_gssapi_recv_handshake(int from_net_sd, int to_net_sd); +static int dcc_gssapi_check_list(char *principal, int sd); +static int dcc_gssapi_bin_search(char *key); static int dcc_gssapi_notify_client(int sd, char status); +static int dcc_gssapi_compare_strings(const void *string_one, + const void *string_two); /*Global credentials so they're only required and released once*/ /*in the most suitable place.*/ @@ -48,6 +58,11 @@ gss_cred_id_t creds; /*Global security context in case other services*/ /*are implemented in the future.*/ gss_ctx_id_t distccd_ctx_handle = GSS_C_NO_CONTEXT; +/*Global sorted list of principal names from either a specified*/ +/*blacklist or a whitelist available to all children*/ +char **list = NULL; +/*Global count of the number of principal names in the sorted list.*/ +int list_count = 0; /* * Perform any requested security. @@ -80,11 +95,25 @@ int dcc_gssapi_check_client(int to_net_sd, int from_net_sd) { return ret; } - rs_log_info("Notifying client."); + if (opt_blacklist_enabled || opt_whitelist_enabled) { + rs_log_info("Checking %s against %slist %s.", + principal, + (opt_blacklist_enabled) ? "black" : "white", + arg_list_file); + if ((ret = dcc_gssapi_check_list(principal, to_net_sd)) != 0) { + dcc_gssapi_delete_ctx(&distccd_ctx_handle); + free(principal); + return ret; + } - if ((ret = dcc_gssapi_notify_client(to_net_sd, ACCESS)) != 0) { - dcc_gssapi_delete_ctx(&distccd_ctx_handle); - return ret; + free(principal); + } else { + rs_log_info("Notifying client."); + + if ((ret = dcc_gssapi_notify_client(to_net_sd, ACCESS)) != 0) { + dcc_gssapi_delete_ctx(&distccd_ctx_handle); + return ret; + } } return 0; @@ -246,6 +275,92 @@ static int dcc_gssapi_recv_handshake(int from_net_sd, int to_net_sd) { } /* + * Check the name of the connecting client principal against the sorted + * list of principal names using a binary search to determine access + * rights depending upon the type of list used. The client is then + * notified of the outcome. + * + * @param principal. The name of the connecting client principal. + * + * @param sd. Socket to write notification to. + * + * Returns 0 on success, otherwise access deinied. + */ +static int dcc_gssapi_check_list(char *principal, int sd) { + char *pos = NULL; + int location, ret; + + if ((pos = strchr(principal, '@')) != NULL) { + *pos = '\0'; + } + + location = dcc_gssapi_bin_search(principal); + + if (opt_blacklist_enabled) { /*blacklist*/ + if (location >= 0) { + rs_log_info("Access denied - %s blacklisted.", principal); + rs_log_info("Notifying client."); + dcc_gssapi_notify_client(sd, NO_ACCESS); + return EXIT_GSSAPI_FAILED; + } else { + rs_log_info("Access granted - %s not blacklisted.", principal); + rs_log_info("Notifying client."); + + if ((ret = dcc_gssapi_notify_client(sd, ACCESS)) != 0) { + return ret; + } + + return 0; + } + } else { /*whitelist*/ + if (location >= 0) { + rs_log_info("Access granted - %s whitelisted.", principal); + rs_log_info("Notifying client."); + + if ((ret = dcc_gssapi_notify_client(sd, ACCESS)) != 0) { + return ret; + } + + return 0; + } else { + rs_log_info("Access denied - %s not whitelisted.", principal); + rs_log_info("Notifying client."); + dcc_gssapi_notify_client(sd, NO_ACCESS); + return EXIT_GSSAPI_FAILED; + } + } +} + +/* + * Perform a binary search on a sorted list for a key. + * + * @param key. The search key. + * + * Returns index if key in list, otherwise + * KEY_NOT_FOUND condition. + */ +static int dcc_gssapi_bin_search(char *key) { + int bottom = 0; + int middle, res; + int top = list_count - 1; + + while (bottom <= top) { + middle = (bottom + top) / 2; + res = strcmp(key, list[middle]); + + if (res < 0) { + top = middle - 1; + } else if (res > 0) { + bottom = middle + 1; + } else { + return middle; + } + } + + return KEY_NOT_FOUND; +} + +/* * Send notification of access/no access to client. * * @param sd. Socket to write notification to. @@ -362,3 +477,122 @@ void dcc_gssapi_release_credentials(void) { rs_log_info("Credentials released successfully."); } + +/* + * Read the set of principal names from the specified file + * to the list global variable and apply a qsort. If the + * list file can not be opened we exit with error as a + * requested security feature can not be implemented. + * + * @param mode. Indicates the type of the list, either + * black or white. Used for the log file. + * + * Returns 0 on success, otherwise error. + */ +int dcc_gssapi_obtain_list(int mode) { + char **head = NULL; + char *line = NULL; + char *pos = NULL; + FILE *file; + int ret; + size_t length = 0; + ssize_t read; + + if (!(file = fopen(arg_list_file, "r"))) { + rs_log_error("Failed to open list file: %s: %s.", arg_list_file, + strerror(errno)); + return EXIT_GSSAPI_FAILED; + } + + rs_log_info("Using file %s as a %slist.", arg_list_file, + (mode) ? "black" : "white"); + + while ((read = getline(&line, &length, file)) != -1) { + list_count++; + } + + if ((ret = fseek(file, 0, SEEK_SET)) != 0) { + rs_log_error("fseek failed: %s.", strerror(errno)); + + /* If seeking to the start of the file fails, + * try achieving the same effect by closing and reopening the file. */ + + if ((ret = fclose(file)) != 0) { + rs_log_error("fclose failed: %s.", strerror(errno)); + } + + if (!(file = fopen(arg_list_file, "r"))) { + rs_log_error("Failed to open list file: %s: %s.", arg_list_file, + strerror(errno)); + return EXIT_GSSAPI_FAILED; + } + } + + if ((list = malloc(list_count * sizeof(char *))) == NULL) { + rs_log_error("malloc failed : %ld bytes: out of memory.", + (long) (list_count * sizeof(char *))); + return EXIT_OUT_OF_MEMORY; + } + + head = list; + + while ((getline(&line, &length, file)) != -1) { + if ((pos = strchr(line, '\n')) != NULL) { + *pos = '\0'; + } + + if ((*list = malloc(strlen(line) + 1)) == NULL) { + rs_log_error("malloc failed : %ld bytes: out of memory.", + (long) (strlen(line) + 1)); + return EXIT_OUT_OF_MEMORY; + } + + strcpy(*list, line); + list++; + } + + list = head; + qsort(list, list_count, sizeof(char *), dcc_gssapi_compare_strings); + + if ((ret = fclose(file)) != 0) { + rs_log_error("fclose failed: %s.", strerror(errno)); + } + + free(line); + + return 0; +} + +/* + * Comparison function used by qsort, simply compares + * the two specified array elements containing two + * principal names. + * + * @param string_one. First element to be compared. + * + * @param string_two. Second element to be compared. + * + * Returns the result of the comparison. + */ +static int dcc_gssapi_compare_strings(const void *string_one, + const void *string_two) { + const char **s1 = (const char **) string_one; + const char **s2 = (const char **) string_two; + + return strcmp(*s1, *s2); +} + +/* + * Free the dynamically allocated memory used by the + * list global variable. First free the individual + * strings then the array itself. + */ +void dcc_gssapi_free_list(void) { + int i; + + for (i = 0; i < list_count; i++) { + free(list[i]); + } + + free(list); +} diff --git a/src/daemon.c b/src/daemon.c index df17c82..395d953 100644 --- a/src/daemon.c +++ b/src/daemon.c @@ -212,6 +212,13 @@ int main(int argc, char *argv[]) if ((ret = dcc_gssapi_acquire_credentials()) != 0) { goto out; } + + /* Read contents of list file into an array and apply qsort. */ + if (opt_blacklist_enabled || opt_whitelist_enabled) { + if ((ret = dcc_gssapi_obtain_list((opt_blacklist_enabled) ? 1 : 0)) != 0) { + goto out; + } + } } #endif @@ -314,6 +321,10 @@ static int dcc_inetd_server(void) #ifdef HAVE_GSSAPI if (dcc_auth_enabled) { dcc_gssapi_release_credentials(); + + if (opt_blacklist_enabled || opt_whitelist_enabled) { + dcc_gssapi_free_list(); + } } #endif @@ -58,6 +58,10 @@ int arg_max_jobs = 0; #ifdef HAVE_GSSAPI /* If true perform GSS-API based authentication. */ int opt_auth_enabled = 0; +/* Control access through a specified list file. */ +int opt_blacklist_enabled = 0; +int opt_whitelist_enabled = 0; +const char *arg_list_file = NULL; #endif int arg_port = DISTCC_DEFAULT_PORT; @@ -115,6 +119,7 @@ const struct poptOption options[] = { { "allow", 'a', POPT_ARG_STRING, 0, 'a', 0, 0 }, #ifdef HAVE_GSSAPI { "auth", 0, POPT_ARG_NONE, &opt_auth_enabled, 'A', 0, 0 }, + { "blacklist", 0, POPT_ARG_STRING, &arg_list_file, 'b', 0, 0 }, #endif { "jobs", 'j', POPT_ARG_INT, &arg_max_jobs, 'j', 0, 0 }, { "daemon", 0, POPT_ARG_NONE, &opt_daemon_mode, 0, 0, 0 }, @@ -138,6 +143,9 @@ const struct poptOption options[] = { { "user", 0, POPT_ARG_STRING, &opt_user, 'u', 0, 0 }, { "verbose", 0, POPT_ARG_NONE, 0, 'v', 0, 0 }, { "version", 0, POPT_ARG_NONE, 0, 'V', 0, 0 }, +#ifdef HAVE_GSSAPI + { "whitelist", 0, POPT_ARG_STRING, &arg_list_file, 'w', 0, 0 }, +#endif { "wizard", 'W', POPT_ARG_NONE, 0, 'W', 0, 0 }, { "stats", 0, POPT_ARG_NONE, &arg_stats, 0, 0, 0 }, { "stats-port", 0, POPT_ARG_INT, &arg_stats_port, 0, 0, 0 }, @@ -147,7 +155,6 @@ const struct poptOption options[] = { { 0, 0, 0, 0, 0, 0, 0 } }; - static void distccd_show_usage(void) { dcc_show_version("distccd"); @@ -172,6 +179,8 @@ static void distccd_show_usage(void) " -a, --allow IP[/BITS] client address access control\n" #ifdef HAVE_GSSAPI " --auth enable GSS-API based mutual authenticaton\n" +" --blacklist=FILE control client access through a blacklist\n" +" --whitelist=FILE control client access through a whitelist\n" #endif " --stats enable statistics reporting via HTTP server\n" " --stats-port PORT TCP port to listen on for statistics requests\n" @@ -205,7 +214,7 @@ static void dcc_gssapi_show_principal(void) { char *princ_env_val = NULL; if ((princ_env_val = getenv("DISTCCD_PRINCIPAL"))) { - printf("Principal is\t: %s\n", princ_env_val); + printf("Principal is\t: %s\n", princ_env_val); } else { printf("Principal\t: Not Set\n"); } @@ -244,7 +253,7 @@ int distccd_parse_options(int argc, const char **argv) break; #ifdef HAVE_GSSAPI - /* Set the flag to indicate that authentication is requested. */ + /* Set the flag to indicate that authentication is requested. */ case 'A': { if (opt_auth_enabled < 0) { opt_auth_enabled = 0; @@ -253,6 +262,18 @@ int distccd_parse_options(int argc, const char **argv) dcc_auth_enabled = opt_auth_enabled; break; } + + case 'b': { + if (opt_whitelist_enabled) { + rs_log_error("Can't specify both --whitelist and --blacklist."); + exitcode = EXIT_BAD_ARGUMENTS; + goto out_exit; + } else { + opt_blacklist_enabled = 1; + } + + break; + } #endif case 'j': @@ -312,6 +333,20 @@ int distccd_parse_options(int argc, const char **argv) opt_log_level_num = RS_LOG_DEBUG; break; +#ifdef HAVE_GSSAPI + case 'w': { + if (opt_blacklist_enabled) { + rs_log_error("Can't specify both --blacklist and --whitelist."); + exitcode = EXIT_BAD_ARGUMENTS; + goto out_exit; + } else { + opt_whitelist_enabled = 1; + } + + break; + } +#endif + case 'W': /* catchall for running under gdb */ opt_log_stderr = 1; @@ -51,3 +51,10 @@ extern int opt_zeroconf; #ifdef HAVE_GSSAPI extern int dcc_auth_enabled; #endif + +#ifdef HAVE_GSSAPI +extern int dcc_auth_enabled; +extern int opt_blacklist_enabled; +extern int opt_whitelist_enabled; +extern const char *arg_list_file; +#endif diff --git a/src/dparent.c b/src/dparent.c index 9ced2a2..77a4ac5 100644 --- a/src/dparent.c +++ b/src/dparent.c @@ -265,6 +265,10 @@ static void dcc_nofork_parent(int listen_fd) #ifdef HAVE_GSSAPI if (dcc_auth_enabled) { dcc_gssapi_release_credentials(); + + if (opt_blacklist_enabled || opt_whitelist_enabled) { + dcc_gssapi_free_list(); + } } #endif dcc_exit(EXIT_CONNECT_FAILED); diff --git a/src/dsignal.c b/src/dsignal.c index 35cc6a2..de033b7 100644 --- a/src/dsignal.c +++ b/src/dsignal.c @@ -142,6 +142,10 @@ static RETSIGTYPE dcc_daemon_terminate(int whichsig) #ifdef HAVE_GSSAPI if (dcc_auth_enabled) { dcc_gssapi_release_credentials(); + + if (opt_blacklist_enabled || opt_whitelist_enabled) { + dcc_gssapi_free_list(); + } } #endif } |