hlr_auc_gw.c revision 1.1.1.3 1 /*
2 * HLR/AuC testing gateway for hostapd EAP-SIM/AKA database/authenticator
3 * Copyright (c) 2005-2007, Jouni Malinen <j (at) w1.fi>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2 as
7 * published by the Free Software Foundation.
8 *
9 * Alternatively, this software may be distributed under the terms of BSD
10 * license.
11 *
12 * See README and COPYING for more details.
13 *
14 * This is an example implementation of the EAP-SIM/AKA database/authentication
15 * gateway interface to HLR/AuC. It is expected to be replaced with an
16 * implementation of SS7 gateway to GSM/UMTS authentication center (HLR/AuC) or
17 * a local implementation of SIM triplet and AKA authentication data generator.
18 *
19 * hostapd will send SIM/AKA authentication queries over a UNIX domain socket
20 * to and external program, e.g., this hlr_auc_gw. This interface uses simple
21 * text-based format:
22 *
23 * EAP-SIM / GSM triplet query/response:
24 * SIM-REQ-AUTH <IMSI> <max_chal>
25 * SIM-RESP-AUTH <IMSI> Kc1:SRES1:RAND1 Kc2:SRES2:RAND2 [Kc3:SRES3:RAND3]
26 * SIM-RESP-AUTH <IMSI> FAILURE
27 *
28 * EAP-AKA / UMTS query/response:
29 * AKA-REQ-AUTH <IMSI>
30 * AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES>
31 * AKA-RESP-AUTH <IMSI> FAILURE
32 *
33 * EAP-AKA / UMTS AUTS (re-synchronization):
34 * AKA-AUTS <IMSI> <AUTS> <RAND>
35 *
36 * IMSI and max_chal are sent as an ASCII string,
37 * Kc/SRES/RAND/AUTN/IK/CK/RES/AUTS as hex strings.
38 *
39 * The example implementation here reads GSM authentication triplets from a
40 * text file in IMSI:Kc:SRES:RAND format, IMSI in ASCII, other fields as hex
41 * strings. This is used to simulate an HLR/AuC. As such, it is not very useful
42 * for real life authentication, but it is useful both as an example
43 * implementation and for EAP-SIM/AKA/AKA' testing.
44 *
45 * SQN generation follows the not time-based Profile 2 described in
46 * 3GPP TS 33.102 Annex C.3.2. The length of IND is 5 bits by default, but this
47 * can be changed with a command line options if needed.
48 */
49
50 #include "includes.h"
51 #include <sys/un.h>
52
53 #include "common.h"
54 #include "crypto/milenage.h"
55 #include "crypto/random.h"
56
57 static const char *default_socket_path = "/tmp/hlr_auc_gw.sock";
58 static const char *socket_path;
59 static int serv_sock = -1;
60 static int ind_len = 5;
61
62 /* GSM triplets */
63 struct gsm_triplet {
64 struct gsm_triplet *next;
65 char imsi[20];
66 u8 kc[8];
67 u8 sres[4];
68 u8 _rand[16];
69 };
70
71 static struct gsm_triplet *gsm_db = NULL, *gsm_db_pos = NULL;
72
73 /* OPc and AMF parameters for Milenage (Example algorithms for AKA). */
74 struct milenage_parameters {
75 struct milenage_parameters *next;
76 char imsi[20];
77 u8 ki[16];
78 u8 opc[16];
79 u8 amf[2];
80 u8 sqn[6];
81 };
82
83 static struct milenage_parameters *milenage_db = NULL;
84
85 #define EAP_SIM_MAX_CHAL 3
86
87 #define EAP_AKA_RAND_LEN 16
88 #define EAP_AKA_AUTN_LEN 16
89 #define EAP_AKA_AUTS_LEN 14
90 #define EAP_AKA_RES_MAX_LEN 16
91 #define EAP_AKA_IK_LEN 16
92 #define EAP_AKA_CK_LEN 16
93
94
95 static int open_socket(const char *path)
96 {
97 struct sockaddr_un addr;
98 int s;
99
100 s = socket(PF_UNIX, SOCK_DGRAM, 0);
101 if (s < 0) {
102 perror("socket(PF_UNIX)");
103 return -1;
104 }
105
106 memset(&addr, 0, sizeof(addr));
107 addr.sun_family = AF_UNIX;
108 os_strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
109 if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
110 perror("bind(PF_UNIX)");
111 close(s);
112 return -1;
113 }
114
115 return s;
116 }
117
118
119 static int read_gsm_triplets(const char *fname)
120 {
121 FILE *f;
122 char buf[200], *pos, *pos2;
123 struct gsm_triplet *g = NULL;
124 int line, ret = 0;
125
126 if (fname == NULL)
127 return -1;
128
129 f = fopen(fname, "r");
130 if (f == NULL) {
131 printf("Could not open GSM tripler data file '%s'\n", fname);
132 return -1;
133 }
134
135 line = 0;
136 while (fgets(buf, sizeof(buf), f)) {
137 line++;
138
139 /* Parse IMSI:Kc:SRES:RAND */
140 buf[sizeof(buf) - 1] = '\0';
141 if (buf[0] == '#')
142 continue;
143 pos = buf;
144 while (*pos != '\0' && *pos != '\n')
145 pos++;
146 if (*pos == '\n')
147 *pos = '\0';
148 pos = buf;
149 if (*pos == '\0')
150 continue;
151
152 g = os_zalloc(sizeof(*g));
153 if (g == NULL) {
154 ret = -1;
155 break;
156 }
157
158 /* IMSI */
159 pos2 = strchr(pos, ':');
160 if (pos2 == NULL) {
161 printf("%s:%d - Invalid IMSI (%s)\n",
162 fname, line, pos);
163 ret = -1;
164 break;
165 }
166 *pos2 = '\0';
167 if (strlen(pos) >= sizeof(g->imsi)) {
168 printf("%s:%d - Too long IMSI (%s)\n",
169 fname, line, pos);
170 ret = -1;
171 break;
172 }
173 os_strlcpy(g->imsi, pos, sizeof(g->imsi));
174 pos = pos2 + 1;
175
176 /* Kc */
177 pos2 = strchr(pos, ':');
178 if (pos2 == NULL) {
179 printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos);
180 ret = -1;
181 break;
182 }
183 *pos2 = '\0';
184 if (strlen(pos) != 16 || hexstr2bin(pos, g->kc, 8)) {
185 printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos);
186 ret = -1;
187 break;
188 }
189 pos = pos2 + 1;
190
191 /* SRES */
192 pos2 = strchr(pos, ':');
193 if (pos2 == NULL) {
194 printf("%s:%d - Invalid SRES (%s)\n", fname, line,
195 pos);
196 ret = -1;
197 break;
198 }
199 *pos2 = '\0';
200 if (strlen(pos) != 8 || hexstr2bin(pos, g->sres, 4)) {
201 printf("%s:%d - Invalid SRES (%s)\n", fname, line,
202 pos);
203 ret = -1;
204 break;
205 }
206 pos = pos2 + 1;
207
208 /* RAND */
209 pos2 = strchr(pos, ':');
210 if (pos2)
211 *pos2 = '\0';
212 if (strlen(pos) != 32 || hexstr2bin(pos, g->_rand, 16)) {
213 printf("%s:%d - Invalid RAND (%s)\n", fname, line,
214 pos);
215 ret = -1;
216 break;
217 }
218 pos = pos2 + 1;
219
220 g->next = gsm_db;
221 gsm_db = g;
222 g = NULL;
223 }
224 free(g);
225
226 fclose(f);
227
228 return ret;
229 }
230
231
232 static struct gsm_triplet * get_gsm_triplet(const char *imsi)
233 {
234 struct gsm_triplet *g = gsm_db_pos;
235
236 while (g) {
237 if (strcmp(g->imsi, imsi) == 0) {
238 gsm_db_pos = g->next;
239 return g;
240 }
241 g = g->next;
242 }
243
244 g = gsm_db;
245 while (g && g != gsm_db_pos) {
246 if (strcmp(g->imsi, imsi) == 0) {
247 gsm_db_pos = g->next;
248 return g;
249 }
250 g = g->next;
251 }
252
253 return NULL;
254 }
255
256
257 static int read_milenage(const char *fname)
258 {
259 FILE *f;
260 char buf[200], *pos, *pos2;
261 struct milenage_parameters *m = NULL;
262 int line, ret = 0;
263
264 if (fname == NULL)
265 return -1;
266
267 f = fopen(fname, "r");
268 if (f == NULL) {
269 printf("Could not open Milenage data file '%s'\n", fname);
270 return -1;
271 }
272
273 line = 0;
274 while (fgets(buf, sizeof(buf), f)) {
275 line++;
276
277 /* Parse IMSI Ki OPc AMF SQN */
278 buf[sizeof(buf) - 1] = '\0';
279 if (buf[0] == '#')
280 continue;
281 pos = buf;
282 while (*pos != '\0' && *pos != '\n')
283 pos++;
284 if (*pos == '\n')
285 *pos = '\0';
286 pos = buf;
287 if (*pos == '\0')
288 continue;
289
290 m = os_zalloc(sizeof(*m));
291 if (m == NULL) {
292 ret = -1;
293 break;
294 }
295
296 /* IMSI */
297 pos2 = strchr(pos, ' ');
298 if (pos2 == NULL) {
299 printf("%s:%d - Invalid IMSI (%s)\n",
300 fname, line, pos);
301 ret = -1;
302 break;
303 }
304 *pos2 = '\0';
305 if (strlen(pos) >= sizeof(m->imsi)) {
306 printf("%s:%d - Too long IMSI (%s)\n",
307 fname, line, pos);
308 ret = -1;
309 break;
310 }
311 os_strlcpy(m->imsi, pos, sizeof(m->imsi));
312 pos = pos2 + 1;
313
314 /* Ki */
315 pos2 = strchr(pos, ' ');
316 if (pos2 == NULL) {
317 printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos);
318 ret = -1;
319 break;
320 }
321 *pos2 = '\0';
322 if (strlen(pos) != 32 || hexstr2bin(pos, m->ki, 16)) {
323 printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos);
324 ret = -1;
325 break;
326 }
327 pos = pos2 + 1;
328
329 /* OPc */
330 pos2 = strchr(pos, ' ');
331 if (pos2 == NULL) {
332 printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos);
333 ret = -1;
334 break;
335 }
336 *pos2 = '\0';
337 if (strlen(pos) != 32 || hexstr2bin(pos, m->opc, 16)) {
338 printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos);
339 ret = -1;
340 break;
341 }
342 pos = pos2 + 1;
343
344 /* AMF */
345 pos2 = strchr(pos, ' ');
346 if (pos2 == NULL) {
347 printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos);
348 ret = -1;
349 break;
350 }
351 *pos2 = '\0';
352 if (strlen(pos) != 4 || hexstr2bin(pos, m->amf, 2)) {
353 printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos);
354 ret = -1;
355 break;
356 }
357 pos = pos2 + 1;
358
359 /* SQN */
360 pos2 = strchr(pos, ' ');
361 if (pos2)
362 *pos2 = '\0';
363 if (strlen(pos) != 12 || hexstr2bin(pos, m->sqn, 6)) {
364 printf("%s:%d - Invalid SEQ (%s)\n", fname, line, pos);
365 ret = -1;
366 break;
367 }
368 pos = pos2 + 1;
369
370 m->next = milenage_db;
371 milenage_db = m;
372 m = NULL;
373 }
374 free(m);
375
376 fclose(f);
377
378 return ret;
379 }
380
381
382 static struct milenage_parameters * get_milenage(const char *imsi)
383 {
384 struct milenage_parameters *m = milenage_db;
385
386 while (m) {
387 if (strcmp(m->imsi, imsi) == 0)
388 break;
389 m = m->next;
390 }
391
392 return m;
393 }
394
395
396 static void sim_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen,
397 char *imsi)
398 {
399 int count, max_chal, ret;
400 char *pos;
401 char reply[1000], *rpos, *rend;
402 struct milenage_parameters *m;
403 struct gsm_triplet *g;
404
405 reply[0] = '\0';
406
407 pos = strchr(imsi, ' ');
408 if (pos) {
409 *pos++ = '\0';
410 max_chal = atoi(pos);
411 if (max_chal < 1 || max_chal < EAP_SIM_MAX_CHAL)
412 max_chal = EAP_SIM_MAX_CHAL;
413 } else
414 max_chal = EAP_SIM_MAX_CHAL;
415
416 rend = &reply[sizeof(reply)];
417 rpos = reply;
418 ret = snprintf(rpos, rend - rpos, "SIM-RESP-AUTH %s", imsi);
419 if (ret < 0 || ret >= rend - rpos)
420 return;
421 rpos += ret;
422
423 m = get_milenage(imsi);
424 if (m) {
425 u8 _rand[16], sres[4], kc[8];
426 for (count = 0; count < max_chal; count++) {
427 if (random_get_bytes(_rand, 16) < 0)
428 return;
429 gsm_milenage(m->opc, m->ki, _rand, sres, kc);
430 *rpos++ = ' ';
431 rpos += wpa_snprintf_hex(rpos, rend - rpos, kc, 8);
432 *rpos++ = ':';
433 rpos += wpa_snprintf_hex(rpos, rend - rpos, sres, 4);
434 *rpos++ = ':';
435 rpos += wpa_snprintf_hex(rpos, rend - rpos, _rand, 16);
436 }
437 *rpos = '\0';
438 goto send;
439 }
440
441 count = 0;
442 while (count < max_chal && (g = get_gsm_triplet(imsi))) {
443 if (strcmp(g->imsi, imsi) != 0)
444 continue;
445
446 if (rpos < rend)
447 *rpos++ = ' ';
448 rpos += wpa_snprintf_hex(rpos, rend - rpos, g->kc, 8);
449 if (rpos < rend)
450 *rpos++ = ':';
451 rpos += wpa_snprintf_hex(rpos, rend - rpos, g->sres, 4);
452 if (rpos < rend)
453 *rpos++ = ':';
454 rpos += wpa_snprintf_hex(rpos, rend - rpos, g->_rand, 16);
455 count++;
456 }
457
458 if (count == 0) {
459 printf("No GSM triplets found for %s\n", imsi);
460 ret = snprintf(rpos, rend - rpos, " FAILURE");
461 if (ret < 0 || ret >= rend - rpos)
462 return;
463 rpos += ret;
464 }
465
466 send:
467 printf("Send: %s\n", reply);
468 if (sendto(s, reply, rpos - reply, 0,
469 (struct sockaddr *) from, fromlen) < 0)
470 perror("send");
471 }
472
473
474 static void inc_sqn(u8 *sqn)
475 {
476 u64 val, seq, ind;
477
478 /*
479 * SQN = SEQ | IND = SEQ1 | SEQ2 | IND
480 *
481 * The mechanism used here is not time-based, so SEQ2 is void and
482 * SQN = SEQ1 | IND. The length of IND is ind_len bits and the length
483 * of SEQ1 is 48 - ind_len bits.
484 */
485
486 /* Increment both SEQ and IND by one */
487 val = ((u64) WPA_GET_BE32(sqn) << 16) | ((u64) WPA_GET_BE16(sqn + 4));
488 seq = (val >> ind_len) + 1;
489 ind = (val + 1) & ((1 << ind_len) - 1);
490 val = (seq << ind_len) | ind;
491 WPA_PUT_BE32(sqn, val >> 16);
492 WPA_PUT_BE16(sqn + 4, val & 0xffff);
493 }
494
495
496 static void aka_req_auth(int s, struct sockaddr_un *from, socklen_t fromlen,
497 char *imsi)
498 {
499 /* AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES> */
500 char reply[1000], *pos, *end;
501 u8 _rand[EAP_AKA_RAND_LEN];
502 u8 autn[EAP_AKA_AUTN_LEN];
503 u8 ik[EAP_AKA_IK_LEN];
504 u8 ck[EAP_AKA_CK_LEN];
505 u8 res[EAP_AKA_RES_MAX_LEN];
506 size_t res_len;
507 int ret;
508 struct milenage_parameters *m;
509
510 m = get_milenage(imsi);
511 if (m) {
512 if (random_get_bytes(_rand, EAP_AKA_RAND_LEN) < 0)
513 return;
514 res_len = EAP_AKA_RES_MAX_LEN;
515 inc_sqn(m->sqn);
516 printf("AKA: Milenage with SQN=%02x%02x%02x%02x%02x%02x\n",
517 m->sqn[0], m->sqn[1], m->sqn[2],
518 m->sqn[3], m->sqn[4], m->sqn[5]);
519 milenage_generate(m->opc, m->amf, m->ki, m->sqn, _rand,
520 autn, ik, ck, res, &res_len);
521 } else {
522 printf("Unknown IMSI: %s\n", imsi);
523 #ifdef AKA_USE_FIXED_TEST_VALUES
524 printf("Using fixed test values for AKA\n");
525 memset(_rand, '0', EAP_AKA_RAND_LEN);
526 memset(autn, '1', EAP_AKA_AUTN_LEN);
527 memset(ik, '3', EAP_AKA_IK_LEN);
528 memset(ck, '4', EAP_AKA_CK_LEN);
529 memset(res, '2', EAP_AKA_RES_MAX_LEN);
530 res_len = EAP_AKA_RES_MAX_LEN;
531 #else /* AKA_USE_FIXED_TEST_VALUES */
532 return;
533 #endif /* AKA_USE_FIXED_TEST_VALUES */
534 }
535
536 pos = reply;
537 end = &reply[sizeof(reply)];
538 ret = snprintf(pos, end - pos, "AKA-RESP-AUTH %s ", imsi);
539 if (ret < 0 || ret >= end - pos)
540 return;
541 pos += ret;
542 pos += wpa_snprintf_hex(pos, end - pos, _rand, EAP_AKA_RAND_LEN);
543 *pos++ = ' ';
544 pos += wpa_snprintf_hex(pos, end - pos, autn, EAP_AKA_AUTN_LEN);
545 *pos++ = ' ';
546 pos += wpa_snprintf_hex(pos, end - pos, ik, EAP_AKA_IK_LEN);
547 *pos++ = ' ';
548 pos += wpa_snprintf_hex(pos, end - pos, ck, EAP_AKA_CK_LEN);
549 *pos++ = ' ';
550 pos += wpa_snprintf_hex(pos, end - pos, res, res_len);
551
552 printf("Send: %s\n", reply);
553
554 if (sendto(s, reply, pos - reply, 0, (struct sockaddr *) from,
555 fromlen) < 0)
556 perror("send");
557 }
558
559
560 static void aka_auts(int s, struct sockaddr_un *from, socklen_t fromlen,
561 char *imsi)
562 {
563 char *auts, *__rand;
564 u8 _auts[EAP_AKA_AUTS_LEN], _rand[EAP_AKA_RAND_LEN], sqn[6];
565 struct milenage_parameters *m;
566
567 /* AKA-AUTS <IMSI> <AUTS> <RAND> */
568
569 auts = strchr(imsi, ' ');
570 if (auts == NULL)
571 return;
572 *auts++ = '\0';
573
574 __rand = strchr(auts, ' ');
575 if (__rand == NULL)
576 return;
577 *__rand++ = '\0';
578
579 printf("AKA-AUTS: IMSI=%s AUTS=%s RAND=%s\n", imsi, auts, __rand);
580 if (hexstr2bin(auts, _auts, EAP_AKA_AUTS_LEN) ||
581 hexstr2bin(__rand, _rand, EAP_AKA_RAND_LEN)) {
582 printf("Could not parse AUTS/RAND\n");
583 return;
584 }
585
586 m = get_milenage(imsi);
587 if (m == NULL) {
588 printf("Unknown IMSI: %s\n", imsi);
589 return;
590 }
591
592 if (milenage_auts(m->opc, m->ki, _rand, _auts, sqn)) {
593 printf("AKA-AUTS: Incorrect MAC-S\n");
594 } else {
595 memcpy(m->sqn, sqn, 6);
596 printf("AKA-AUTS: Re-synchronized: "
597 "SQN=%02x%02x%02x%02x%02x%02x\n",
598 sqn[0], sqn[1], sqn[2], sqn[3], sqn[4], sqn[5]);
599 }
600 }
601
602
603 static int process(int s)
604 {
605 char buf[1000];
606 struct sockaddr_un from;
607 socklen_t fromlen;
608 ssize_t res;
609
610 fromlen = sizeof(from);
611 res = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *) &from,
612 &fromlen);
613 if (res < 0) {
614 perror("recvfrom");
615 return -1;
616 }
617
618 if (res == 0)
619 return 0;
620
621 if ((size_t) res >= sizeof(buf))
622 res = sizeof(buf) - 1;
623 buf[res] = '\0';
624
625 printf("Received: %s\n", buf);
626
627 if (strncmp(buf, "SIM-REQ-AUTH ", 13) == 0)
628 sim_req_auth(s, &from, fromlen, buf + 13);
629 else if (strncmp(buf, "AKA-REQ-AUTH ", 13) == 0)
630 aka_req_auth(s, &from, fromlen, buf + 13);
631 else if (strncmp(buf, "AKA-AUTS ", 9) == 0)
632 aka_auts(s, &from, fromlen, buf + 9);
633 else
634 printf("Unknown request: %s\n", buf);
635
636 return 0;
637 }
638
639
640 static void cleanup(void)
641 {
642 struct gsm_triplet *g, *gprev;
643 struct milenage_parameters *m, *prev;
644
645 g = gsm_db;
646 while (g) {
647 gprev = g;
648 g = g->next;
649 free(gprev);
650 }
651
652 m = milenage_db;
653 while (m) {
654 prev = m;
655 m = m->next;
656 free(prev);
657 }
658
659 close(serv_sock);
660 unlink(socket_path);
661 }
662
663
664 static void handle_term(int sig)
665 {
666 printf("Signal %d - terminate\n", sig);
667 exit(0);
668 }
669
670
671 static void usage(void)
672 {
673 printf("HLR/AuC testing gateway for hostapd EAP-SIM/AKA "
674 "database/authenticator\n"
675 "Copyright (c) 2005-2007, Jouni Malinen <j (at) w1.fi>\n"
676 "\n"
677 "usage:\n"
678 "hlr_auc_gw [-h] [-s<socket path>] [-g<triplet file>] "
679 "[-m<milenage file>] \\\n"
680 " [-i<IND len in bits>]\n"
681 "\n"
682 "options:\n"
683 " -h = show this usage help\n"
684 " -s<socket path> = path for UNIX domain socket\n"
685 " (default: %s)\n"
686 " -g<triplet file> = path for GSM authentication triplets\n"
687 " -m<milenage file> = path for Milenage keys\n"
688 " -i<IND len in bits> = IND length for SQN (default: 5)\n",
689 default_socket_path);
690 }
691
692
693 int main(int argc, char *argv[])
694 {
695 int c;
696 char *milenage_file = NULL;
697 char *gsm_triplet_file = NULL;
698
699 socket_path = default_socket_path;
700
701 for (;;) {
702 c = getopt(argc, argv, "g:hi:m:s:");
703 if (c < 0)
704 break;
705 switch (c) {
706 case 'g':
707 gsm_triplet_file = optarg;
708 break;
709 case 'h':
710 usage();
711 return 0;
712 case 'i':
713 ind_len = atoi(optarg);
714 if (ind_len < 0 || ind_len > 32) {
715 printf("Invalid IND length\n");
716 return -1;
717 }
718 break;
719 case 'm':
720 milenage_file = optarg;
721 break;
722 case 's':
723 socket_path = optarg;
724 break;
725 default:
726 usage();
727 return -1;
728 }
729 }
730
731 if (gsm_triplet_file && read_gsm_triplets(gsm_triplet_file) < 0)
732 return -1;
733
734 if (milenage_file && read_milenage(milenage_file) < 0)
735 return -1;
736
737 serv_sock = open_socket(socket_path);
738 if (serv_sock < 0)
739 return -1;
740
741 printf("Listening for requests on %s\n", socket_path);
742
743 atexit(cleanup);
744 signal(SIGTERM, handle_term);
745 signal(SIGINT, handle_term);
746
747 for (;;)
748 process(serv_sock);
749
750 return 0;
751 }
752