Branch data Line data Source code
1 : : /*
2 : : * HLR/AuC testing gateway for hostapd EAP-SIM/AKA database/authenticator
3 : : * Copyright (c) 2005-2007, 2012-2013, Jouni Malinen <j@w1.fi>
4 : : *
5 : : * This software may be distributed under the terms of the BSD license.
6 : : * See README for more details.
7 : : *
8 : : * This is an example implementation of the EAP-SIM/AKA database/authentication
9 : : * gateway interface to HLR/AuC. It is expected to be replaced with an
10 : : * implementation of SS7 gateway to GSM/UMTS authentication center (HLR/AuC) or
11 : : * a local implementation of SIM triplet and AKA authentication data generator.
12 : : *
13 : : * hostapd will send SIM/AKA authentication queries over a UNIX domain socket
14 : : * to and external program, e.g., this hlr_auc_gw. This interface uses simple
15 : : * text-based format:
16 : : *
17 : : * EAP-SIM / GSM triplet query/response:
18 : : * SIM-REQ-AUTH <IMSI> <max_chal>
19 : : * SIM-RESP-AUTH <IMSI> Kc1:SRES1:RAND1 Kc2:SRES2:RAND2 [Kc3:SRES3:RAND3]
20 : : * SIM-RESP-AUTH <IMSI> FAILURE
21 : : * GSM-AUTH-REQ <IMSI> RAND1:RAND2[:RAND3]
22 : : * GSM-AUTH-RESP <IMSI> Kc1:SRES1:Kc2:SRES2[:Kc3:SRES3]
23 : : * GSM-AUTH-RESP <IMSI> FAILURE
24 : : *
25 : : * EAP-AKA / UMTS query/response:
26 : : * AKA-REQ-AUTH <IMSI>
27 : : * AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES>
28 : : * AKA-RESP-AUTH <IMSI> FAILURE
29 : : *
30 : : * EAP-AKA / UMTS AUTS (re-synchronization):
31 : : * AKA-AUTS <IMSI> <AUTS> <RAND>
32 : : *
33 : : * IMSI and max_chal are sent as an ASCII string,
34 : : * Kc/SRES/RAND/AUTN/IK/CK/RES/AUTS as hex strings.
35 : : *
36 : : * An example implementation here reads GSM authentication triplets from a
37 : : * text file in IMSI:Kc:SRES:RAND format, IMSI in ASCII, other fields as hex
38 : : * strings. This is used to simulate an HLR/AuC. As such, it is not very useful
39 : : * for real life authentication, but it is useful both as an example
40 : : * implementation and for EAP-SIM/AKA/AKA' testing.
41 : : *
42 : : * For a stronger example design, Milenage and GSM-Milenage algorithms can be
43 : : * used to dynamically generate authenticatipn information for EAP-AKA/AKA' and
44 : : * EAP-SIM, respectively, if Ki is known.
45 : : *
46 : : * SQN generation follows the not time-based Profile 2 described in
47 : : * 3GPP TS 33.102 Annex C.3.2. The length of IND is 5 bits by default, but this
48 : : * can be changed with a command line options if needed.
49 : : */
50 : :
51 : : #include "includes.h"
52 : : #include <sys/un.h>
53 : : #ifdef CONFIG_SQLITE
54 : : #include <sqlite3.h>
55 : : #endif /* CONFIG_SQLITE */
56 : :
57 : : #include "common.h"
58 : : #include "crypto/milenage.h"
59 : : #include "crypto/random.h"
60 : :
61 : : static const char *default_socket_path = "/tmp/hlr_auc_gw.sock";
62 : : static const char *socket_path;
63 : : static int serv_sock = -1;
64 : : static char *milenage_file = NULL;
65 : : static int update_milenage = 0;
66 : : static int sqn_changes = 0;
67 : : static int ind_len = 5;
68 : : static int stdout_debug = 1;
69 : :
70 : : /* GSM triplets */
71 : : struct gsm_triplet {
72 : : struct gsm_triplet *next;
73 : : char imsi[20];
74 : : u8 kc[8];
75 : : u8 sres[4];
76 : : u8 _rand[16];
77 : : };
78 : :
79 : : static struct gsm_triplet *gsm_db = NULL, *gsm_db_pos = NULL;
80 : :
81 : : /* OPc and AMF parameters for Milenage (Example algorithms for AKA). */
82 : : struct milenage_parameters {
83 : : struct milenage_parameters *next;
84 : : char imsi[20];
85 : : u8 ki[16];
86 : : u8 opc[16];
87 : : u8 amf[2];
88 : : u8 sqn[6];
89 : : int set;
90 : : };
91 : :
92 : : static struct milenage_parameters *milenage_db = NULL;
93 : :
94 : : #define EAP_SIM_MAX_CHAL 3
95 : :
96 : : #define EAP_AKA_RAND_LEN 16
97 : : #define EAP_AKA_AUTN_LEN 16
98 : : #define EAP_AKA_AUTS_LEN 14
99 : : #define EAP_AKA_RES_MAX_LEN 16
100 : : #define EAP_AKA_IK_LEN 16
101 : : #define EAP_AKA_CK_LEN 16
102 : :
103 : :
104 : : #ifdef CONFIG_SQLITE
105 : :
106 : : static sqlite3 *sqlite_db = NULL;
107 : : static struct milenage_parameters db_tmp_milenage;
108 : :
109 : :
110 : : static int db_table_exists(sqlite3 *db, const char *name)
111 : : {
112 : : char cmd[128];
113 : : os_snprintf(cmd, sizeof(cmd), "SELECT 1 FROM %s;", name);
114 : : return sqlite3_exec(db, cmd, NULL, NULL, NULL) == SQLITE_OK;
115 : : }
116 : :
117 : :
118 : : static int db_table_create_milenage(sqlite3 *db)
119 : : {
120 : : char *err = NULL;
121 : : const char *sql =
122 : : "CREATE TABLE milenage("
123 : : " imsi INTEGER PRIMARY KEY NOT NULL,"
124 : : " ki CHAR(32) NOT NULL,"
125 : : " opc CHAR(32) NOT NULL,"
126 : : " amf CHAR(4) NOT NULL,"
127 : : " sqn CHAR(12) NOT NULL"
128 : : ");";
129 : :
130 : : printf("Adding database table for milenage information\n");
131 : : if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
132 : : printf("SQLite error: %s\n", err);
133 : : sqlite3_free(err);
134 : : return -1;
135 : : }
136 : :
137 : : return 0;
138 : : }
139 : :
140 : :
141 : : static sqlite3 * db_open(const char *db_file)
142 : : {
143 : : sqlite3 *db;
144 : :
145 : : if (sqlite3_open(db_file, &db)) {
146 : : printf("Failed to open database %s: %s\n",
147 : : db_file, sqlite3_errmsg(db));
148 : : sqlite3_close(db);
149 : : return NULL;
150 : : }
151 : :
152 : : if (!db_table_exists(db, "milenage") &&
153 : : db_table_create_milenage(db) < 0) {
154 : : sqlite3_close(db);
155 : : return NULL;
156 : : }
157 : :
158 : : return db;
159 : : }
160 : :
161 : :
162 : : static int get_milenage_cb(void *ctx, int argc, char *argv[], char *col[])
163 : : {
164 : : struct milenage_parameters *m = ctx;
165 : : int i;
166 : :
167 : : m->set = 1;
168 : :
169 : : for (i = 0; i < argc; i++) {
170 : : if (os_strcmp(col[i], "ki") == 0 && argv[i] &&
171 : : hexstr2bin(argv[i], m->ki, sizeof(m->ki))) {
172 : : printf("Invalid ki value in database\n");
173 : : return -1;
174 : : }
175 : :
176 : : if (os_strcmp(col[i], "opc") == 0 && argv[i] &&
177 : : hexstr2bin(argv[i], m->opc, sizeof(m->opc))) {
178 : : printf("Invalid opcvalue in database\n");
179 : : return -1;
180 : : }
181 : :
182 : : if (os_strcmp(col[i], "amf") == 0 && argv[i] &&
183 : : hexstr2bin(argv[i], m->amf, sizeof(m->amf))) {
184 : : printf("Invalid amf value in database\n");
185 : : return -1;
186 : : }
187 : :
188 : : if (os_strcmp(col[i], "sqn") == 0 && argv[i] &&
189 : : hexstr2bin(argv[i], m->sqn, sizeof(m->sqn))) {
190 : : printf("Invalid sqn value in database\n");
191 : : return -1;
192 : : }
193 : : }
194 : :
195 : : return 0;
196 : : }
197 : :
198 : :
199 : : static struct milenage_parameters * db_get_milenage(const char *imsi_txt)
200 : : {
201 : : char cmd[128];
202 : : unsigned long long imsi;
203 : :
204 : : os_memset(&db_tmp_milenage, 0, sizeof(db_tmp_milenage));
205 : : imsi = atoll(imsi_txt);
206 : : os_snprintf(db_tmp_milenage.imsi, sizeof(db_tmp_milenage.imsi),
207 : : "%llu", imsi);
208 : : os_snprintf(cmd, sizeof(cmd),
209 : : "SELECT ki,opc,amf,sqn FROM milenage WHERE imsi=%llu;",
210 : : imsi);
211 : : if (sqlite3_exec(sqlite_db, cmd, get_milenage_cb, &db_tmp_milenage,
212 : : NULL) != SQLITE_OK)
213 : : return NULL;
214 : :
215 : : if (!db_tmp_milenage.set)
216 : : return NULL;
217 : : return &db_tmp_milenage;
218 : : }
219 : :
220 : :
221 : : static int db_update_milenage_sqn(struct milenage_parameters *m)
222 : : {
223 : : char cmd[128], val[13], *pos;
224 : :
225 : : if (sqlite_db == NULL)
226 : : return 0;
227 : :
228 : : pos = val;
229 : : pos += wpa_snprintf_hex(pos, sizeof(val), m->sqn, 6);
230 : : *pos = '\0';
231 : : os_snprintf(cmd, sizeof(cmd),
232 : : "UPDATE milenage SET sqn='%s' WHERE imsi=%s;",
233 : : val, m->imsi);
234 : : if (sqlite3_exec(sqlite_db, cmd, NULL, NULL, NULL) != SQLITE_OK) {
235 : : printf("Failed to update SQN in database for IMSI %s\n",
236 : : m->imsi);
237 : : return -1;
238 : : }
239 : : return 0;
240 : : }
241 : :
242 : : #endif /* CONFIG_SQLITE */
243 : :
244 : :
245 : 1 : static int open_socket(const char *path)
246 : : {
247 : : struct sockaddr_un addr;
248 : : int s;
249 : :
250 : 1 : s = socket(PF_UNIX, SOCK_DGRAM, 0);
251 [ - + ]: 1 : if (s < 0) {
252 : 0 : perror("socket(PF_UNIX)");
253 : 0 : return -1;
254 : : }
255 : :
256 : 1 : memset(&addr, 0, sizeof(addr));
257 : 1 : addr.sun_family = AF_UNIX;
258 : 1 : os_strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
259 [ - + ]: 1 : if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
260 : 0 : perror("hlr-auc-gw: bind(PF_UNIX)");
261 : 0 : close(s);
262 : 0 : return -1;
263 : : }
264 : :
265 : 1 : return s;
266 : : }
267 : :
268 : :
269 : 0 : static int read_gsm_triplets(const char *fname)
270 : : {
271 : : FILE *f;
272 : : char buf[200], *pos, *pos2;
273 : 0 : struct gsm_triplet *g = NULL;
274 : 0 : int line, ret = 0;
275 : :
276 [ # # ]: 0 : if (fname == NULL)
277 : 0 : return -1;
278 : :
279 : 0 : f = fopen(fname, "r");
280 [ # # ]: 0 : if (f == NULL) {
281 : 0 : printf("Could not open GSM tripler data file '%s'\n", fname);
282 : 0 : return -1;
283 : : }
284 : :
285 : 0 : line = 0;
286 [ # # ]: 0 : while (fgets(buf, sizeof(buf), f)) {
287 : 0 : line++;
288 : :
289 : : /* Parse IMSI:Kc:SRES:RAND */
290 : 0 : buf[sizeof(buf) - 1] = '\0';
291 [ # # ]: 0 : if (buf[0] == '#')
292 : 0 : continue;
293 : 0 : pos = buf;
294 [ # # ][ # # ]: 0 : while (*pos != '\0' && *pos != '\n')
295 : 0 : pos++;
296 [ # # ]: 0 : if (*pos == '\n')
297 : 0 : *pos = '\0';
298 : 0 : pos = buf;
299 [ # # ]: 0 : if (*pos == '\0')
300 : 0 : continue;
301 : :
302 : 0 : g = os_zalloc(sizeof(*g));
303 [ # # ]: 0 : if (g == NULL) {
304 : 0 : ret = -1;
305 : 0 : break;
306 : : }
307 : :
308 : : /* IMSI */
309 : 0 : pos2 = strchr(pos, ':');
310 [ # # ]: 0 : if (pos2 == NULL) {
311 : 0 : printf("%s:%d - Invalid IMSI (%s)\n",
312 : : fname, line, pos);
313 : 0 : ret = -1;
314 : 0 : break;
315 : : }
316 : 0 : *pos2 = '\0';
317 [ # # ]: 0 : if (strlen(pos) >= sizeof(g->imsi)) {
318 : 0 : printf("%s:%d - Too long IMSI (%s)\n",
319 : : fname, line, pos);
320 : 0 : ret = -1;
321 : 0 : break;
322 : : }
323 : 0 : os_strlcpy(g->imsi, pos, sizeof(g->imsi));
324 : 0 : pos = pos2 + 1;
325 : :
326 : : /* Kc */
327 : 0 : pos2 = strchr(pos, ':');
328 [ # # ]: 0 : if (pos2 == NULL) {
329 : 0 : printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos);
330 : 0 : ret = -1;
331 : 0 : break;
332 : : }
333 : 0 : *pos2 = '\0';
334 [ # # ][ # # ]: 0 : if (strlen(pos) != 16 || hexstr2bin(pos, g->kc, 8)) {
335 : 0 : printf("%s:%d - Invalid Kc (%s)\n", fname, line, pos);
336 : 0 : ret = -1;
337 : 0 : break;
338 : : }
339 : 0 : pos = pos2 + 1;
340 : :
341 : : /* SRES */
342 : 0 : pos2 = strchr(pos, ':');
343 [ # # ]: 0 : if (pos2 == NULL) {
344 : 0 : printf("%s:%d - Invalid SRES (%s)\n", fname, line,
345 : : pos);
346 : 0 : ret = -1;
347 : 0 : break;
348 : : }
349 : 0 : *pos2 = '\0';
350 [ # # ][ # # ]: 0 : if (strlen(pos) != 8 || hexstr2bin(pos, g->sres, 4)) {
351 : 0 : printf("%s:%d - Invalid SRES (%s)\n", fname, line,
352 : : pos);
353 : 0 : ret = -1;
354 : 0 : break;
355 : : }
356 : 0 : pos = pos2 + 1;
357 : :
358 : : /* RAND */
359 : 0 : pos2 = strchr(pos, ':');
360 [ # # ]: 0 : if (pos2)
361 : 0 : *pos2 = '\0';
362 [ # # ][ # # ]: 0 : if (strlen(pos) != 32 || hexstr2bin(pos, g->_rand, 16)) {
363 : 0 : printf("%s:%d - Invalid RAND (%s)\n", fname, line,
364 : : pos);
365 : 0 : ret = -1;
366 : 0 : break;
367 : : }
368 : 0 : pos = pos2 + 1;
369 : :
370 : 0 : g->next = gsm_db;
371 : 0 : gsm_db = g;
372 : 0 : g = NULL;
373 : : }
374 : 0 : os_free(g);
375 : :
376 : 0 : fclose(f);
377 : :
378 : 0 : return ret;
379 : : }
380 : :
381 : :
382 : 0 : static struct gsm_triplet * get_gsm_triplet(const char *imsi)
383 : : {
384 : 0 : struct gsm_triplet *g = gsm_db_pos;
385 : :
386 [ # # ]: 0 : while (g) {
387 [ # # ]: 0 : if (strcmp(g->imsi, imsi) == 0) {
388 : 0 : gsm_db_pos = g->next;
389 : 0 : return g;
390 : : }
391 : 0 : g = g->next;
392 : : }
393 : :
394 : 0 : g = gsm_db;
395 [ # # ][ # # ]: 0 : while (g && g != gsm_db_pos) {
396 [ # # ]: 0 : if (strcmp(g->imsi, imsi) == 0) {
397 : 0 : gsm_db_pos = g->next;
398 : 0 : return g;
399 : : }
400 : 0 : g = g->next;
401 : : }
402 : :
403 : 0 : return NULL;
404 : : }
405 : :
406 : :
407 : 1 : static int read_milenage(const char *fname)
408 : : {
409 : : FILE *f;
410 : : char buf[200], *pos, *pos2;
411 : 1 : struct milenage_parameters *m = NULL;
412 : 1 : int line, ret = 0;
413 : :
414 [ - + ]: 1 : if (fname == NULL)
415 : 0 : return -1;
416 : :
417 : 1 : f = fopen(fname, "r");
418 [ - + ]: 1 : if (f == NULL) {
419 : 0 : printf("Could not open Milenage data file '%s'\n", fname);
420 : 0 : return -1;
421 : : }
422 : :
423 : 1 : line = 0;
424 [ + + ]: 14 : while (fgets(buf, sizeof(buf), f)) {
425 : 13 : line++;
426 : :
427 : : /* Parse IMSI Ki OPc AMF SQN */
428 : 13 : buf[sizeof(buf) - 1] = '\0';
429 [ + + ]: 13 : if (buf[0] == '#')
430 : 9 : continue;
431 : 4 : pos = buf;
432 [ + - ][ + + ]: 202 : while (*pos != '\0' && *pos != '\n')
433 : 198 : pos++;
434 [ + - ]: 4 : if (*pos == '\n')
435 : 4 : *pos = '\0';
436 : 4 : pos = buf;
437 [ + + ]: 4 : if (*pos == '\0')
438 : 2 : continue;
439 : :
440 : 2 : m = os_zalloc(sizeof(*m));
441 [ - + ]: 2 : if (m == NULL) {
442 : 0 : ret = -1;
443 : 0 : break;
444 : : }
445 : :
446 : : /* IMSI */
447 : 2 : pos2 = strchr(pos, ' ');
448 [ - + ]: 2 : if (pos2 == NULL) {
449 : 0 : printf("%s:%d - Invalid IMSI (%s)\n",
450 : : fname, line, pos);
451 : 0 : ret = -1;
452 : 0 : break;
453 : : }
454 : 2 : *pos2 = '\0';
455 [ - + ]: 2 : if (strlen(pos) >= sizeof(m->imsi)) {
456 : 0 : printf("%s:%d - Too long IMSI (%s)\n",
457 : : fname, line, pos);
458 : 0 : ret = -1;
459 : 0 : break;
460 : : }
461 : 2 : os_strlcpy(m->imsi, pos, sizeof(m->imsi));
462 : 2 : pos = pos2 + 1;
463 : :
464 : : /* Ki */
465 : 2 : pos2 = strchr(pos, ' ');
466 [ - + ]: 2 : if (pos2 == NULL) {
467 : 0 : printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos);
468 : 0 : ret = -1;
469 : 0 : break;
470 : : }
471 : 2 : *pos2 = '\0';
472 [ + - ][ - + ]: 2 : if (strlen(pos) != 32 || hexstr2bin(pos, m->ki, 16)) {
473 : 0 : printf("%s:%d - Invalid Ki (%s)\n", fname, line, pos);
474 : 0 : ret = -1;
475 : 0 : break;
476 : : }
477 : 2 : pos = pos2 + 1;
478 : :
479 : : /* OPc */
480 : 2 : pos2 = strchr(pos, ' ');
481 [ - + ]: 2 : if (pos2 == NULL) {
482 : 0 : printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos);
483 : 0 : ret = -1;
484 : 0 : break;
485 : : }
486 : 2 : *pos2 = '\0';
487 [ + - ][ - + ]: 2 : if (strlen(pos) != 32 || hexstr2bin(pos, m->opc, 16)) {
488 : 0 : printf("%s:%d - Invalid OPc (%s)\n", fname, line, pos);
489 : 0 : ret = -1;
490 : 0 : break;
491 : : }
492 : 2 : pos = pos2 + 1;
493 : :
494 : : /* AMF */
495 : 2 : pos2 = strchr(pos, ' ');
496 [ - + ]: 2 : if (pos2 == NULL) {
497 : 0 : printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos);
498 : 0 : ret = -1;
499 : 0 : break;
500 : : }
501 : 2 : *pos2 = '\0';
502 [ + - ][ - + ]: 2 : if (strlen(pos) != 4 || hexstr2bin(pos, m->amf, 2)) {
503 : 0 : printf("%s:%d - Invalid AMF (%s)\n", fname, line, pos);
504 : 0 : ret = -1;
505 : 0 : break;
506 : : }
507 : 2 : pos = pos2 + 1;
508 : :
509 : : /* SQN */
510 : 2 : pos2 = strchr(pos, ' ');
511 [ - + ]: 2 : if (pos2)
512 : 0 : *pos2 = '\0';
513 [ + - ][ - + ]: 2 : if (strlen(pos) != 12 || hexstr2bin(pos, m->sqn, 6)) {
514 : 0 : printf("%s:%d - Invalid SEQ (%s)\n", fname, line, pos);
515 : 0 : ret = -1;
516 : 0 : break;
517 : : }
518 : 2 : pos = pos2 + 1;
519 : :
520 : 2 : m->next = milenage_db;
521 : 2 : milenage_db = m;
522 : 2 : m = NULL;
523 : : }
524 : 1 : os_free(m);
525 : :
526 : 1 : fclose(f);
527 : :
528 : 1 : return ret;
529 : : }
530 : :
531 : :
532 : 0 : static void update_milenage_file(const char *fname)
533 : : {
534 : : FILE *f, *f2;
535 : : char buf[500], *pos;
536 : 0 : char *end = buf + sizeof(buf);
537 : : struct milenage_parameters *m;
538 : : size_t imsi_len;
539 : :
540 : 0 : f = fopen(fname, "r");
541 [ # # ]: 0 : if (f == NULL) {
542 : 0 : printf("Could not open Milenage data file '%s'\n", fname);
543 : 0 : return;
544 : : }
545 : :
546 : 0 : snprintf(buf, sizeof(buf), "%s.new", fname);
547 : 0 : f2 = fopen(buf, "w");
548 [ # # ]: 0 : if (f2 == NULL) {
549 : 0 : printf("Could not write Milenage data file '%s'\n", buf);
550 : 0 : fclose(f);
551 : 0 : return;
552 : : }
553 : :
554 [ # # ]: 0 : while (fgets(buf, sizeof(buf), f)) {
555 : : /* IMSI Ki OPc AMF SQN */
556 : 0 : buf[sizeof(buf) - 1] = '\0';
557 : :
558 : 0 : pos = strchr(buf, ' ');
559 [ # # ][ # # ]: 0 : if (buf[0] == '#' || pos == NULL || pos - buf >= 20)
[ # # ]
560 : : goto no_update;
561 : :
562 : 0 : imsi_len = pos - buf;
563 : :
564 [ # # ]: 0 : for (m = milenage_db; m; m = m->next) {
565 [ # # ][ # # ]: 0 : if (strncmp(buf, m->imsi, imsi_len) == 0 &&
566 : 0 : m->imsi[imsi_len] == '\0')
567 : 0 : break;
568 : : }
569 : :
570 [ # # ]: 0 : if (!m)
571 : 0 : goto no_update;
572 : :
573 : 0 : pos = buf;
574 : 0 : pos += snprintf(pos, end - pos, "%s ", m->imsi);
575 : 0 : pos += wpa_snprintf_hex(pos, end - pos, m->ki, 16);
576 : 0 : *pos++ = ' ';
577 : 0 : pos += wpa_snprintf_hex(pos, end - pos, m->opc, 16);
578 : 0 : *pos++ = ' ';
579 : 0 : pos += wpa_snprintf_hex(pos, end - pos, m->amf, 2);
580 : 0 : *pos++ = ' ';
581 : 0 : pos += wpa_snprintf_hex(pos, end - pos, m->sqn, 6);
582 : 0 : *pos++ = '\n';
583 : :
584 : : no_update:
585 : 0 : fprintf(f2, "%s", buf);
586 : : }
587 : :
588 : 0 : fclose(f2);
589 : 0 : fclose(f);
590 : :
591 : 0 : snprintf(buf, sizeof(buf), "%s.bak", fname);
592 [ # # ]: 0 : if (rename(fname, buf) < 0) {
593 : 0 : perror("rename");
594 : 0 : return;
595 : : }
596 : :
597 : 0 : snprintf(buf, sizeof(buf), "%s.new", fname);
598 [ # # ]: 0 : if (rename(buf, fname) < 0) {
599 : 0 : perror("rename");
600 : 0 : return;
601 : : }
602 : :
603 : : }
604 : :
605 : :
606 : 17 : static struct milenage_parameters * get_milenage(const char *imsi)
607 : : {
608 : 17 : struct milenage_parameters *m = milenage_db;
609 : :
610 [ + - ]: 27 : while (m) {
611 [ + + ]: 27 : if (strcmp(m->imsi, imsi) == 0)
612 : 17 : break;
613 : 10 : m = m->next;
614 : : }
615 : :
616 : : #ifdef CONFIG_SQLITE
617 : : if (!m)
618 : : m = db_get_milenage(imsi);
619 : : #endif /* CONFIG_SQLITE */
620 : :
621 : 17 : return m;
622 : : }
623 : :
624 : :
625 : 9 : static int sim_req_auth(char *imsi, char *resp, size_t resp_len)
626 : : {
627 : : int count, max_chal, ret;
628 : : char *pos;
629 : : char *rpos, *rend;
630 : : struct milenage_parameters *m;
631 : : struct gsm_triplet *g;
632 : :
633 : 9 : resp[0] = '\0';
634 : :
635 : 9 : pos = strchr(imsi, ' ');
636 [ + - ]: 9 : if (pos) {
637 : 9 : *pos++ = '\0';
638 : 9 : max_chal = atoi(pos);
639 [ + - ][ - + ]: 9 : if (max_chal < 1 || max_chal > EAP_SIM_MAX_CHAL)
640 : 0 : max_chal = EAP_SIM_MAX_CHAL;
641 : : } else
642 : 0 : max_chal = EAP_SIM_MAX_CHAL;
643 : :
644 : 9 : rend = resp + resp_len;
645 : 9 : rpos = resp;
646 : 9 : ret = snprintf(rpos, rend - rpos, "SIM-RESP-AUTH %s", imsi);
647 [ + - ][ - + ]: 9 : if (ret < 0 || ret >= rend - rpos)
648 : 0 : return -1;
649 : 9 : rpos += ret;
650 : :
651 : 9 : m = get_milenage(imsi);
652 [ + - ]: 9 : if (m) {
653 : : u8 _rand[16], sres[4], kc[8];
654 [ + + ]: 36 : for (count = 0; count < max_chal; count++) {
655 [ - + ]: 27 : if (random_get_bytes(_rand, 16) < 0)
656 : 0 : return -1;
657 : 27 : gsm_milenage(m->opc, m->ki, _rand, sres, kc);
658 : 27 : *rpos++ = ' ';
659 : 27 : rpos += wpa_snprintf_hex(rpos, rend - rpos, kc, 8);
660 : 27 : *rpos++ = ':';
661 : 27 : rpos += wpa_snprintf_hex(rpos, rend - rpos, sres, 4);
662 : 27 : *rpos++ = ':';
663 : 27 : rpos += wpa_snprintf_hex(rpos, rend - rpos, _rand, 16);
664 : : }
665 : 9 : *rpos = '\0';
666 : 9 : return 0;
667 : : }
668 : :
669 : 0 : count = 0;
670 [ # # ][ # # ]: 0 : while (count < max_chal && (g = get_gsm_triplet(imsi))) {
671 [ # # ]: 0 : if (strcmp(g->imsi, imsi) != 0)
672 : 0 : continue;
673 : :
674 [ # # ]: 0 : if (rpos < rend)
675 : 0 : *rpos++ = ' ';
676 : 0 : rpos += wpa_snprintf_hex(rpos, rend - rpos, g->kc, 8);
677 [ # # ]: 0 : if (rpos < rend)
678 : 0 : *rpos++ = ':';
679 : 0 : rpos += wpa_snprintf_hex(rpos, rend - rpos, g->sres, 4);
680 [ # # ]: 0 : if (rpos < rend)
681 : 0 : *rpos++ = ':';
682 : 0 : rpos += wpa_snprintf_hex(rpos, rend - rpos, g->_rand, 16);
683 : 0 : count++;
684 : : }
685 : :
686 [ # # ]: 0 : if (count == 0) {
687 : 0 : printf("No GSM triplets found for %s\n", imsi);
688 : 0 : ret = snprintf(rpos, rend - rpos, " FAILURE");
689 [ # # ][ # # ]: 0 : if (ret < 0 || ret >= rend - rpos)
690 : 0 : return -1;
691 : 0 : rpos += ret;
692 : : }
693 : :
694 : 9 : return 0;
695 : : }
696 : :
697 : :
698 : 0 : static int gsm_auth_req(char *imsi, char *resp, size_t resp_len)
699 : : {
700 : : int count, ret;
701 : : char *pos, *rpos, *rend;
702 : : struct milenage_parameters *m;
703 : :
704 : 0 : resp[0] = '\0';
705 : :
706 : 0 : pos = os_strchr(imsi, ' ');
707 [ # # ]: 0 : if (!pos)
708 : 0 : return -1;
709 : 0 : *pos++ = '\0';
710 : :
711 : 0 : rend = resp + resp_len;
712 : 0 : rpos = resp;
713 : 0 : ret = os_snprintf(rpos, rend - rpos, "GSM-AUTH-RESP %s", imsi);
714 [ # # ][ # # ]: 0 : if (ret < 0 || ret >= rend - rpos)
715 : 0 : return -1;
716 : 0 : rpos += ret;
717 : :
718 : 0 : m = get_milenage(imsi);
719 [ # # ]: 0 : if (m) {
720 : : u8 _rand[16], sres[4], kc[8];
721 [ # # ]: 0 : for (count = 0; count < EAP_SIM_MAX_CHAL; count++) {
722 [ # # ]: 0 : if (hexstr2bin(pos, _rand, 16) != 0)
723 : 0 : return -1;
724 : 0 : gsm_milenage(m->opc, m->ki, _rand, sres, kc);
725 [ # # ]: 0 : *rpos++ = count == 0 ? ' ' : ':';
726 : 0 : rpos += wpa_snprintf_hex(rpos, rend - rpos, kc, 8);
727 : 0 : *rpos++ = ':';
728 : 0 : rpos += wpa_snprintf_hex(rpos, rend - rpos, sres, 4);
729 : 0 : pos += 16 * 2;
730 [ # # ]: 0 : if (*pos != ':')
731 : 0 : break;
732 : 0 : pos++;
733 : : }
734 : 0 : *rpos = '\0';
735 : 0 : return 0;
736 : : }
737 : :
738 : 0 : printf("No GSM triplets found for %s\n", imsi);
739 : 0 : ret = os_snprintf(rpos, rend - rpos, " FAILURE");
740 [ # # ][ # # ]: 0 : if (ret < 0 || ret >= rend - rpos)
741 : 0 : return -1;
742 : 0 : rpos += ret;
743 : :
744 : 0 : return 0;
745 : : }
746 : :
747 : :
748 : 7 : static void inc_sqn(u8 *sqn)
749 : : {
750 : : u64 val, seq, ind;
751 : :
752 : : /*
753 : : * SQN = SEQ | IND = SEQ1 | SEQ2 | IND
754 : : *
755 : : * The mechanism used here is not time-based, so SEQ2 is void and
756 : : * SQN = SEQ1 | IND. The length of IND is ind_len bits and the length
757 : : * of SEQ1 is 48 - ind_len bits.
758 : : */
759 : :
760 : : /* Increment both SEQ and IND by one */
761 : 7 : val = ((u64) WPA_GET_BE32(sqn) << 16) | ((u64) WPA_GET_BE16(sqn + 4));
762 : 7 : seq = (val >> ind_len) + 1;
763 : 7 : ind = (val + 1) & ((1 << ind_len) - 1);
764 : 7 : val = (seq << ind_len) | ind;
765 : 7 : WPA_PUT_BE32(sqn, val >> 16);
766 : 7 : WPA_PUT_BE16(sqn + 4, val & 0xffff);
767 : 7 : }
768 : :
769 : :
770 : 7 : static int aka_req_auth(char *imsi, char *resp, size_t resp_len)
771 : : {
772 : : /* AKA-RESP-AUTH <IMSI> <RAND> <AUTN> <IK> <CK> <RES> */
773 : : char *pos, *end;
774 : : u8 _rand[EAP_AKA_RAND_LEN];
775 : : u8 autn[EAP_AKA_AUTN_LEN];
776 : : u8 ik[EAP_AKA_IK_LEN];
777 : : u8 ck[EAP_AKA_CK_LEN];
778 : : u8 res[EAP_AKA_RES_MAX_LEN];
779 : : size_t res_len;
780 : : int ret;
781 : : struct milenage_parameters *m;
782 : 7 : int failed = 0;
783 : :
784 : 7 : m = get_milenage(imsi);
785 [ + - ]: 7 : if (m) {
786 [ - + ]: 7 : if (random_get_bytes(_rand, EAP_AKA_RAND_LEN) < 0)
787 : 0 : return -1;
788 : 7 : res_len = EAP_AKA_RES_MAX_LEN;
789 : 7 : inc_sqn(m->sqn);
790 : : #ifdef CONFIG_SQLITE
791 : : db_update_milenage_sqn(m);
792 : : #endif /* CONFIG_SQLITE */
793 : 7 : sqn_changes = 1;
794 [ + - ]: 7 : if (stdout_debug) {
795 : 7 : printf("AKA: Milenage with SQN=%02x%02x%02x%02x%02x%02x\n",
796 : 21 : m->sqn[0], m->sqn[1], m->sqn[2],
797 : 21 : m->sqn[3], m->sqn[4], m->sqn[5]);
798 : : }
799 : 7 : milenage_generate(m->opc, m->amf, m->ki, m->sqn, _rand,
800 : : autn, ik, ck, res, &res_len);
801 : : } else {
802 : 0 : printf("Unknown IMSI: %s\n", imsi);
803 : : #ifdef AKA_USE_FIXED_TEST_VALUES
804 : : printf("Using fixed test values for AKA\n");
805 : : memset(_rand, '0', EAP_AKA_RAND_LEN);
806 : : memset(autn, '1', EAP_AKA_AUTN_LEN);
807 : : memset(ik, '3', EAP_AKA_IK_LEN);
808 : : memset(ck, '4', EAP_AKA_CK_LEN);
809 : : memset(res, '2', EAP_AKA_RES_MAX_LEN);
810 : : res_len = EAP_AKA_RES_MAX_LEN;
811 : : #else /* AKA_USE_FIXED_TEST_VALUES */
812 : 0 : failed = 1;
813 : : #endif /* AKA_USE_FIXED_TEST_VALUES */
814 : : }
815 : :
816 : 7 : pos = resp;
817 : 7 : end = resp + resp_len;
818 : 7 : ret = snprintf(pos, end - pos, "AKA-RESP-AUTH %s ", imsi);
819 [ + - ][ - + ]: 7 : if (ret < 0 || ret >= end - pos)
820 : 0 : return -1;
821 : 7 : pos += ret;
822 [ - + ]: 7 : if (failed) {
823 : 0 : ret = snprintf(pos, end - pos, "FAILURE");
824 [ # # ][ # # ]: 0 : if (ret < 0 || ret >= end - pos)
825 : 0 : return -1;
826 : 0 : pos += ret;
827 : 0 : return 0;
828 : : }
829 : 7 : pos += wpa_snprintf_hex(pos, end - pos, _rand, EAP_AKA_RAND_LEN);
830 : 7 : *pos++ = ' ';
831 : 7 : pos += wpa_snprintf_hex(pos, end - pos, autn, EAP_AKA_AUTN_LEN);
832 : 7 : *pos++ = ' ';
833 : 7 : pos += wpa_snprintf_hex(pos, end - pos, ik, EAP_AKA_IK_LEN);
834 : 7 : *pos++ = ' ';
835 : 7 : pos += wpa_snprintf_hex(pos, end - pos, ck, EAP_AKA_CK_LEN);
836 : 7 : *pos++ = ' ';
837 : 7 : pos += wpa_snprintf_hex(pos, end - pos, res, res_len);
838 : :
839 : 7 : return 0;
840 : : }
841 : :
842 : :
843 : 1 : static int aka_auts(char *imsi, char *resp, size_t resp_len)
844 : : {
845 : : char *auts, *__rand;
846 : : u8 _auts[EAP_AKA_AUTS_LEN], _rand[EAP_AKA_RAND_LEN], sqn[6];
847 : : struct milenage_parameters *m;
848 : :
849 : 1 : resp[0] = '\0';
850 : :
851 : : /* AKA-AUTS <IMSI> <AUTS> <RAND> */
852 : :
853 : 1 : auts = strchr(imsi, ' ');
854 [ - + ]: 1 : if (auts == NULL)
855 : 0 : return -1;
856 : 1 : *auts++ = '\0';
857 : :
858 : 1 : __rand = strchr(auts, ' ');
859 [ - + ]: 1 : if (__rand == NULL)
860 : 0 : return -1;
861 : 1 : *__rand++ = '\0';
862 : :
863 [ + - ]: 1 : if (stdout_debug) {
864 : 1 : printf("AKA-AUTS: IMSI=%s AUTS=%s RAND=%s\n",
865 : : imsi, auts, __rand);
866 : : }
867 [ + - - + ]: 2 : if (hexstr2bin(auts, _auts, EAP_AKA_AUTS_LEN) ||
868 : 1 : hexstr2bin(__rand, _rand, EAP_AKA_RAND_LEN)) {
869 : 0 : printf("Could not parse AUTS/RAND\n");
870 : 0 : return -1;
871 : : }
872 : :
873 : 1 : m = get_milenage(imsi);
874 [ - + ]: 1 : if (m == NULL) {
875 : 0 : printf("Unknown IMSI: %s\n", imsi);
876 : 0 : return -1;
877 : : }
878 : :
879 [ - + ]: 1 : if (milenage_auts(m->opc, m->ki, _rand, _auts, sqn)) {
880 : 0 : printf("AKA-AUTS: Incorrect MAC-S\n");
881 : : } else {
882 : 1 : memcpy(m->sqn, sqn, 6);
883 [ + - ]: 1 : if (stdout_debug) {
884 : 1 : printf("AKA-AUTS: Re-synchronized: "
885 : : "SQN=%02x%02x%02x%02x%02x%02x\n",
886 : 6 : sqn[0], sqn[1], sqn[2], sqn[3], sqn[4], sqn[5]);
887 : : }
888 : : #ifdef CONFIG_SQLITE
889 : : db_update_milenage_sqn(m);
890 : : #endif /* CONFIG_SQLITE */
891 : 1 : sqn_changes = 1;
892 : : }
893 : :
894 : 1 : return 0;
895 : : }
896 : :
897 : :
898 : 17 : static int process_cmd(char *cmd, char *resp, size_t resp_len)
899 : : {
900 [ + + ]: 17 : if (os_strncmp(cmd, "SIM-REQ-AUTH ", 13) == 0)
901 : 9 : return sim_req_auth(cmd + 13, resp, resp_len);
902 : :
903 [ - + ]: 8 : if (os_strncmp(cmd, "GSM-AUTH-REQ ", 13) == 0)
904 : 0 : return gsm_auth_req(cmd + 13, resp, resp_len);
905 : :
906 [ + + ]: 8 : if (os_strncmp(cmd, "AKA-REQ-AUTH ", 13) == 0)
907 : 7 : return aka_req_auth(cmd + 13, resp, resp_len);
908 : :
909 [ + - ]: 1 : if (os_strncmp(cmd, "AKA-AUTS ", 9) == 0)
910 : 1 : return aka_auts(cmd + 9, resp, resp_len);
911 : :
912 : 0 : printf("Unknown request: %s\n", cmd);
913 : 17 : return -1;
914 : : }
915 : :
916 : :
917 : 18 : static int process(int s)
918 : : {
919 : : char buf[1000], resp[1000];
920 : : struct sockaddr_un from;
921 : : socklen_t fromlen;
922 : : ssize_t res;
923 : :
924 : 18 : fromlen = sizeof(from);
925 : 18 : res = recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *) &from,
926 : : &fromlen);
927 [ - + ]: 17 : if (res < 0) {
928 : 0 : perror("recvfrom");
929 : 0 : return -1;
930 : : }
931 : :
932 [ - + ]: 17 : if (res == 0)
933 : 0 : return 0;
934 : :
935 [ - + ]: 17 : if ((size_t) res >= sizeof(buf))
936 : 0 : res = sizeof(buf) - 1;
937 : 17 : buf[res] = '\0';
938 : :
939 : 17 : printf("Received: %s\n", buf);
940 : :
941 [ - + ]: 17 : if (process_cmd(buf, resp, sizeof(resp)) < 0) {
942 : 0 : printf("Failed to process request\n");
943 : 0 : return -1;
944 : : }
945 : :
946 [ + + ]: 17 : if (resp[0] == '\0') {
947 : 1 : printf("No response\n");
948 : 1 : return 0;
949 : : }
950 : :
951 : 16 : printf("Send: %s\n", resp);
952 : :
953 [ - + ]: 16 : if (sendto(s, resp, os_strlen(resp), 0, (struct sockaddr *) &from,
954 : : fromlen) < 0)
955 : 0 : perror("send");
956 : :
957 : 17 : return 0;
958 : : }
959 : :
960 : :
961 : 1 : static void cleanup(void)
962 : : {
963 : : struct gsm_triplet *g, *gprev;
964 : : struct milenage_parameters *m, *prev;
965 : :
966 [ - + ][ # # ]: 1 : if (update_milenage && milenage_file && sqn_changes)
[ # # ]
967 : 0 : update_milenage_file(milenage_file);
968 : :
969 : 1 : g = gsm_db;
970 [ - + ]: 1 : while (g) {
971 : 0 : gprev = g;
972 : 0 : g = g->next;
973 : 0 : os_free(gprev);
974 : : }
975 : :
976 : 1 : m = milenage_db;
977 [ + + ]: 3 : while (m) {
978 : 2 : prev = m;
979 : 2 : m = m->next;
980 : 2 : os_free(prev);
981 : : }
982 : :
983 [ + - ]: 1 : if (serv_sock >= 0)
984 : 1 : close(serv_sock);
985 [ + - ]: 1 : if (socket_path)
986 : 1 : unlink(socket_path);
987 : :
988 : : #ifdef CONFIG_SQLITE
989 : : if (sqlite_db) {
990 : : sqlite3_close(sqlite_db);
991 : : sqlite_db = NULL;
992 : : }
993 : : #endif /* CONFIG_SQLITE */
994 : 1 : }
995 : :
996 : :
997 : 1 : static void handle_term(int sig)
998 : : {
999 : 1 : printf("Signal %d - terminate\n", sig);
1000 : 1 : exit(0);
1001 : : }
1002 : :
1003 : :
1004 : 0 : static void usage(void)
1005 : : {
1006 : 0 : printf("HLR/AuC testing gateway for hostapd EAP-SIM/AKA "
1007 : : "database/authenticator\n"
1008 : : "Copyright (c) 2005-2007, 2012-2013, Jouni Malinen <j@w1.fi>\n"
1009 : : "\n"
1010 : : "usage:\n"
1011 : : "hlr_auc_gw [-hu] [-s<socket path>] [-g<triplet file>] "
1012 : : "[-m<milenage file>] \\\n"
1013 : : " [-D<DB file>] [-i<IND len in bits>] [command]\n"
1014 : : "\n"
1015 : : "options:\n"
1016 : : " -h = show this usage help\n"
1017 : : " -u = update SQN in Milenage file on exit\n"
1018 : : " -s<socket path> = path for UNIX domain socket\n"
1019 : : " (default: %s)\n"
1020 : : " -g<triplet file> = path for GSM authentication triplets\n"
1021 : : " -m<milenage file> = path for Milenage keys\n"
1022 : : " -D<DB file> = path to SQLite database\n"
1023 : : " -i<IND len in bits> = IND length for SQN (default: 5)\n"
1024 : : "\n"
1025 : : "If the optional command argument, like "
1026 : : "\"AKA-REQ-AUTH <IMSI>\" is used, a single\n"
1027 : : "command is processed with response sent to stdout. Otherwise, "
1028 : : "hlr_auc_gw opens\n"
1029 : : "a control interface and processes commands sent through it "
1030 : : "(e.g., by EAP server\n"
1031 : : "in hostapd).\n",
1032 : : default_socket_path);
1033 : 0 : }
1034 : :
1035 : :
1036 : 1 : int main(int argc, char *argv[])
1037 : : {
1038 : : int c;
1039 : 1 : char *gsm_triplet_file = NULL;
1040 : 1 : char *sqlite_db_file = NULL;
1041 : 1 : int ret = 0;
1042 : :
1043 [ - + ]: 1 : if (os_program_init())
1044 : 0 : return -1;
1045 : :
1046 : 1 : socket_path = default_socket_path;
1047 : :
1048 : : for (;;) {
1049 : 2 : c = getopt(argc, argv, "D:g:hi:m:s:u");
1050 [ + + ]: 2 : if (c < 0)
1051 : 1 : break;
1052 [ - - - - : 1 : switch (c) {
+ - - - ]
1053 : : case 'D':
1054 : : #ifdef CONFIG_SQLITE
1055 : : sqlite_db_file = optarg;
1056 : : break;
1057 : : #else /* CONFIG_SQLITE */
1058 : 0 : printf("No SQLite support included in the build\n");
1059 : 0 : return -1;
1060 : : #endif /* CONFIG_SQLITE */
1061 : : case 'g':
1062 : 0 : gsm_triplet_file = optarg;
1063 : 0 : break;
1064 : : case 'h':
1065 : 0 : usage();
1066 : 0 : return 0;
1067 : : case 'i':
1068 : 0 : ind_len = atoi(optarg);
1069 [ # # ][ # # ]: 0 : if (ind_len < 0 || ind_len > 32) {
1070 : 0 : printf("Invalid IND length\n");
1071 : 0 : return -1;
1072 : : }
1073 : 0 : break;
1074 : : case 'm':
1075 : 1 : milenage_file = optarg;
1076 : 1 : break;
1077 : : case 's':
1078 : 0 : socket_path = optarg;
1079 : 0 : break;
1080 : : case 'u':
1081 : 0 : update_milenage = 1;
1082 : 0 : break;
1083 : : default:
1084 : 0 : usage();
1085 : 0 : return -1;
1086 : : }
1087 : 1 : }
1088 : :
1089 [ + - ][ - + ]: 1 : if (!gsm_triplet_file && !milenage_file && !sqlite_db_file) {
[ # # ]
1090 : 0 : usage();
1091 : 0 : return -1;
1092 : : }
1093 : :
1094 : : #ifdef CONFIG_SQLITE
1095 : : if (sqlite_db_file && (sqlite_db = db_open(sqlite_db_file)) == NULL)
1096 : : return -1;
1097 : : #endif /* CONFIG_SQLITE */
1098 : :
1099 [ - + ][ # # ]: 1 : if (gsm_triplet_file && read_gsm_triplets(gsm_triplet_file) < 0)
1100 : 0 : return -1;
1101 : :
1102 [ + - ][ - + ]: 1 : if (milenage_file && read_milenage(milenage_file) < 0)
1103 : 0 : return -1;
1104 : :
1105 [ + - ]: 1 : if (optind == argc) {
1106 : 1 : serv_sock = open_socket(socket_path);
1107 [ - + ]: 1 : if (serv_sock < 0)
1108 : 0 : return -1;
1109 : :
1110 : 1 : printf("Listening for requests on %s\n", socket_path);
1111 : :
1112 : 1 : atexit(cleanup);
1113 : 1 : signal(SIGTERM, handle_term);
1114 : 1 : signal(SIGINT, handle_term);
1115 : :
1116 : : for (;;)
1117 : 18 : process(serv_sock);
1118 : : } else {
1119 : : char buf[1000];
1120 : 0 : socket_path = NULL;
1121 : 0 : stdout_debug = 0;
1122 [ # # ]: 0 : if (process_cmd(argv[optind], buf, sizeof(buf)) < 0) {
1123 : 0 : printf("FAIL\n");
1124 : 0 : ret = -1;
1125 : : } else {
1126 : 0 : printf("%s\n", buf);
1127 : : }
1128 : 0 : cleanup();
1129 : : }
1130 : :
1131 : : #ifdef CONFIG_SQLITE
1132 : : if (sqlite_db) {
1133 : : sqlite3_close(sqlite_db);
1134 : : sqlite_db = NULL;
1135 : : }
1136 : : #endif /* CONFIG_SQLITE */
1137 : :
1138 : 0 : os_program_deinit();
1139 : :
1140 : 0 : return ret;
1141 : : }
|