DEADSOFTWARE

ebeb2c5c9a5d7640b8ff04e3fdb9bd4a6a85c6d9
[d2df-sdl.git] / src / mastersrv / master.c
1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <ctype.h>
4 #include <string.h>
5 #include <time.h>
7 #include <enet/enet.h>
8 #include <enet/types.h>
10 #define MS_VERSION "0.2"
11 #define MS_MAXSRVS 128
12 #define MS_MAXHOST 5
13 #define MS_MAXBANS 256
14 #define MS_TIMEOUT 100
15 #define MS_BANTIME (3 * 86400)
16 #define MS_MAXHEUR 100
18 #define NET_CHANS 2
19 #define NET_CH_MAIN 0
20 #define NET_CH_UPD 1
21 #define NET_MAXCLIENTS 64
23 #define NET_BUFSIZE 65536
25 #define NET_MSG_ADD 200
26 #define NET_MSG_RM 201
27 #define NET_MSG_LIST 202
29 #define LC_MS_INIT "D2DF master server starting on port %d...\n"
30 #define LC_MS_ADD "\nAdded server in slot #%d:\n%s:%d\n%s\n%s (%d)\n%d/%d plrs\nproto: %d pw?: %d\n"
31 #define LC_MS_UPD "\nUpdated server #%d (%s:%d):\n%s\n%s (%d)\n%d/%d plrs\nproto: %d pw?: %d\n"
32 #define LC_MS_RM "\nRemoved server #%d (%s:%d) by request.\n"
33 #define LC_MS_TIME "\nServer #%d (%s:%d) timed out.\n"
34 #define LC_MS_LIST "\nSent server list to %x:%u (ver. %s).\n"
35 #define LC_MS_DIE "\nD2DF master server shutting down...\n"
36 #define LC_MS_CONN "\nIncoming connection from %x:%u...\n"
37 #define LC_MS_MOTD "\nMOTD: %s\n"
38 #define LC_MS_URGENT "\nURGENT: %s\n"
39 #define LC_MS_BANNED "\nBanned %s until %s, reason: %s (#%d)\n"
40 #define LC_MS_NOBANS "\nCould not load ban list from file\n"
41 #define LC_MS_BADADR "\nBad address in file: %s\n"
42 #define LC_MS_BANHEUR "tripped heuristic check"
43 #define LC_MS_BANTOOMUCH "created too many servers"
44 #define LC_MS_BANSPAM "suspicious multiple server activity"
45 #define LC_MS_BANLIST "address in ban list"
46 #define LC_MS_BANTRASH "garbage server data"
47 #define LC_MS_BANINVAL "invalid message ID"
48 #define LC_MS_OOM "\nOut of memory\n"
50 #define MS_URGENT_FILE "urgent.txt"
51 #define MS_MOTD_FILE "motd.txt"
52 #define MS_BAN_FILE "master_bans.txt"
54 struct ms_ban_s;
56 typedef struct ms_ban_record_s {
57 ENetAddress mask;
58 char ip[18];
59 int ban_count;
60 time_t cur_ban;
61 struct ms_ban_record_s *next;
62 struct ms_ban_record_s *prev;
63 } ms_ban_record;
65 struct ms_server_s {
66 enet_uint8 used;
67 char s_ip[18];
68 char s_name[257];
69 char s_map[257];
70 enet_uint8 s_pw;
71 enet_uint8 s_plrs;
72 enet_uint8 s_maxplrs;
73 enet_uint8 s_mode;
74 enet_uint8 s_protocol;
75 enet_uint16 s_port;
76 time_t deathtime;
77 time_t lasttime;
78 };
80 typedef struct ms_server_s ms_server;
82 typedef struct enet_buf_s {
83 enet_uint8 *data;
84 size_t size;
85 size_t pos;
86 int overflow;
87 } enet_buf;
89 const char ms_game_ver[] = "0.63";
90 char ms_motd[255] = "";
91 char ms_urgent[255] = "";
93 int ms_port = 25660;
94 int ms_timeout = 100;
95 int ms_checkmultiple = 0;
97 enet_uint8 b_send_data[NET_BUFSIZE];
98 enet_buf b_send = { .data = b_send_data, .size = sizeof(b_send_data) };
99 enet_buf b_recv;
101 ENetHost *ms_host = NULL;
102 ENetPeer *ms_peers[NET_MAXCLIENTS];
104 ms_server ms_srv[MS_MAXSRVS];
105 enet_uint8 ms_count = 0;
107 ms_ban_record *ms_bans;
109 // fake servers to show on old versions of the game
110 static const ms_server ms_fake_srv[] = {
112 .used = 1,
113 .s_ip = "0.0.0.0",
114 .s_name = "! \xc2\xc0\xd8\xc0 \xca\xce\xcf\xc8\xdf \xc8\xc3\xd0\xdb "
115 "\xd3\xd1\xd2\xc0\xd0\xc5\xcb\xc0! "
116 "\xd1\xca\xc0\xd7\xc0\xc9\xd2\xc5 \xcd\xce\xc2\xd3\xde C "
117 "doom2d.org !",
118 .s_map = "! Your game is outdated. "
119 "Get latest version at doom2d.org !",
120 .s_protocol = 255,
121 },
123 .used = 1,
124 .s_ip = "0.0.0.0",
125 .s_name = "! \xcf\xd0\xce\xc1\xd0\xce\xd1\xdcTE \xcf\xce\xd0\xd2\xdb "
126 "25666 \xc8 57133 HA CEPBEPE \xcf\xc5\xd0\xc5\xc4 \xc8\xc3\xd0\xce\xc9 !",
127 .s_map = "! Forward ports 25666 and 57133 before hosting !",
128 .s_protocol = 255,
129 },
130 };
132 #define MS_FAKESRVS (sizeof(ms_fake_srv) / sizeof(ms_fake_srv[0]))
134 void i_usage (void) {
135 printf("Usage: d2df_master -p port_number [-t timeout_seconds]\n");
136 fflush(stdout);
140 void i_version (void) {
141 printf("Doom 2D Forever master server v%s\n", MS_VERSION);
142 fflush(stdout);
146 void d_error (const char *msg, int fatal) {
147 if (fatal) {
148 fprintf(stderr, "FATAL ERROR: %s\n", msg);
149 exit(EXIT_FAILURE);
150 } else {
151 fprintf(stderr, "ERROR: %s\n", msg);
156 void d_getargs (int argc, char *argv[]) {
157 if (argc < 2) {
158 i_usage();
159 exit(0);
160 return;
163 for (int i = 1; i < argc; ++i) {
164 if (!strcmp(argv[i], "-v")) {
165 i_version();
166 exit(0);
167 return;
168 } else if (!strcmp(argv[i], "-p")) {
169 if (i + 1 >= argc) {
170 d_error("Specify a port value!", 1);
171 return;
172 } else {
173 ms_port = atoi(argv[++i]);
175 } else if (!strcmp(argv[i], "-t") & (i + 1 < argc)) {
176 ms_timeout = atoi(argv[++i]);
177 } else if (!strcmp(argv[i], "--check-multihost")) {
178 ms_checkmultiple = 1;
185 int d_readtextfile (const char *fname, char *buf, size_t max) {
186 FILE *f = fopen(fname, "r");
187 char *const end = buf + max - 1;
188 char *p = buf;
189 if (f) {
190 char ln[max];
191 char *const lend = ln + max - 1;
192 while (p < end && fgets(ln, max, f)) {
193 for (char *n = ln; n < lend && *n && *n != '\r' && *n != '\n'; ++n) {
194 *(p++) = *n;
195 if (p == end) break;
198 *p = '\0';
199 fclose(f);
200 return 0;
202 return 1;
206 int d_strisprint (const char *str) {
207 if (!str || !*str) return 0;
208 for (const char *p = str; p && *p; ++p)
209 if (isprint(*p) || *p > 0x7f) return 1;
210 return 0;
214 const char *d_strtime(const time_t t) {
215 static char buf[128];
216 struct tm *ptm = localtime(&t);
217 strftime(buf, sizeof(buf), "%c", ptm);
218 return buf;
222 static inline int b_enough_left(enet_buf *buf, size_t size) {
223 if (buf->pos + size > buf->size) {
224 buf->overflow = 1;
225 return 0;
227 return 1;
231 enet_uint8 b_read_uint8 (enet_buf *buf) {
232 if (b_enough_left(buf, 1))
233 return buf->data[buf->pos++];
234 return 0;
238 enet_uint16 b_read_uint16 (enet_buf *buf) {
239 enet_uint16 ret = 0;
241 if (b_enough_left(buf, sizeof(ret))) {
242 ret = *(enet_uint16*)(buf->data + buf->pos);
243 buf->pos += sizeof(ret);
246 return ret;
250 char* b_read_dstring (enet_buf *buf) {
251 char *ret = NULL;
253 if (b_enough_left(buf, 1)) {
254 size_t len = b_read_uint8(buf);
255 if (b_enough_left(buf, len)) {
256 ret = malloc(len + 1);
257 memmove(ret, (char*)(buf->data + buf->pos), len);
258 buf->pos += len;
259 ret[len] = '\0';
263 return ret;
267 void b_write_uint8 (enet_buf *buf, enet_uint8 val) {
268 buf->data[buf->pos++] = val;
272 void b_write_uint16 (enet_buf *buf, enet_uint16 val) {
273 *(enet_uint16*)(buf->data + buf->pos) = val;
274 buf->pos += sizeof(val);
278 void b_write_dstring (enet_buf *buf, const char* val) {
279 enet_uint8 len = strlen(val);
280 b_write_uint8(buf, len);
282 memmove((char*)(buf->data + buf->pos), val, len);
283 buf->pos += len;
287 void b_write_server (enet_buf *b_send, ms_server s) {
288 b_write_dstring(b_send, s.s_ip);
289 b_write_uint16 (b_send, s.s_port);
290 b_write_dstring(b_send, s.s_name);
291 b_write_dstring(b_send, s.s_map);
292 b_write_uint8 (b_send, s.s_mode);
293 b_write_uint8 (b_send, s.s_plrs);
294 b_write_uint8 (b_send, s.s_maxplrs);
295 b_write_uint8 (b_send, s.s_protocol);
296 b_write_uint8 (b_send, s.s_pw);
300 time_t ban_get_time(const int cnt) {
301 static const time_t times[] = {
302 1 * 5 * 60,
303 1 * 30 * 60,
304 1 * 60 * 60,
305 24 * 60 * 60,
306 72 * 60 * 60,
307 8760 * 60 * 60,
308 };
310 static const size_t numtimes = sizeof(times) / sizeof(*times);
312 if (cnt >= numtimes || cnt < 0)
313 return times[numtimes - 1];
315 return times[cnt];
319 ms_ban_record *ban_check (const ENetAddress *addr) {
320 const time_t now = time(NULL);
322 for (ms_ban_record *b = ms_bans; b; b = b->next) {
323 if (b->mask.host == addr->host) {
324 if (b->cur_ban > now)
325 return b;
329 return NULL;
333 ms_ban_record *ban_record_check (const ENetAddress *addr) {
334 for (ms_ban_record *b = ms_bans; b; b = b->next) {
335 if (b->mask.host == addr->host)
336 return b;
338 return NULL;
342 ms_ban_record *ban_record_add_addr (const ENetAddress *addr, const int cnt, const time_t cur) {
343 ms_ban_record *rec = ban_record_check(addr);
344 if (rec) return rec;
346 rec = calloc(1, sizeof(*rec));
347 if (!rec) return NULL;
349 enet_address_get_host_ip(addr, rec->ip, 17);
350 rec->mask = *addr;
351 rec->ban_count = cnt;
352 rec->cur_ban = cur;
354 if (ms_bans) ms_bans->prev = rec;
355 rec->next = ms_bans;
356 ms_bans = rec;
358 return rec;
362 ms_ban_record *ban_record_add_ip (const char *ip, const int cnt, const time_t cur) {
363 ENetAddress addr;
364 if (enet_address_set_host_ip(&addr, ip) != 0) {
365 fprintf(stderr, LC_MS_BADADR, ip);
366 return NULL;
368 return ban_record_add_addr(&addr, cnt, cur);
372 void ban_load_list (const char *fname) {
373 FILE *f = fopen(fname, "r");
374 if (!f) {
375 d_error(LC_MS_NOBANS, 0);
376 return;
379 char ln[256] = { 0 };
381 while (fgets(ln, sizeof(ln), f)) {
382 for (int i = sizeof(ln) - 1; i >= 0; --i)
383 if (ln[i] == '\n' || ln[i] == '\r')
384 ln[i] = 0;
386 char ip[17] = { 0 };
387 time_t exp = 0;
388 int count = 0;
390 sscanf(ln, "%16s %ld %d", ip, &exp, &count);
392 if (ban_record_add_ip(ip, count, exp))
393 printf(LC_MS_BANNED, ip, d_strtime(exp), LC_MS_BANLIST, count);
396 fclose(f);
400 void ban_save_list (const char *fname) {
401 FILE *f = fopen(fname, "w");
402 if (!f) {
403 d_error(LC_MS_NOBANS, 0);
404 return;
407 for (ms_ban_record *rec = ms_bans; rec; rec = rec->next)
408 if (rec->ban_count)
409 fprintf(f, "%s %ld %d\n", rec->ip, rec->cur_ban, rec->ban_count);
411 fclose(f);
415 int ban_heur (const ms_server *srv, const time_t now) {
416 int score = 0;
418 // can't have more than 24 maxplayers; can't have more than max
419 if (srv->s_plrs > srv->s_maxplrs || srv->s_maxplrs > 24 || srv->s_maxplrs == 0)
420 score += MS_MAXHEUR;
422 // name and map have to be non-garbage
423 if (!d_strisprint(srv->s_map) || !d_strisprint(srv->s_name))
424 score += MS_MAXHEUR;
426 // these protocols don't exist
427 if (srv->s_protocol < 100 || srv->s_protocol > 250)
428 score += MS_MAXHEUR;
430 // the game doesn't allow server names longer than 64 chars
431 if (strlen(srv->s_name) > 64)
432 score += MS_MAXHEUR;
434 // game mode has to actually exist
435 if (srv->s_mode > 5)
436 score += MS_MAXHEUR;
438 // password field can be either 0 or 1
439 if (srv->s_pw > 1)
440 score += MS_MAXHEUR;
442 // port has to be set, although the game allows you to set it to 0
443 // if (!srv->s_port)
444 // score += MS_MAXHEUR;
446 // servers usually don't update more often than once every 30 seconds
447 if (now - srv->lasttime < 5)
448 score += MS_MAXHEUR / 2;
450 return score;
453 void erase_banned_host(const char *ip) {
454 for (int i = 0; i < MS_MAXSRVS; ++i) {
455 if (!strcmp(ms_srv[i].s_ip, ip)) {
456 if (ms_srv[i].used) {
457 ms_srv[i].used = 0;
458 ms_count--;
464 time_t get_sum_lasttime(char* ip) {
465 time_t sumLastTime = 0;
466 const time_t now = time(NULL);
467 for (int i = 0; i < MS_MAXSRVS; ++i) {
468 if (ms_srv[i].used && (strncmp(ip, ms_srv[i].s_ip, 16) == 0)) {
469 sumLastTime = sumLastTime + (now - ms_srv[i].lasttime);
472 return sumLastTime;
475 void ban_add (const ENetAddress *addr, const char *reason) {
476 const time_t now = time(NULL);
478 ms_ban_record *rec = ban_record_add_addr(addr, 0, 0);
479 if (!rec) d_error(LC_MS_OOM, 1);
481 rec->cur_ban = now + ban_get_time(rec->ban_count);
482 rec->ban_count++;
484 printf(LC_MS_BANNED, rec->ip, d_strtime(rec->cur_ban), reason, rec->ban_count);
486 ban_save_list(MS_BAN_FILE);
487 erase_banned_host(rec->ip);
491 void d_deinit(void) {
492 ban_save_list(MS_BAN_FILE);
495 int count_servers(char* ip) {
496 int sameHostServers = 0;
497 for (int i = 0; i < MS_MAXSRVS; ++i) {
498 if ((strncmp(ip, ms_srv[i].s_ip, 16) == 0)) {
499 ++sameHostServers;
502 return sameHostServers;
506 #define CHECK_RECV_OVERFLOW(addr) \
507 if (b_recv.overflow) { \
508 ban_add(addr, LC_MS_BANTRASH); \
509 break; \
512 int main (int argc, char *argv[]) {
513 d_getargs(argc, argv);
515 if (enet_initialize()) {
516 d_error("Could not init ENet!", 1);
517 return EXIT_FAILURE;
520 printf(LC_MS_INIT, ms_port);
522 d_readtextfile(MS_MOTD_FILE, ms_motd, sizeof(ms_motd));
523 d_readtextfile(MS_URGENT_FILE, ms_urgent, sizeof(ms_urgent));
524 ban_load_list(MS_BAN_FILE);
526 if (ms_motd[0]) printf(LC_MS_MOTD, ms_motd);
527 if (ms_urgent[0]) printf(LC_MS_URGENT, ms_urgent);
529 for (int i = 0; i < NET_MAXCLIENTS; ++i) ms_peers[i] = NULL;
531 for (int i = 0; i < MS_MAXSRVS; ++i) {
532 ms_srv[i].used = 0;
533 ms_srv[i].s_ip[0] = '\0';
534 ms_srv[i].s_name[0] = '\0';
535 ms_srv[i].s_map[0] = '\0';
536 ms_srv[i].deathtime = 0;
539 ENetAddress addr;
540 addr.host = ENET_HOST_ANY;
541 addr.port = ms_port;
543 ms_host = enet_host_create(&addr, NET_MAXCLIENTS, NET_CHANS, 0, 0);
544 if (!ms_host) {
545 d_error("Could not create host on specified port!", 1);
546 return EXIT_FAILURE;
549 atexit(d_deinit);
551 ENetEvent event;
552 int shutdown = 0;
553 enet_uint8 msg = 255;
555 char ip[17];
556 enet_uint16 port = 0;
558 char *name = NULL;
559 char *map = NULL;
560 char *clientver = NULL;
561 enet_uint8 gm = 0;
562 enet_uint16 pl = 0;
563 enet_uint16 mpl = 0;
565 enet_uint8 proto = 0;
566 enet_uint8 pw = 0;
567 while (!shutdown) {
568 while (enet_host_service(ms_host, &event, 5000) > 0) {
569 if (event.peer && ban_check(&(event.peer->address)))
570 continue;
572 const time_t now = time(NULL);
574 switch (event.type) {
575 case ENET_EVENT_TYPE_CONNECT:
576 printf(LC_MS_CONN, event.peer->address.host, event.peer->address.port);
577 break;
579 case ENET_EVENT_TYPE_RECEIVE:
580 if (!event.peer) continue;
582 b_recv.pos = 0;
583 b_recv.overflow = 0;
584 b_recv.data = event.packet->data;
585 b_recv.size = event.packet->dataLength;
586 msg = b_read_uint8(&b_recv);
588 switch (msg) {
589 case NET_MSG_ADD:
590 enet_address_get_host_ip(&(event.peer->address), ip, 17);
591 port = b_read_uint16(&b_recv);
593 name = b_read_dstring(&b_recv);
594 map = b_read_dstring(&b_recv);
595 gm = b_read_uint8(&b_recv);
597 pl = b_read_uint8(&b_recv);
598 mpl = b_read_uint8(&b_recv);
600 proto = b_read_uint8(&b_recv);
601 pw = b_read_uint8(&b_recv);
603 CHECK_RECV_OVERFLOW(&(event.peer->address));
605 for (int i = 0; i < MS_MAXSRVS; ++i) {
606 if (ms_srv[i].used) {
607 if ((strncmp(ip, ms_srv[i].s_ip, 16) == 0) && (ms_srv[i].s_port == port)) {
608 strncpy(ms_srv[i].s_map, map, sizeof(ms_srv[i].s_map) - 1);
609 strncpy(ms_srv[i].s_name, name, sizeof(ms_srv[i].s_name) - 1);
610 ms_srv[i].s_plrs = pl;
611 ms_srv[i].s_maxplrs = mpl;
612 ms_srv[i].s_pw = pw;
613 ms_srv[i].s_mode = gm;
615 if (ban_heur(ms_srv + i, now) >= MS_MAXHEUR) {
616 ban_add(&(event.peer->address), LC_MS_BANHEUR);
617 break;
620 ms_srv[i].deathtime = now + ms_timeout;
621 ms_srv[i].lasttime = now;
623 printf(LC_MS_UPD, i, ip, port, name, map, gm, pl, mpl, proto, pw);
624 break;
626 } else {
627 int countServer = count_servers(ip);
628 if (countServer > MS_MAXHOST) {
629 ban_add(&(event.peer->address), LC_MS_BANTOOMUCH);
630 break;
632 else if (ms_checkmultiple && countServer > 1) {
633 if (get_sum_lasttime(ip) < (countServer*3)) {
634 ban_add(&(event.peer->address), LC_MS_BANSPAM);
635 break;
638 strncpy(ms_srv[i].s_ip, ip, sizeof(ms_srv[i].s_ip) - 1);
639 strncpy(ms_srv[i].s_map, map, sizeof(ms_srv[i].s_map) - 1);
640 strncpy(ms_srv[i].s_name, name, sizeof(ms_srv[i].s_name) - 1);
641 ms_srv[i].s_port = port;
642 ms_srv[i].s_plrs = pl;
643 ms_srv[i].s_maxplrs = mpl;
644 ms_srv[i].s_pw = pw;
645 ms_srv[i].s_mode = gm;
646 ms_srv[i].s_protocol = proto;
647 ms_srv[i].deathtime = now + ms_timeout;
648 ms_srv[i].lasttime = now;
650 if (ban_heur(ms_srv + i, now) >= MS_MAXHEUR) {
651 ban_add(&(event.peer->address), LC_MS_BANHEUR);
652 break;
655 ms_srv[i].used = 1;
657 printf(LC_MS_ADD, i, ip, port, name, map, gm, pl, mpl, proto, pw);
659 ++ms_count;
660 break;
663 free(name);
664 free(map);
665 break;
667 case NET_MSG_RM:
668 enet_address_get_host_ip(&(event.peer->address), ip, 17);
669 port = b_read_uint16(&b_recv);
670 CHECK_RECV_OVERFLOW(&(event.peer->address));
671 for (int i = 0; i < MS_MAXSRVS; ++i) {
672 if (ms_srv[i].used) {
673 if ((strncmp(ip, ms_srv[i].s_ip, 16) == 0) && (ms_srv[i].s_port == port)) {
674 if (ban_heur(ms_srv + i, now) >= MS_MAXHEUR) {
675 ban_add(&(event.peer->address), LC_MS_BANHEUR);
676 break;
678 ms_srv[i].used = 0;
679 printf(LC_MS_RM, i, ip, port);
680 --ms_count;
684 break;
686 case NET_MSG_LIST:
687 b_send.pos = 0;
688 b_write_uint8(&b_send, NET_MSG_LIST);
690 if (event.packet->dataLength > 2) {
691 // holy shit a fresh client
692 clientver = b_read_dstring(&b_recv);
693 b_write_uint8(&b_send, ms_count);
694 } else {
695 // old client, feed them bullshit first
696 b_write_uint8(&b_send, ms_count + 2);
697 for (int i = 0; i < MS_FAKESRVS; ++i)
698 b_write_server(&b_send, ms_fake_srv[i]);
701 CHECK_RECV_OVERFLOW(&(event.peer->address));
703 for (int i = 0; i < MS_MAXSRVS; ++i) {
704 if (ms_srv[i].used)
705 b_write_server(&b_send, ms_srv[i]);
708 if (clientver) {
709 // TODO: check if this client is outdated (?) and send back new verstring
710 // for now just write the same shit back
711 b_write_dstring(&b_send, clientver);
712 // write the motd and urgent message
713 b_write_dstring(&b_send, ms_motd);
714 b_write_dstring(&b_send, ms_urgent);
717 ENetPacket *p = enet_packet_create(b_send.data, b_send.pos, ENET_PACKET_FLAG_RELIABLE);
718 enet_peer_send(event.peer, NET_CH_MAIN, p);
719 enet_host_flush(ms_host);
721 printf(LC_MS_LIST, event.peer->address.host, event.peer->address.port, clientver ? clientver : "<old>");
722 free(clientver);
723 clientver = NULL;
724 break;
726 default:
727 // cheeky cunt sending invalid messages
728 ban_add(&(event.peer->address), LC_MS_BANINVAL);
729 break;
732 enet_packet_destroy(event.packet);
733 break;
735 default:
736 break;
740 time_t now = time(NULL);
741 for (int i = 0; i < MS_MAXSRVS; ++i) {
742 if (ms_srv[i].used) {
743 if (ms_srv[i].deathtime <= now) {
744 ms_srv[i].used = 0;
745 printf(LC_MS_TIME, i, ms_srv[i].s_ip, ms_srv[i].s_port);
746 --ms_count;
752 printf(LC_MS_DIE);
754 return EXIT_SUCCESS;