/* Copyright (C) 2008 CERN * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ /* Author: Ian Baker */ #include #ifdef HAVE_GSSAPI #include #endif #include #include #include #include #include "auth.h" #include "distcc.h" #include "dopt.h" #include "exitcode.h" #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.*/ 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. * * @param to_net_sd. Socket to write to. * * @param from_net_sd. Socket to read from. * * @param ret_flags. A representation of the services requested * by the client. * * @param principal. The name of the client principal. * * Returns 0 on success, otherwise error. */ int dcc_gssapi_check_client(int to_net_sd, int from_net_sd) { char *principal = NULL; int ret; OM_uint32 ret_flags; if ((ret = dcc_gssapi_accept_secure_context(to_net_sd, from_net_sd, &ret_flags, &principal)) != 0) { return ret; } if ((ret = dcc_gssapi_compare_flags(GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, ret_flags)) != 0) { dcc_gssapi_delete_ctx(&distccd_ctx_handle); return ret; } 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; } 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; } /* * Accept a secure context using the GSS-API. A handshake is attempted * in order to detect a non-authenticating client. * * @param to_net_sd. Socket to write to. * * @param from_net_sd. Socket to read from. * * @param ret_flags. A representation of the security services * requested by the client to be returned to * the invoking function. * * @param principal. The name of the client principal to be returned * to the invoking function. * * Returns 0 on success, otherwise error. */ static int dcc_gssapi_accept_secure_context(int to_net_sd, int from_net_sd, OM_uint32 *ret_flags, char **principal) { gss_buffer_desc input_tok = GSS_C_EMPTY_BUFFER; gss_buffer_desc name_buffer = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_tok = GSS_C_EMPTY_BUFFER; gss_name_t int_client_name; gss_OID name_type; int ret; OM_uint32 major_status, minor_status, return_status; input_tok.value = NULL; input_tok.length = 0; output_tok.value = NULL; output_tok.length = 0; if ((ret = dcc_gssapi_recv_handshake(from_net_sd, to_net_sd)) != 0) { return ret; } do { if ((ret = recv_token(from_net_sd, &input_tok)) != 0) { rs_log_error("Error receiving token."); rs_log_info("(eof may indicate a client error during ctx init)."); return ret; } major_status = gss_accept_sec_context(&minor_status, &distccd_ctx_handle, creds, &input_tok, GSS_C_NO_CHANNEL_BINDINGS, &int_client_name, NULL, &output_tok, ret_flags, NULL, NULL); if (GSS_ERROR(major_status)) { rs_log_crit("Failed to accept secure context."); dcc_gssapi_status_to_log(major_status, GSS_C_GSS_CODE); dcc_gssapi_status_to_log(minor_status, GSS_C_MECH_CODE); if ((return_status = gss_release_buffer(&minor_status, &input_tok)) != GSS_S_COMPLETE) { rs_log_error("Failed to release buffer."); } return EXIT_GSSAPI_FAILED; } if (output_tok.length > 0) { if ((ret = send_token(to_net_sd, &output_tok)) != 0) { dcc_gssapi_cleanup(&input_tok, &output_tok, &int_client_name); return ret; } if ((return_status = gss_release_buffer(&minor_status, &output_tok)) != GSS_S_COMPLETE) { rs_log_error("Failed to release buffer."); } } if (input_tok.length > 0) { if ((return_status = gss_release_buffer(&minor_status, &input_tok)) != GSS_S_COMPLETE) { rs_log_error("Failed to release buffer."); } } } while (major_status != GSS_S_COMPLETE); if ((major_status = gss_display_name(&minor_status, int_client_name, &name_buffer, &name_type)) != GSS_S_COMPLETE) { rs_log_error("Failed to convert name."); } rs_log_info("Successfully authenticated %s.", (char *) name_buffer.value); if ((*principal = malloc(strlen((char *) name_buffer.value) + 1 )) == NULL) { rs_log_error("malloc failed : %ld bytes: out of memory.", (long) (strlen((char *) name_buffer.value) + 1)); return EXIT_OUT_OF_MEMORY; } strcpy(*principal, (char *) name_buffer.value); dcc_gssapi_cleanup(&input_tok, &output_tok, &int_client_name); return 0; } /* * Attempt handshake exchange with the client to indicate server's * desire to authentciate. * * @param from_net_sd. Socket to read from. * * @param to_net_sd. Socket to write to. * * Returns 0 on success, otherwise error. */ static int dcc_gssapi_recv_handshake(int from_net_sd, int to_net_sd) { char auth; int ret; rs_log_info("Receiving handshake."); if ((ret = dcc_readx(from_net_sd, &auth, sizeof(auth))) != 0) { return ret; } rs_log_info("Received %c.", auth); if (auth != HANDSHAKE) { rs_log_crit("No client handshake - did the client require authentication?"); return EXIT_GSSAPI_FAILED; } rs_log_info("Sending handshake."); if ((ret = dcc_writex(to_net_sd, &auth, sizeof(auth))) != 0) { return ret; } rs_log_info("Sent %c.", auth); return 0; } /* * 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. * * @param status. Status of access request. * Either 'y' or 'n' * * Returns 0 on success, otherwise error. */ static int dcc_gssapi_notify_client(int sd, char status) { int ret; if ((ret = dcc_writex(sd, &status, sizeof(status))) != 0) { rs_log_crit("Failed to notify client."); return ret; } return 0; } /* * Acquire credentials for the distccd daemon. We attempt to extract * the server principal name from the environment and ascertain the * name type. * * Returns 0 on success, otherwise error. */ int dcc_gssapi_acquire_credentials(void) { char *princ_env_val = NULL; gss_buffer_desc name_buffer = GSS_C_EMPTY_BUFFER; gss_name_t int_princ_name; gss_OID name_type; OM_uint32 major_status, minor_status; princ_env_val = getenv("DISTCCD_PRINCIPAL"); if (princ_env_val == NULL) { rs_log_error("No principal name specified."); return EXIT_GSSAPI_FAILED; } if (strchr(princ_env_val, '@') != NULL) { name_type = GSS_C_NT_HOSTBASED_SERVICE; } else { name_type = GSS_C_NT_USER_NAME; } name_buffer.value = princ_env_val; name_buffer.length = strlen(princ_env_val); rs_log_info("Acquiring credentials."); name_buffer.length = strlen(name_buffer.value); if ((major_status = gss_import_name(&minor_status, &name_buffer, name_type, &int_princ_name)) != GSS_S_COMPLETE) { rs_log_error("Failed to import princ name (%s) to internal GSS-API format.", (char *) name_buffer.value); return EXIT_GSSAPI_FAILED; } major_status = gss_acquire_cred(&minor_status, int_princ_name, 0, GSS_C_NO_OID_SET, GSS_C_ACCEPT, &creds, NULL, NULL); if (major_status != GSS_S_COMPLETE) { rs_log_crit("Failed to acquire credentials."); dcc_gssapi_status_to_log(major_status, GSS_C_GSS_CODE); dcc_gssapi_status_to_log(minor_status, GSS_C_MECH_CODE); if ((major_status = gss_release_name(&minor_status, &int_princ_name)) != GSS_S_COMPLETE) { rs_log_error("Failed to release GSS-API buffer."); } return EXIT_GSSAPI_FAILED; } if ((major_status = gss_release_name(&minor_status, &int_princ_name)) != GSS_S_COMPLETE) { rs_log_error("Failed to release GSS-API buffer."); } rs_log_info("Credentials successfully acquired for %s.", (char *) name_buffer.value); name_buffer.value = NULL; if ((major_status = gss_release_buffer(&minor_status, &name_buffer)) != GSS_S_COMPLETE) { rs_log_error("Failed to release GSS-API buffer."); } return 0; } /* * Release acquired credentials. */ void dcc_gssapi_release_credentials(void) { OM_uint32 major_status, minor_status; if ((major_status = gss_release_cred(&minor_status, &creds)) != GSS_S_COMPLETE) { rs_log_error("Failed to release credentials."); } 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); }