blob: d7d97b1ddd42e739aa420721229ee69d8b2ff629 [file] [log] [blame]
Harald Welte936f6722016-05-03 18:51:18 +02001/* (C) 2016 by Harald Welte <laforge@gnumonks.org>
2 *
3 * All Rights Reserved
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
Harald Welteaabae9e2016-04-28 12:48:14 +020020#include <signal.h>
Harald Weltee687be52016-05-03 18:49:27 +020021#include <errno.h>
Harald Welteaabae9e2016-04-28 12:48:14 +020022
Harald Weltee72cf552016-04-28 07:18:49 +020023#include <osmocom/core/msgb.h>
24#include <osmocom/core/logging.h>
25#include <osmocom/core/application.h>
26#include <osmocom/gsm/gsup.h>
27
28#include "db.h"
29#include "logging.h"
30#include "gsup_server.h"
Harald Weltee687be52016-05-03 18:49:27 +020031#include "gsup_router.h"
Harald Weltee72cf552016-04-28 07:18:49 +020032#include "rand.h"
33
34static struct db_context *g_dbc;
35
Harald Weltee687be52016-05-03 18:49:27 +020036/***********************************************************************
37 * Send Auth Info handling
38 ***********************************************************************/
39
Harald Weltee72cf552016-04-28 07:18:49 +020040/* process an incoming SAI request */
41static int rx_send_auth_info(struct osmo_gsup_conn *conn,
42 const struct osmo_gsup_message *gsup)
43{
44 struct osmo_gsup_message gsup_out;
45 struct msgb *msg_out;
46 int rc;
47
48 /* initialize return message structure */
49 memset(&gsup_out, 0, sizeof(gsup_out));
Harald Weltee72cf552016-04-28 07:18:49 +020050 memcpy(&gsup_out.imsi, &gsup->imsi, sizeof(gsup_out.imsi));
51
52 rc = db_get_auc(g_dbc, gsup->imsi, gsup_out.auth_vectors,
53 ARRAY_SIZE(gsup_out.auth_vectors),
Harald Welte9be0d2f2016-06-10 17:34:02 +020054 gsup->rand, gsup->auts);
Harald Weltecfc752b2016-05-05 16:38:14 +020055 if (rc < 0) {
Harald Weltee72cf552016-04-28 07:18:49 +020056 gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
Harald Weltecfc752b2016-05-05 16:38:14 +020057 gsup_out.cause = GMM_CAUSE_NET_FAIL;
58 } else if (rc == 0) {
59 gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR;
60 gsup_out.cause = GMM_CAUSE_IMSI_UNKNOWN;
Harald Welte15db8262016-05-05 16:50:39 +020061 } else {
62 gsup_out.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT;
63 gsup_out.num_auth_vectors = rc;
Harald Weltee72cf552016-04-28 07:18:49 +020064 }
65
Harald Weltee687be52016-05-03 18:49:27 +020066 msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
Harald Weltee72cf552016-04-28 07:18:49 +020067 osmo_gsup_encode(msg_out, &gsup_out);
68 return osmo_gsup_conn_send(conn, msg_out);
69}
70
Harald Weltee687be52016-05-03 18:49:27 +020071/***********************************************************************
72 * LU Operation State / Structure
73 ***********************************************************************/
74
75static LLIST_HEAD(g_lu_ops);
76
77#define CANCEL_TIMEOUT_SECS 30
78#define ISD_TIMEOUT_SECS 30
79
80enum lu_state {
81 LU_S_NULL,
82 LU_S_LU_RECEIVED,
83 LU_S_CANCEL_SENT,
84 LU_S_CANCEL_ACK_RECEIVED,
85 LU_S_ISD_SENT,
86 LU_S_ISD_ACK_RECEIVED,
87 LU_S_COMPLETE,
88};
89
90static const struct value_string lu_state_names[] = {
91 { LU_S_NULL, "NULL" },
92 { LU_S_LU_RECEIVED, "LU RECEIVED" },
93 { LU_S_CANCEL_SENT, "CANCEL SENT" },
94 { LU_S_CANCEL_ACK_RECEIVED, "CANCEL-ACK RECEIVED" },
95 { LU_S_ISD_SENT, "ISD SENT" },
96 { LU_S_ISD_ACK_RECEIVED, "ISD-ACK RECEIVED" },
97 { LU_S_COMPLETE, "COMPLETE" },
98 { 0, NULL }
99};
100
101struct lu_operation {
102 /*! entry in global list of location update operations */
103 struct llist_head list;
104 /*! to which gsup_server do we belong */
105 struct osmo_gsup_server *gsup_server;
106 /*! state of the location update */
107 enum lu_state state;
108 /*! CS (false) or PS (true) Location Update? */
109 bool is_ps;
110 /*! currently running timer */
111 struct osmo_timer_list timer;
112
113 /*! subscriber related to this operation */
114 struct hlr_subscriber subscr;
115 /*! peer VLR/SGSN starting the request */
116 uint8_t *peer;
117};
118
Neels Hofmeyr6eed3222016-12-11 01:21:49 +0100119void lu_op_tx_insert_subscr_data(struct lu_operation *luop);
120
Harald Weltee687be52016-05-03 18:49:27 +0200121void lu_op_statechg(struct lu_operation *luop, enum lu_state new_state)
122{
123 enum lu_state old_state = luop->state;
124
125 DEBUGP(DMAIN, "LU OP state change: %s -> ",
126 get_value_string(lu_state_names, old_state));
127 DEBUGPC(DMAIN, "%s\n",
128 get_value_string(lu_state_names, new_state));
129
130 luop->state = new_state;
131}
132
133struct lu_operation *lu_op_by_imsi(const char *imsi)
134{
135 struct lu_operation *luop;
136
137 llist_for_each_entry(luop, &g_lu_ops, list) {
138 if (!strcmp(imsi, luop->subscr.imsi))
139 return luop;
140 }
141 return NULL;
142}
143
144/* Send a msgb to a given address using routing */
145int osmo_gsup_addr_send(struct osmo_gsup_server *gs,
146 const uint8_t *addr, size_t addrlen,
147 struct msgb *msg)
148{
149 struct osmo_gsup_conn *conn;
150
151 conn = gsup_route_find(gs, addr, addrlen);
152 if (!conn) {
153 DEBUGP(DMAIN, "Cannot find route for addr %s\n", addr);
154 msgb_free(msg);
155 return -ENODEV;
156 }
157
158 return osmo_gsup_conn_send(conn, msg);
159}
160
Harald Welte99909272016-05-05 18:24:15 +0200161/* Transmit a given GSUP message for the given LU operation */
Harald Weltee687be52016-05-03 18:49:27 +0200162static void _luop_tx_gsup(struct lu_operation *luop,
163 const struct osmo_gsup_message *gsup)
164{
165 struct msgb *msg_out;
166
167 msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP LUOP");
168 osmo_gsup_encode(msg_out, gsup);
169
170 osmo_gsup_addr_send(luop->gsup_server, luop->peer,
171 talloc_total_size(luop->peer),
172 msg_out);
173}
174
175/*! Transmit UPD_LOC_ERROR and destroy lu_operation */
176void lu_op_tx_error(struct lu_operation *luop, enum gsm48_gmm_cause cause)
177{
178 struct osmo_gsup_message gsup;
179
180 DEBUGP(DMAIN, "%s: LU OP Tx Error (cause=%u)\n",
181 luop->subscr.imsi, cause);
182
183 memset(&gsup, 0, sizeof(gsup));
184 gsup.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR;
Neels Hofmeyrec1b9592016-12-11 01:22:23 +0100185 strncpy((char*)&gsup.imsi, luop->subscr.imsi, sizeof(gsup.imsi));
Harald Weltee687be52016-05-03 18:49:27 +0200186 gsup.imsi[sizeof(gsup.imsi)-1] = '\0';
187 gsup.cause = cause;
188
189 _luop_tx_gsup(luop, &gsup);
190
191 llist_del(&luop->list);
192 talloc_free(luop);
193}
194
Harald Welte99909272016-05-05 18:24:15 +0200195/* timer call-back in case LU operation doesn't receive an response */
Harald Weltee687be52016-05-03 18:49:27 +0200196static void lu_op_timer_cb(void *data)
197{
198 struct lu_operation *luop = data;
199
200 DEBUGP(DMAIN, "LU OP timer expired in state %s\n",
201 get_value_string(lu_state_names, luop->state));
202
203 switch (luop->state) {
204 case LU_S_CANCEL_SENT:
205 break;
206 case LU_S_ISD_SENT:
207 break;
208 default:
209 break;
210 }
211
212 lu_op_tx_error(luop, GMM_CAUSE_NET_FAIL);
213}
214
215/*! Transmit UPD_LOC_RESULT and destroy lu_operation */
216void lu_op_tx_ack(struct lu_operation *luop)
217{
218 struct osmo_gsup_message gsup;
219
220 memset(&gsup, 0, sizeof(gsup));
221 gsup.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT;
222 strncpy(gsup.imsi, luop->subscr.imsi, sizeof(gsup.imsi)-1);
223 //FIXME gsup.hlr_enc;
224
225 _luop_tx_gsup(luop, &gsup);
226
227 llist_del(&luop->list);
228 talloc_free(luop);
229}
230
231/*! Send Cancel Location to old VLR/SGSN */
232void lu_op_tx_cancel_old(struct lu_operation *luop)
233{
234 struct osmo_gsup_message gsup;
235
236 OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED);
237
238 memset(&gsup, 0, sizeof(gsup));
239 gsup.message_type = OSMO_GSUP_MSGT_LOCATION_CANCEL_REQUEST;
240 //gsup.cause = FIXME;
241 //gsup.cancel_type = FIXME;
242
243 _luop_tx_gsup(luop, &gsup);
244
245 lu_op_statechg(luop, LU_S_CANCEL_SENT);
246 osmo_timer_schedule(&luop->timer, CANCEL_TIMEOUT_SECS, 0);
247}
248
249/*! Receive Cancel Location Result from old VLR/SGSN */
250void lu_op_rx_cancel_old_ack(struct lu_operation *luop,
251 const struct osmo_gsup_message *gsup)
252{
253 OSMO_ASSERT(luop->state == LU_S_CANCEL_SENT);
254 /* FIXME: Check for spoofing */
255
256 osmo_timer_del(&luop->timer);
257
258 /* FIXME */
259
260 lu_op_tx_insert_subscr_data(luop);
261}
262
263/*! Transmit Insert Subscriber Data to new VLR/SGSN */
264void lu_op_tx_insert_subscr_data(struct lu_operation *luop)
265{
266 struct osmo_gsup_message gsup;
267
268 OSMO_ASSERT(luop->state == LU_S_LU_RECEIVED ||
269 luop->state == LU_S_CANCEL_ACK_RECEIVED);
270
271 memset(&gsup, 0, sizeof(gsup));
272 gsup.message_type = OSMO_GSUP_MSGT_INSERT_DATA_REQUEST;
273 strncpy(gsup.imsi, luop->subscr.imsi, sizeof(gsup.imsi)-1);
Harald Welte99909272016-05-05 18:24:15 +0200274 /* FIXME: deal with encoding the following data */
Harald Weltee687be52016-05-03 18:49:27 +0200275 gsup.msisdn_enc;
276 gsup.hlr_enc;
277
278 if (luop->is_ps) {
279 /* FIXME: PDP infos */
280 }
281
282 /* Send ISD to new VLR/SGSN */
283 _luop_tx_gsup(luop, &gsup);
284
285 lu_op_statechg(luop, LU_S_ISD_SENT);
286 osmo_timer_schedule(&luop->timer, ISD_TIMEOUT_SECS, 0);
287}
288
289/*! Receive Insert Subscriber Data Result from new VLR/SGSN */
290static void lu_op_rx_insert_subscr_data_ack(struct lu_operation *luop,
291 const struct osmo_gsup_message *gsup)
292{
293 OSMO_ASSERT(luop->state == LU_S_ISD_SENT);
294 /* FIXME: Check for spoofing */
295
296 osmo_timer_del(&luop->timer);
297
298 /* Subscriber_Present_HLR */
299 /* CS only: Check_SS_required? -> MAP-FW-CHECK_SS_IND.req */
300
301 /* Send final ACK towards inquiring VLR/SGSN */
302 lu_op_tx_ack(luop);
303}
304
305/*! Receive GSUP message for given \ref lu_operation */
306void lu_op_rx_gsup(struct lu_operation *luop,
307 const struct osmo_gsup_message *gsup)
308{
309 switch (gsup->message_type) {
310 case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
311 /* FIXME */
312 break;
313 case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
314 lu_op_rx_insert_subscr_data_ack(luop, gsup);
315 break;
316 case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
317 /* FIXME */
318 break;
319 case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
320 lu_op_rx_cancel_old_ack(luop, gsup);
321 break;
322 default:
323 LOGP(DMAIN, LOGL_ERROR, "Unhandled GSUP msg_type 0x%02x\n",
324 gsup->message_type);
325 break;
326 }
327}
328
329static struct lu_operation *lu_op_alloc(struct osmo_gsup_server *srv)
330{
331 struct lu_operation *luop;
332
333 luop = talloc_zero(srv, struct lu_operation);
334 OSMO_ASSERT(luop);
335 luop->gsup_server = srv;
336 luop->timer.cb = lu_op_timer_cb;
337 luop->timer.data = luop;
338
339 return luop;
340}
341
342/*! Receive Update Location Request, creates new \ref lu_operation */
343static int rx_upd_loc_req(struct osmo_gsup_conn *conn,
344 const struct osmo_gsup_message *gsup)
345{
346 int rc;
Harald Weltee687be52016-05-03 18:49:27 +0200347 struct lu_operation *luop;
348 struct hlr_subscriber *subscr;
349 uint8_t *peer_addr;
350
351 rc = osmo_gsup_conn_ccm_get(conn, &peer_addr, IPAC_IDTAG_SERNR);
352 if (rc < 0) {
353 LOGP(DMAIN, LOGL_ERROR, "LU REQ from conn without addr?\n");
354 return rc;
355 }
356
357 luop = lu_op_alloc(conn->server);
358 luop->peer = talloc_memdup(luop, peer_addr, rc);
359 lu_op_statechg(luop, LU_S_LU_RECEIVED);
360 subscr = &luop->subscr;
361 if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
362 luop->is_ps = true;
363 llist_add(&luop->list, &g_lu_ops);
364
365 /* Roughly follwing "Process Update_Location_HLR" of TS 09.02 */
366
367 /* check if subscriber is known at all */
368 rc = db_subscr_get(g_dbc, gsup->imsi, subscr);
369 if (rc < 0) {
370 /* Send Error back: Subscriber Unknown in HLR */
371 strcpy(luop->subscr.imsi, gsup->imsi);
372 lu_op_tx_error(luop, GMM_CAUSE_IMSI_UNKNOWN);
373 return 0;
374 }
375
Harald Welte99909272016-05-05 18:24:15 +0200376 /* Check if subscriber is generally permitted on CS or PS
377 * service (as requested) */
Harald Welte53b86782016-05-05 21:04:11 +0200378 if (!luop->is_ps && !subscr->nam_cs) {
Harald Weltee687be52016-05-03 18:49:27 +0200379 lu_op_tx_error(luop, GMM_CAUSE_PLMN_NOTALLOWED);
380 return 0;
Harald Welte53b86782016-05-05 21:04:11 +0200381 } else if (luop->is_ps && !subscr->nam_ps) {
Harald Weltee687be52016-05-03 18:49:27 +0200382 lu_op_tx_error(luop, GMM_CAUSE_GPRS_NOTALLOWED);
383 return 0;
384 }
385
386 /* TODO: Set subscriber tracing = deactive in VLR/SGSN */
387
388#if 0
389 /* Cancel in old VLR/SGSN, if new VLR/SGSN differs from old */
390 if (luop->is_ps == false &&
391 strcmp(subscr->vlr_number, vlr_number)) {
Harald Weltee687be52016-05-03 18:49:27 +0200392 lu_op_tx_cancel_old(luop);
393 } else if (luop->is_ps == true &&
394 strcmp(subscr->sgsn_number, sgsn_number)) {
Harald Weltee687be52016-05-03 18:49:27 +0200395 lu_op_tx_cancel_old(luop);
396 } else
397#endif
398 {
399 /* TODO: Subscriber allowed to roam in PLMN? */
400 /* TODO: Update RoutingInfo */
401 /* TODO: Reset Flag MS Purged (cs/ps) */
402 /* TODO: Control_Tracing_HLR / Control_Tracing_HLR_with_SGSN */
403 lu_op_tx_insert_subscr_data(luop);
404 }
405 return 0;
406}
407
Harald Welteb18f0e02016-05-05 21:03:03 +0200408static int rx_purge_ms_req(struct osmo_gsup_conn *conn,
409 const struct osmo_gsup_message *gsup)
410{
411 struct osmo_gsup_message gsup_reply = {0};
412 struct msgb *msg_out;
413 bool is_ps = false;
414 int rc;
415
416 LOGP(DAUC, LOGL_INFO, "%s: Purge MS (%s)\n", gsup->imsi,
417 is_ps ? "PS" : "CS");
418
419 memcpy(gsup_reply.imsi, gsup->imsi, sizeof(gsup_reply.imsi));
420
421 if (gsup->cn_domain == OSMO_GSUP_CN_DOMAIN_PS)
422 is_ps = true;
423
424 /* FIXME: check if the VLR that sends the purge is the same that
425 * we have on record. Only update if yes */
426
427 /* Perform the actual update of the DB */
428 rc = db_subscr_purge(g_dbc, gsup->imsi, is_ps);
429
430 if (rc == 1)
431 gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_RESULT;
432 else if (rc == 0) {
433 gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
434 gsup_reply.cause = GMM_CAUSE_IMSI_UNKNOWN;
435 } else {
436 gsup_reply.message_type = OSMO_GSUP_MSGT_PURGE_MS_ERROR;
437 gsup_reply.cause = GMM_CAUSE_NET_FAIL;
438 }
439
440 msg_out = msgb_alloc_headroom(1024+16, 16, "GSUP AUC response");
441 osmo_gsup_encode(msg_out, &gsup_reply);
442 return osmo_gsup_conn_send(conn, msg_out);
443}
444
Harald Weltee72cf552016-04-28 07:18:49 +0200445static int read_cb(struct osmo_gsup_conn *conn, struct msgb *msg)
446{
447 static struct osmo_gsup_message gsup;
448 int rc;
449
Harald Weltee687be52016-05-03 18:49:27 +0200450 rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup);
Harald Weltee72cf552016-04-28 07:18:49 +0200451 if (rc < 0) {
452 LOGP(DMAIN, LOGL_ERROR, "error in GSUP decode: %d\n", rc);
453 return rc;
454 }
455
456 switch (gsup.message_type) {
457 /* requests sent to us */
458 case OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST:
459 rx_send_auth_info(conn, &gsup);
460 break;
461 case OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST:
Harald Weltee687be52016-05-03 18:49:27 +0200462 rx_upd_loc_req(conn, &gsup);
Harald Weltee72cf552016-04-28 07:18:49 +0200463 break;
Harald Welteb18f0e02016-05-05 21:03:03 +0200464 case OSMO_GSUP_MSGT_PURGE_MS_REQUEST:
465 rx_purge_ms_req(conn, &gsup);
466 break;
Harald Weltee72cf552016-04-28 07:18:49 +0200467 /* responses to requests sent by us */
468 case OSMO_GSUP_MSGT_INSERT_DATA_ERROR:
Harald Weltee72cf552016-04-28 07:18:49 +0200469 case OSMO_GSUP_MSGT_INSERT_DATA_RESULT:
Harald Weltee687be52016-05-03 18:49:27 +0200470 case OSMO_GSUP_MSGT_LOCATION_CANCEL_ERROR:
471 case OSMO_GSUP_MSGT_LOCATION_CANCEL_RESULT:
472 {
473 struct lu_operation *luop = lu_op_by_imsi(gsup.imsi);
474 if (!luop) {
475 LOGP(DMAIN, LOGL_ERROR, "GSUP message %u for "
476 "unknown IMSI %s\n", gsup.message_type,
477 gsup.imsi);
478 break;
479 }
480 lu_op_rx_gsup(luop, &gsup);
481 }
Harald Weltee72cf552016-04-28 07:18:49 +0200482 break;
483 default:
484 LOGP(DMAIN, LOGL_DEBUG, "Unhandled GSUP message type %u\n",
485 gsup.message_type);
486 break;
487 }
Harald Welte5341b5d2016-04-28 12:48:39 +0200488 msgb_free(msg);
Harald Weltee72cf552016-04-28 07:18:49 +0200489 return 0;
490}
491
Harald Welteaabae9e2016-04-28 12:48:14 +0200492static struct osmo_gsup_server *gs;
493
494static void signal_hdlr(int signal)
495{
496 switch (signal) {
497 case SIGINT:
498 LOGP(DMAIN, LOGL_NOTICE, "Terminating due to SIGINT\n");
499 osmo_gsup_server_destroy(gs);
500 db_close(g_dbc);
501 log_fini();
502 exit(0);
503 break;
504 case SIGUSR1:
505 LOGP(DMAIN, LOGL_DEBUG, "Talloc Report due to SIGUSR1\n");
506 talloc_report_full(NULL, stderr);
507 break;
508 }
509}
Harald Weltee72cf552016-04-28 07:18:49 +0200510
511int main(int argc, char **argv)
512{
Harald Weltee72cf552016-04-28 07:18:49 +0200513 int rc;
514
Harald Welteaabae9e2016-04-28 12:48:14 +0200515 talloc_enable_leak_report_full();
516
Harald Weltee72cf552016-04-28 07:18:49 +0200517 rc = osmo_init_logging(&hlr_log_info);
518 if (rc < 0) {
519 fprintf(stderr, "Error initializing logging\n");
520 exit(1);
521 }
522 LOGP(DMAIN, LOGL_NOTICE, "hlr starting\n");
523
524 rc = rand_init();
525 if (rc < 0) {
526 LOGP(DMAIN, LOGL_FATAL, "Error initializing random source\n");
527 exit(1);
528 }
529
530 g_dbc = db_open(NULL, "hlr.db");
531 if (!g_dbc) {
532 LOGP(DMAIN, LOGL_FATAL, "Error opening database\n");
533 exit(1);
534 }
535
536 gs = osmo_gsup_server_create(NULL, NULL, 2222, read_cb);
537 if (!gs) {
538 LOGP(DMAIN, LOGL_FATAL, "Error starting GSUP server\n");
539 exit(1);
540 }
541
Harald Welteaabae9e2016-04-28 12:48:14 +0200542 osmo_init_ignore_signals();
543 signal(SIGINT, &signal_hdlr);
544 signal(SIGUSR1, &signal_hdlr);
545
546 //osmo_daemonize();
547
Harald Weltee72cf552016-04-28 07:18:49 +0200548 while (1) {
549 osmo_select_main(0);
550 }
551
552 db_close(g_dbc);
553
554 log_fini();
555
556 exit(0);
557}