Clean up RPM spec for newer distributions
[clumanager.git] / librhcm / auth_md5.c
blobff6ce0fb109730ae17568659a6d3ce9fb96e5f0f
1 /*
2 Copyright Red Hat, Inc. 2003
4 The Red Hat Cluster Manager API Library is free software; you can
5 redistribute it and/or modify it under the terms of the GNU Lesser
6 General Public License as published by the Free Software Foundation;
7 either version 2.1 of the License, or (at your option) any later
8 version.
10 The Red Hat Cluster Manager API Library is distributed in the hope
11 that it will be useful, but WITHOUT ANY WARRANTY; without even the
12 implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13 PURPOSE. See the GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18 USA.
20 /** @file
21 * Very simple challenge-response, based on MD5 + shared-secret.
22 * Uses Colin Plumb's MD5 code.
24 #include <sys/mman.h>
25 #include <sys/types.h>
26 #include <stdlib.h>
27 #include <fcntl.h>
28 #include <md5.h>
29 #include <pthread.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <unistd.h>
34 #define AUTH_TIMEOUT 5
35 #define KEY_SIZE 16
37 static pthread_mutex_t auth_mutex = PTHREAD_MUTEX_INITIALIZER;
38 static int auth_enabled = 0;
39 static struct MD5Context auth_md5_ctx;
40 static int urfd = -1;
41 #if 0
42 static int rfd = -1;
43 static char __rdata[KEY_SIZE*4];
44 #endif
46 typedef struct _random_data {
47 int rd_size;
48 void *rd_data;
49 } random_data_t;
51 #ifdef DEBUG
52 #include <stdio.h>
54 /**
55 * Dump a buffer of bytes in human-readable hexadecimal to output.
57 * @param buf Buffer to dump
58 * @param len Length of buf
60 static void
61 hex_dump(char *buf, int len)
63 int x;
65 printf("0x");
66 for(x=0; x<len; x++)
67 printf("%02x", (int)(buf[x]&0x000000ff));
69 #endif
72 /**
73 * Mash pseudo random data with real (entropy) random data. This is so we
74 * don't run the system out of entropy when lots of authentication packets
75 * are being sent back and forth.
77 * @param pd Pseudo random data set.
78 * @param pdsize Pseudo random data set size.
79 * @param rd Random data set (based on system entropy).
80 * @param rdsize Random data set size.
82 #if 0
83 void
84 mash(char *pd, int psize, char *rd, int rsize)
86 int x;
87 int start = 0;
89 /* Pass 1: Generate pseudorandom start offset */
90 for (x = 0; x < psize; x++)
91 start ^= (int)(pd[x]&0xff);
92 start %= (rsize - psize + 1);
93 read(rfd, rd + (start % rsize), 1);
96 * Pass 2: XOR pseudo w/ random data starting
97 * at pseudorandom offset...
99 for (x = 0; x < psize; x++)
100 pd[x] ^= rd[x+start];
102 #endif
106 * Fudge random data based on /dev/urandom and previously
107 * read-in data from /dev/random.
109 * @param data random_data_t info to initialize.
110 * @param size Size of buffer (or amount to fill).
111 * @return -1 on failure; 0 on success.
113 static int
114 alloc_random_data(random_data_t *data, int size)
116 pthread_mutex_lock(&auth_mutex);
117 #if 0
118 if (rfd == -1) {
119 rfd = open("/dev/random", O_RDONLY);
120 if (rfd == -1) {
121 pthread_mutex_unlock(&auth_mutex);
122 return -1;
124 read(rfd, __rdata, sizeof(__rdata));
126 #endif
128 if (urfd == -1) {
129 urfd = open("/dev/urandom", O_RDONLY);
130 if (urfd == -1) {
131 #if 0
132 close(rfd);
133 rfd = -1;
134 #endif
135 pthread_mutex_unlock(&auth_mutex);
136 return -1;
138 pthread_mutex_unlock(&auth_mutex);
141 data->rd_size = size;
142 data->rd_data = malloc(size);
143 pthread_mutex_unlock(&auth_mutex);
145 if (!data->rd_data)
146 return -1;
148 if (size > 0) {
149 /* Kind of depends on cryptographic strength
150 of /dev/urandom this way */
151 read(urfd, (char *)(data->rd_data), size);
152 #if 0
153 mash((char *)data->rd_data, size, __rdata,
154 sizeof(__rdata));
155 #endif
157 return 0;
162 * Munmap/free data stored in a random_data_t structure.
164 * @param data random_data_t to clear out.
166 static void
167 free_random_data(random_data_t *data)
169 free(data->rd_data);
174 * Initialize our global MD5 context.
176 * @param key Key we're starting with.
177 * @param keylen Length of our key.
178 * @return 0
181 auth_md5_init(char *key, size_t keylen)
183 pthread_mutex_lock(&auth_mutex);
184 MD5Init(&auth_md5_ctx);
186 if (key && keylen) {
187 MD5Update(&auth_md5_ctx, (uint8_t *)key, keylen);
188 auth_enabled = 1;
189 } else {
190 auth_enabled = 0;
193 pthread_mutex_unlock(&auth_mutex);
194 return 0;
199 * Close our /dev/random FD & clear up our MD5 Context.
202 auth_md5_deinit(void)
204 pthread_mutex_lock(&auth_mutex);
205 #if 0
206 if (rfd != -1) {
207 close(rfd);
208 rfd = -1;
210 #endif
211 if (urfd != -1) {
212 close(urfd);
213 urfd = -1;
215 MD5Init(&auth_md5_ctx);
216 auth_enabled = 0;
217 pthread_mutex_unlock(&auth_mutex);
218 return 0;
223 * Issue MD5 challenge. We basically just send some number of bytes read
224 * from /dev/random to the specified file descriptor. After we're done, we
225 * update a copy of our global key-MD5 context, and compare what the
226 * client sends back with what we expect it to send back, based on our
227 * view of the secret key. If the client sends back the proper message,
228 * we report 'success'; otherwise, 'permission denied'.
230 * @param fd File descriptor to authenticate.
231 * @return -1 on fail/not authentic, 0 on success/authentic
234 auth_md5_challenge(int fd)
236 int remain = KEY_SIZE, n;
237 uint8_t expected_md5[16], client_md5[16];
238 unsigned char *p, reply = 0;
239 random_data_t challenge_data;
240 struct MD5Context ch_ctx;
241 struct timeval tv;
242 fd_set rfds;
244 pthread_mutex_lock(&auth_mutex);
245 if (!auth_enabled) {
246 pthread_mutex_unlock(&auth_mutex);
247 return 0;
250 memcpy(&ch_ctx, &auth_md5_ctx, sizeof(ch_ctx));
251 pthread_mutex_unlock(&auth_mutex);
253 /* No random data, no authentication */
254 if (alloc_random_data(&challenge_data, KEY_SIZE) < 0)
255 return -1;
257 /* Figure out what we expect to receive */
258 MD5Update(&ch_ctx, (uint8_t *)challenge_data.rd_data,
259 challenge_data.rd_size);
260 MD5Final(expected_md5, &ch_ctx);
262 #ifdef DEBUG
263 printf("Issuing challenge: ");
264 hex_dump(challenge_data.rd_data, challenge_data.rd_size);
265 printf("\n");
266 printf("Expecting: ");
267 hex_dump(expected_md5, 16);
268 printf("\n");
269 #endif
271 p = challenge_data.rd_data;
272 while (remain) {
273 n = write(fd, p, remain);
274 if (n == -1) {
275 if (errno == EINTR)
276 continue;
277 free_random_data(&challenge_data);
278 return -1;
280 if (n == 0) {
281 free_random_data(&challenge_data);
282 return -1;
285 p += n;
286 remain -= n;
288 free_random_data(&challenge_data);
290 tv.tv_sec = AUTH_TIMEOUT;
291 tv.tv_usec = 0;
292 remain = KEY_SIZE;
293 #ifdef DEBUG
294 printf("Challenge sent, waiting for response\n");
295 #endif
297 p = client_md5;
298 while (remain && (tv.tv_sec || tv.tv_usec)) {
299 FD_ZERO(&rfds);
300 FD_SET(fd, &rfds);
302 n = select(fd+1, &rfds, NULL, NULL, &tv);
303 if (n == -1) {
304 if (errno == EINTR)
305 continue;
306 return -1;
308 if (n == 0) {
309 errno = ETIMEDOUT;
310 return -1;
313 n = read(fd, p, remain);
314 if (n == -1) {
315 if (errno == EINTR)
316 continue;
317 return -1;
319 if (n == 0) {
320 errno = EPIPE;
321 return -1;
324 p += n;
325 remain -= n;
328 #ifdef DEBUG
329 printf("Received: ");
330 hex_dump(client_md5, 16);
331 printf("\n");
332 #endif
334 if (remain || memcmp(expected_md5, client_md5, 16)) {
335 /* Notify client */
336 reply = (char)(0xff);
337 write(fd, &reply, 1);
338 errno = EACCES;
339 return -1;
342 #ifdef DEBUG
343 printf("Authentication successful\n");
344 #endif
345 write(fd, &reply, 1);
346 return 0;
351 * Handle an MD5 challenge from a server. This is generally done
352 * immediately following a connect() call. This handles the challenge
353 * data from the server, updates an MD5 context based on our "secret"
354 * key, and sends the resulting MD5 back to the server. If the server
355 * accepts our response, it writes a null byte to us; anything else
356 * is assumed to be a rejection.
358 * @param fd File descriptor to authenticate.
359 * @return -1 on failure/auth-failed, 0 on success
362 auth_md5(int fd)
364 int remain = KEY_SIZE, n;
365 unsigned char key[KEY_SIZE];
366 unsigned char *p;
367 unsigned char response[16];
368 struct MD5Context ch_ctx;
369 struct timeval tv;
370 fd_set rfds;
372 pthread_mutex_lock(&auth_mutex);
373 if (!auth_enabled) {
374 pthread_mutex_unlock(&auth_mutex);
375 return 0;
378 memcpy(&ch_ctx, &auth_md5_ctx, sizeof(ch_ctx));
379 pthread_mutex_unlock(&auth_mutex);
381 #ifdef DEBUG
382 printf("Awaiting challenge\n");
383 #endif
385 /* Grab the challenge from the server */
386 tv.tv_sec = 5;
387 tv.tv_usec = 0;
388 remain = KEY_SIZE;
389 p = key;
391 while (remain && (tv.tv_sec || tv.tv_usec)) {
392 FD_ZERO(&rfds);
393 FD_SET(fd, &rfds);
395 n = select(fd+1, &rfds, NULL, NULL, &tv);
396 if (n == -1) {
397 if (errno == EINTR)
398 continue;
399 return -1;
401 if (n == 0) {
402 errno = ETIMEDOUT;
403 return -1;
406 n = read(fd, p, remain);
407 if (n == -1) {
408 if (errno == EINTR)
409 continue;
410 return -1;
412 if (n == 0) {
413 errno = EPIPE;
414 return -1;
417 remain -= n;
418 p += n;
421 if (remain)
422 return -1;
424 #ifdef DEBUG
425 printf("Received: ");
426 hex_dump(key, KEY_SIZE);
427 printf("\n");
428 #endif
429 /* Build response */
430 MD5Update(&ch_ctx, (uint8_t *)key, KEY_SIZE);
431 MD5Final(response, &ch_ctx);
433 remain = 16;
434 p = response;
435 while (remain) {
436 n = write(fd, p, remain);
437 if (n == -1) {
438 if (errno == EINTR)
439 continue;
440 return -1;
442 if (n == 0)
443 return -1;
445 p += n;
446 remain -= n;
449 #ifdef DEBUG
450 printf("Responded with: ");
451 hex_dump(response, 16);
452 printf("\n");
453 #endif
455 /* wait for reply from server as to whether or not we
456 are authenticated */
457 p = key; /* Reuse */
458 while (tv.tv_sec || tv.tv_usec) {
459 FD_ZERO(&rfds);
460 FD_SET(fd, &rfds);
462 n = select(fd+1, &rfds, NULL, NULL, &tv);
463 if (n == -1) {
464 if (errno == EINTR)
465 continue;
466 return -1;
468 if (n == 0) {
469 errno = ETIMEDOUT;
470 return -1;
473 n = read(fd, p, 1);
474 if (n == -1) {
475 if (errno == EINTR)
476 continue;
477 return -1;
479 if (n == 0) {
480 errno = EPIPE;
481 return -1;
483 break;
486 if (*p == 0) {
487 return 0;
490 /* Permission denied */
491 errno = EACCES;
492 return -1;
496 #ifdef STANDALONE
497 #include <arpa/inet.h>
498 #include <stdio.h>
500 #ifdef SERVER
502 main(int argc, char **argv)
504 char buf[256];
505 int fd, newfd, len, flag, pid;
506 struct sockaddr_in sai;
507 fd_set rfds;
509 printf("TCP auth_md5 Echo Server\n");
510 if (argc < 2) {
511 fprintf(stderr,"usage: %s <port> [password]\n", argv[0]);
512 return 1;
515 if (argc >= 3)
516 auth_md5_init(argv[2], strlen(argv[2]));
517 else
518 auth_md5_init(NULL, 0);
520 fd = socket(PF_INET, SOCK_STREAM, 0);
521 memset(&sai, 0, sizeof(sai));
522 sai.sin_family = PF_INET;
523 sai.sin_port = htons(atoi(argv[1]));
524 sai.sin_addr.s_addr = htonl(INADDR_ANY);
526 flag = 1;
527 setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
528 &flag, sizeof(flag));
530 if (bind(fd, (struct sockaddr *)&sai, sizeof(sai)) == -1) {
531 perror("bind");
532 return(-1);
535 if (listen(fd, 5) == -1) {
536 perror("listen");
537 return(-1);
540 len = sizeof(sai);
541 while (1) {
542 newfd = accept(fd, (struct sockaddr *)&sai, &len);
544 if (newfd == -1) {
545 perror("accept");
546 return(-1);
549 if (auth_md5_challenge(newfd) == -1) {
550 perror("auth_md5_challenge");
551 close(newfd);
552 continue;
555 pid = fork();
556 if (pid == 0) {
557 close(fd);
558 break;
561 if (pid == -1) {
562 perror("fork");
563 continue;
566 printf("New Child: PID%d\n", pid);
567 close(newfd);
570 pid = getpid();
571 while(1) {
572 FD_ZERO(&rfds);
573 FD_SET(newfd, &rfds);
575 if (select(newfd + 1, &rfds, NULL, NULL, NULL) == 1) {
576 memset(buf,0,sizeof(buf));
577 switch(len = read(newfd, buf, sizeof(buf))) {
578 case 0:
579 printf("[%d] Connection closed by remote host\n",
580 pid);
581 exit(0);
582 case -1:
583 fprintf(stderr, "[%d] read: %s", pid,
584 strerror(errno));
585 return(0);
586 default:
587 write(newfd, buf, len);
592 #else
595 main(int argc, char **argv)
597 char buf[256];
598 int fd, len;
599 struct sockaddr_in sai;
600 fd_set rfds;
602 printf("TCP auth_md5 Echo Client\n");
604 if (argc < 2) {
605 fprintf(stderr, "usage: %s <port> [password]\n",
606 argv[0]);
607 return 1;
610 if (argc >= 3)
611 auth_md5_init(argv[2], strlen(argv[2]));
612 else
613 auth_md5_init(NULL, 0);
615 fd = socket(PF_INET, SOCK_STREAM, 0);
616 memset(&sai, 0, sizeof(sai));
617 sai.sin_family = PF_INET;
618 sai.sin_port = htons(atoi(argv[1]));
619 sai.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
621 if (connect(fd, (struct sockaddr *)&sai, sizeof(sai)) == -1) {
622 perror("connect");
623 return(-1);
626 if (auth_md5(fd) != 0) {
627 perror("auth_md5");
628 return(-1);
631 printf("Authenticated. Type away.\n");
632 while(1) {
633 FD_ZERO(&rfds);
634 FD_SET(STDIN_FILENO,&rfds);
635 FD_SET(fd,&rfds);
637 select(1024, &rfds, NULL, NULL, NULL);
639 if (FD_ISSET(STDIN_FILENO,&rfds)) {
640 len = read(STDIN_FILENO, buf, sizeof(buf));
642 if (!len) {
643 printf("Connection closed by remote host\n");
644 return(0);
647 if (write(fd, buf, len) == -1) {
648 perror("write");
649 return(-1);
653 if (FD_ISSET(fd, &rfds)) {
654 memset(buf,0,sizeof(buf));
655 if (read(fd, buf, sizeof(buf)) == -1) {
656 perror("read");
657 return(-1);
660 printf("%s", buf);
664 return(0);
666 #endif
667 #endif