1 /* Copyright (C) 2008 CERN
2  *
3  * This program is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU General Public License
5  * as published by the Free Software Foundation; either version 2
6  * of the License, or (at your option) any later version.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
16  * USA.
17  */
18 
19 /* Author: Ian Baker */
20 
21 #include <config.h>
22 
23 #ifdef HAVE_GSSAPI
24 #include <arpa/inet.h>
25 #endif
26 
27 #include <errno.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 
32 #include "auth.h"
33 #include "distcc.h"
34 #include "dopt.h"
35 #include "exitcode.h"
36 #include "netutil.h"
37 #include "trace.h"
38 
39 /*Maximum length of principal name in black/white list.*/
40 #define MAX_NAME_LENGTH 50
41 /*Key not found during binary search*/
42 #define KEY_NOT_FOUND -1
43 
44 static int dcc_gssapi_accept_secure_context(int to_net_sd,
45 					    int from_net_sd,
46 					    OM_uint32 *ret_flags,
47 					    char **principal);
48 static int dcc_gssapi_recv_handshake(int from_net_sd, int to_net_sd);
49 static int dcc_gssapi_check_list(char *principal, int sd);
50 static int dcc_gssapi_bin_search(char *key);
51 static int dcc_gssapi_notify_client(int sd, char status);
52 static int dcc_gssapi_compare_strings(const void *string_one,
53 				      const void *string_two);
54 
55 /*Global credentials so they're only required and released once*/
56 /*in the most suitable place.*/
57 gss_cred_id_t creds;
58 /*Global security context in case other services*/
59 /*are implemented in the future.*/
60 gss_ctx_id_t distccd_ctx_handle = GSS_C_NO_CONTEXT;
61 /*Global sorted list of principal names from either a specified*/
62 /*blacklist or a whitelist available to all children*/
63 char **list = NULL;
64 /*Global count of the number of principal names in the sorted list.*/
65 int list_count = 0;
66 
67 /*
68  * Perform any requested security.
69  *
70  * @param to_net_sd.	Socket to write to.
71  *
72  * @param from_net_sd.	Socket to read from.
73  *
74  * @param ret_flags.	A representation of the services requested
75  *			by the client.
76  *
77  * @param principal.	The name of the client principal.
78  *
79  * Returns 0 on success, otherwise error.
80  */
dcc_gssapi_check_client(int to_net_sd,int from_net_sd)81 int dcc_gssapi_check_client(int to_net_sd, int from_net_sd) {
82     char *principal = NULL;
83     int ret;
84     OM_uint32 ret_flags;
85 
86     if ((ret = dcc_gssapi_accept_secure_context(to_net_sd,
87 					       from_net_sd,
88 					       &ret_flags,
89 					       &principal)) != 0) {
90         return ret;
91     }
92 
93     if ((ret = dcc_gssapi_compare_flags(GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, ret_flags)) != 0) {
94 	dcc_gssapi_delete_ctx(&distccd_ctx_handle);
95         return ret;
96     }
97 
98     if (opt_blacklist_enabled || opt_whitelist_enabled) {
99 	rs_log_info("Checking %s against %slist %s.",
100 				principal,
101 				(opt_blacklist_enabled) ? "black" : "white",
102 				arg_list_file);
103         if ((ret = dcc_gssapi_check_list(principal, to_net_sd)) != 0) {
104 	    dcc_gssapi_delete_ctx(&distccd_ctx_handle);
105 	    free(principal);
106             return ret;
107         }
108 
109 	free(principal);
110     } else {
111 	rs_log_info("Notifying client.");
112 
113 	if ((ret = dcc_gssapi_notify_client(to_net_sd, ACCESS)) != 0) {
114 	    dcc_gssapi_delete_ctx(&distccd_ctx_handle);
115 	    return ret;
116 	}
117     }
118 
119     return 0;
120 }
121 
122 /*
123  * Accept a secure context using the GSS-API.  A handshake is attempted
124  * in order to detect a non-authenticating client.
125  *
126  * @param to_net_sd.	Socket to write to.
127  *
128  * @param from_net_sd.	Socket to read from.
129  *
130  * @param ret_flags.	A representation of the security services
131  *			requested by the client to be returned to
132  *			the invoking function.
133  *
134  * @param principal.	The name of the client principal to be returned
135  *			to the invoking function.
136  *
137  * Returns 0 on success, otherwise error.
138  */
dcc_gssapi_accept_secure_context(int to_net_sd,int from_net_sd,OM_uint32 * ret_flags,char ** principal)139 static int dcc_gssapi_accept_secure_context(int to_net_sd,
140 					    int from_net_sd,
141 					    OM_uint32 *ret_flags,
142 					    char **principal) {
143     gss_buffer_desc input_tok = GSS_C_EMPTY_BUFFER;
144     gss_buffer_desc name_buffer = GSS_C_EMPTY_BUFFER;
145     gss_buffer_desc output_tok = GSS_C_EMPTY_BUFFER;
146     gss_name_t int_client_name;
147     gss_OID name_type;
148     int ret;
149     OM_uint32 major_status, minor_status, return_status;
150 
151     input_tok.value = NULL;
152     input_tok.length = 0;
153     output_tok.value = NULL;
154     output_tok.length = 0;
155 
156     if ((ret = dcc_gssapi_recv_handshake(from_net_sd, to_net_sd)) != 0) {
157         return ret;
158     }
159 
160     do {
161             if ((ret = recv_token(from_net_sd, &input_tok)) != 0) {
162 		rs_log_error("Error receiving token.");
163 		rs_log_info("(eof may indicate a client error during ctx init).");
164 
165                 return ret;
166             }
167 
168             major_status = gss_accept_sec_context(&minor_status,
169 						  &distccd_ctx_handle,
170 						  creds,
171 						  &input_tok,
172 						  GSS_C_NO_CHANNEL_BINDINGS,
173 						  &int_client_name,
174 						  NULL,
175 						  &output_tok,
176 						  ret_flags,
177 						  NULL,
178 						  NULL);
179 
180             if (GSS_ERROR(major_status)) {
181 		        rs_log_crit("Failed to accept secure context.");
182 		        dcc_gssapi_status_to_log(major_status, GSS_C_GSS_CODE);
183 	            dcc_gssapi_status_to_log(minor_status, GSS_C_MECH_CODE);
184 
185                 if ((return_status = gss_release_buffer(&minor_status,
186 						  &input_tok)) != GSS_S_COMPLETE) {
187                     rs_log_error("Failed to release buffer.");
188                 }
189 
190 	            return EXIT_GSSAPI_FAILED;
191             }
192 
193             if (output_tok.length > 0) {
194                 if ((ret = send_token(to_net_sd,
195 				     &output_tok)) != 0) {
196                     dcc_gssapi_cleanup(&input_tok,
197 				       &output_tok,
198 				       &int_client_name);
199 		    return ret;
200                 }
201 
202                 if ((return_status = gss_release_buffer(&minor_status,
203 						  &output_tok)) != GSS_S_COMPLETE) {
204                     rs_log_error("Failed to release buffer.");
205                 }
206             }
207 
208             if (input_tok.length > 0) {
209                 if ((return_status = gss_release_buffer(&minor_status,
210 						  &input_tok)) != GSS_S_COMPLETE) {
211                     rs_log_error("Failed to release buffer.");
212                 }
213             }
214 
215 
216     } while (major_status != GSS_S_COMPLETE);
217 
218     if ((major_status = gss_display_name(&minor_status,
219 					int_client_name,
220 					&name_buffer,
221 					&name_type)) != GSS_S_COMPLETE) {
222         rs_log_error("Failed to convert name.");
223     }
224 
225     rs_log_info("Successfully authenticated %s.", (char *) name_buffer.value);
226 
227     if ((*principal = malloc(strlen((char *) name_buffer.value) + 1 )) == NULL) {
228         rs_log_error("malloc failed : %ld bytes: out of memory.",
229                                         (long) (strlen((char *) name_buffer.value) + 1));
230         return EXIT_OUT_OF_MEMORY;
231     }
232 
233     strcpy(*principal, (char *) name_buffer.value);
234     dcc_gssapi_cleanup(&input_tok, &output_tok, &int_client_name);
235 
236     return 0;
237 }
238 
239 /*
240  * Attempt handshake exchange with the client to indicate server's
241  * desire to authentciate.
242  *
243  * @param from_net_sd.	Socket to read from.
244  *
245  * @param to_net_sd.	Socket to write to.
246  *
247  * Returns 0 on success, otherwise error.
248  */
dcc_gssapi_recv_handshake(int from_net_sd,int to_net_sd)249 static int dcc_gssapi_recv_handshake(int from_net_sd, int to_net_sd) {
250     char auth;
251     int ret;
252 
253     rs_log_info("Receiving handshake.");
254 
255     if ((ret = dcc_readx(from_net_sd, &auth, sizeof(auth))) != 0) {
256         return ret;
257     }
258 
259     rs_log_info("Received %c.", auth);
260 
261     if (auth != HANDSHAKE) {
262 	rs_log_crit("No client handshake - did the client require authentication?");
263 	return EXIT_GSSAPI_FAILED;
264     }
265 
266     rs_log_info("Sending handshake.");
267 
268     if ((ret = dcc_writex(to_net_sd, &auth, sizeof(auth))) != 0) {
269         return ret;
270     }
271 
272     rs_log_info("Sent %c.", auth);
273 
274     return 0;
275 }
276 
277 /*
278  * Check the name of the connecting client principal against the sorted
279  * list of principal names using a binary search to determine access
280  * rights depending upon the type of list used.  The client is then
281  * notified of the outcome.
282  *
283  * @param principal.	The name of the connecting client principal.
284  *
285  * @param sd.		Socket to write notification to.
286  *
287  * Returns 0 on success, otherwise access deinied.
288  */
dcc_gssapi_check_list(char * principal,int sd)289 static int dcc_gssapi_check_list(char *principal, int sd) {
290     char *pos = NULL;
291     int location, ret;
292 
293     if ((pos = strchr(principal, '@')) != NULL) {
294         *pos = '\0';
295     }
296 
297     location = dcc_gssapi_bin_search(principal);
298 
299     if (opt_blacklist_enabled) {	/*blacklist*/
300         if (location >= 0) {
301 	        rs_log_info("Access denied - %s blacklisted.", principal);
302 	        rs_log_info("Notifying client.");
303 	        dcc_gssapi_notify_client(sd, NO_ACCESS);
304 	        return EXIT_GSSAPI_FAILED;
305 	    } else {
306 	        rs_log_info("Access granted - %s not blacklisted.", principal);
307 	        rs_log_info("Notifying client.");
308 
309 	        if ((ret = dcc_gssapi_notify_client(sd, ACCESS)) != 0) {
310 	            return ret;
311 	        }
312 
313 	        return 0;
314 	    }
315     } else {	/*whitelist*/
316 	    if (location >= 0) {
317 	        rs_log_info("Access granted - %s whitelisted.", principal);
318 	        rs_log_info("Notifying client.");
319 
320             if ((ret = dcc_gssapi_notify_client(sd, ACCESS)) != 0) {
321 	            return ret;
322 	        }
323 
324 	        return 0;
325 	    } else {
326 	        rs_log_info("Access denied - %s not whitelisted.", principal);
327 	        rs_log_info("Notifying client.");
328 	        dcc_gssapi_notify_client(sd, NO_ACCESS);
329 	        return EXIT_GSSAPI_FAILED;
330 	    }
331     }
332 }
333 
334 /*
335  * Perform a binary search on a sorted list for a key.
336  *
337  * @param key.	The search key.
338  *
339  * Returns index if key in list, otherwise
340  * KEY_NOT_FOUND condition.
341  */
dcc_gssapi_bin_search(char * key)342 static int dcc_gssapi_bin_search(char *key) {
343     int bottom = 0;
344     int middle, res;
345     int top = list_count - 1;
346 
347     while (bottom <= top) {
348         middle = (bottom + top) / 2;
349         res = strcmp(key, list[middle]);
350 
351         if (res < 0) {
352             top = middle - 1;
353         } else if (res > 0) {
354             bottom = middle + 1;
355         } else {
356             return middle;
357         }
358     }
359 
360     return KEY_NOT_FOUND;
361 }
362 
363 /*
364  * Send notification of access/no access to client.
365  *
366  * @param sd.		Socket to write notification to.
367  *
368  * @param status.	Status of access request.
369  *			Either 'y' or 'n'
370  *
371  * Returns 0 on success, otherwise error.
372  */
dcc_gssapi_notify_client(int sd,char status)373 static int dcc_gssapi_notify_client(int sd, char status) {
374     int ret;
375 
376     if ((ret = dcc_writex(sd, &status, sizeof(status))) != 0) {
377 	rs_log_crit("Failed to notify client.");
378 	return ret;
379     }
380 
381     return 0;
382 }
383 
384 /*
385  * Acquire credentials for the distccd daemon.  We attempt to extract
386  * the server principal name from the environment and ascertain the
387  * name type.
388  *
389  * Returns 0 on success, otherwise error.
390  */
dcc_gssapi_acquire_credentials(void)391 int dcc_gssapi_acquire_credentials(void) {
392     char *princ_env_val = NULL;
393     gss_buffer_desc name_buffer = GSS_C_EMPTY_BUFFER;
394     gss_name_t int_princ_name;
395     gss_OID name_type;
396     OM_uint32 major_status, minor_status;
397 
398     princ_env_val = getenv("DISTCCD_PRINCIPAL");
399 
400     if (princ_env_val == NULL) {
401         rs_log_error("No principal name specified.");
402         return EXIT_GSSAPI_FAILED;
403     }
404 
405     if (strchr(princ_env_val, '@') != NULL) {
406         name_type = GSS_C_NT_HOSTBASED_SERVICE;
407     } else {
408         name_type = GSS_C_NT_USER_NAME;
409     }
410 
411     name_buffer.value = princ_env_val;
412     name_buffer.length = strlen(princ_env_val);
413 
414     rs_log_info("Acquiring credentials.");
415 
416     name_buffer.length = strlen(name_buffer.value);
417 
418     if ((major_status = gss_import_name(&minor_status,
419 				       &name_buffer,
420 				       name_type,
421 				       &int_princ_name)) != GSS_S_COMPLETE) {
422 	rs_log_error("Failed to import princ name (%s) to internal GSS-API format.",
423 							(char *) name_buffer.value);
424         return EXIT_GSSAPI_FAILED;
425     }
426 
427     major_status = gss_acquire_cred(&minor_status,
428                                     int_princ_name,
429                                     0,
430                                     GSS_C_NO_OID_SET,
431                                     GSS_C_ACCEPT,
432                                     &creds,
433                                     NULL,
434                                     NULL);
435 
436     if (major_status != GSS_S_COMPLETE) {
437         rs_log_crit("Failed to acquire credentials.");
438         dcc_gssapi_status_to_log(major_status, GSS_C_GSS_CODE);
439 	    dcc_gssapi_status_to_log(minor_status, GSS_C_MECH_CODE);
440 
441 	    if ((major_status = gss_release_name(&minor_status,
442 					    &int_princ_name)) != GSS_S_COMPLETE) {
443 	        rs_log_error("Failed to release GSS-API buffer.");
444         }
445 
446         return EXIT_GSSAPI_FAILED;
447     }
448 
449     if ((major_status = gss_release_name(&minor_status,
450 					&int_princ_name)) != GSS_S_COMPLETE) {
451 	rs_log_error("Failed to release GSS-API buffer.");
452     }
453 
454     rs_log_info("Credentials successfully acquired for %s.",
455 						(char *) name_buffer.value);
456 
457     name_buffer.value = NULL;
458 
459     if ((major_status = gss_release_buffer(&minor_status,
460 					  &name_buffer)) != GSS_S_COMPLETE) {
461         rs_log_error("Failed to release GSS-API buffer.");
462     }
463 
464     return 0;
465 }
466 
467 /*
468  * Release acquired credentials.
469  */
dcc_gssapi_release_credentials(void)470 void dcc_gssapi_release_credentials(void) {
471     OM_uint32 major_status, minor_status;
472 
473     if ((major_status = gss_release_cred(&minor_status,
474 					&creds)) != GSS_S_COMPLETE) {
475 	rs_log_error("Failed to release credentials.");
476     }
477 
478     rs_log_info("Credentials released successfully.");
479 }
480 
481 /*
482  * Read the set of principal names from the specified file
483  * to the list global variable and apply a qsort.  If the
484  * list file can not be opened we exit with error as a
485  * requested security feature can not be implemented.
486  *
487  * @param mode.	Indicates the type of the list, either
488  *		black or white.  Used for the log file.
489  *
490  * Returns 0 on success, otherwise error.
491  */
dcc_gssapi_obtain_list(int mode)492 int dcc_gssapi_obtain_list(int mode) {
493     char **head = NULL;
494     char *line = NULL;
495     char *pos = NULL;
496     FILE *file;
497     int ret;
498     size_t length = 0;
499     ssize_t read;
500 
501     if (!(file = fopen(arg_list_file, "r"))) {
502         rs_log_error("Failed to open list file: %s: %s.", arg_list_file,
503                                                         strerror(errno));
504         return EXIT_GSSAPI_FAILED;
505     }
506 
507     rs_log_info("Using file %s as a %slist.", arg_list_file,
508 					                        (mode) ? "black" : "white");
509 
510     while ((read = getline(&line, &length, file)) != -1) {
511         list_count++;
512     }
513 
514     if ((ret = fseek(file, 0, SEEK_SET)) != 0) {
515         rs_log_error("fseek failed: %s.", strerror(errno));
516 
517         /* If seeking to the start of the file fails,
518          * try achieving the same effect by closing and reopening the file. */
519 
520 	    if ((ret = fclose(file)) != 0) {
521             rs_log_error("fclose failed: %s.", strerror(errno));
522         }
523 
524 	    if (!(file = fopen(arg_list_file, "r"))) {
525             rs_log_error("Failed to open list file: %s: %s.", arg_list_file,
526 							                               strerror(errno));
527             return EXIT_GSSAPI_FAILED;
528         }
529     }
530 
531     if ((list = malloc(list_count * sizeof(char *))) == NULL) {
532         rs_log_error("malloc failed : %ld bytes: out of memory.",
533                                         (long) (list_count * sizeof(char *)));
534         return EXIT_OUT_OF_MEMORY;
535     }
536 
537     head = list;
538 
539     while ((getline(&line, &length, file)) != -1) {
540         if ((pos = strchr(line, '\n')) != NULL) {
541             *pos = '\0';
542         }
543 
544 	    if ((*list = malloc(strlen(line) + 1)) == NULL) {
545             rs_log_error("malloc failed : %ld bytes: out of memory.",
546                                             (long) (strlen(line) + 1));
547             return EXIT_OUT_OF_MEMORY;
548         }
549 
550 	    strcpy(*list, line);
551 	    list++;
552     }
553 
554     list = head;
555     qsort(list, list_count, sizeof(char *), dcc_gssapi_compare_strings);
556 
557     if ((ret = fclose(file)) != 0) {
558         rs_log_error("fclose failed: %s.", strerror(errno));
559     }
560 
561     free(line);
562 
563     return 0;
564 }
565 
566 /*
567  * Comparison function used by qsort, simply compares
568  * the two specified array elements containing two
569  * principal names.
570  *
571  * @param string_one.	First element to be compared.
572  *
573  * @param string_two.	Second element to be compared.
574  *
575  * Returns the result of the comparison.
576  */
dcc_gssapi_compare_strings(const void * string_one,const void * string_two)577 static int dcc_gssapi_compare_strings(const void *string_one,
578 				                      const void *string_two) {
579     const char **s1 = (const char **) string_one;
580     const char **s2 = (const char **) string_two;
581 
582     return strcmp(*s1, *s2);
583 }
584 
585 /*
586  * Free the dynamically allocated memory used by the
587  * list global variable.  First free the individual
588  * strings then the array itself.
589  */
dcc_gssapi_free_list(void)590 void dcc_gssapi_free_list(void) {
591     int i;
592 
593     for (i = 0; i < list_count; i++) {
594         free(list[i]);
595     }
596 
597     free(list);
598 }
599