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