forked from fluffos/fluffos
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcomm.cc
1669 lines (1464 loc) · 45.2 KB
/
comm.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* comm.c -- communications functions and more.
* Dwayne Fontenot (Jacques@TMI)
*/
#include "base/std.h"
#include "comm.h"
#include <event2/buffer.h> // for evbuffer_freeze, etc
#include <event2/bufferevent.h> // for bufferevent_enable, etc
#include <event2/bufferevent_ssl.h>
#include <event2/event.h> // for EV_TIMEOUT, etc
#include <event2/listener.h> // for evconnlistener_free, etc
#include <event2/util.h> // for evutil_closesocket, etc
#include <stdarg.h> // for va_end, va_list, va_copy, etc
#include <stdio.h> // for snprintf, vsnprintf, fwrite, etc
#include <string.h> // for NULL, memcpy, strlen, etc
#include <unistd.h> // for gethostname
#include <memory> // for unique_ptr
// Network stuff
#ifndef _WIN32
#include <netdb.h> // for addrinfo, freeaddrinfo, etc
#include <netinet/in.h> // for ntohl, IPPROTO_TCP
#include <netinet/tcp.h> // for TCP_NODELAY
#include <sys/socket.h> // for SOCK_STREAM
#else
#include <ws2tcpip.h>
#endif
// ICU
#include <unicode/ucnv.h>
#include "backend.h"
#include "interactive.h"
#include "thirdparty/libtelnet/libtelnet.h"
#include "net/telnet.h"
#include "net/websocket.h"
#include "net/tls.h"
#include "user.h"
#include "vm/vm.h"
#include "ghc/filesystem.hpp"
namespace fs = ghc::filesystem;
#include "packages/core/add_action.h" // FIXME?
#include "packages/core/dns.h" // FIXME?
#include "packages/core/ed.h" // FIXME?
// in backend.cc
extern void update_load_av();
/*
* local function prototypes.
*/
static char *get_user_command(interactive_t * /*ip*/);
static char *first_cmd_in_buf(interactive_t * /*ip*/);
static int call_function_interactive(interactive_t * /*i*/, char * /*str*/);
static void print_prompt(interactive_t * /*ip*/);
#ifdef NO_SNOOP
#define handle_snoop(str, len, who)
#else
#define handle_snoop(str, len, who) \
if ((who)->snooped_by) receive_snoop(str, len, who->snooped_by)
static void receive_snoop(const char * /*buf*/, int /*len*/, object_t *ob);
#endif
namespace {
// User socket event
struct user_event_data {
int idx;
};
void maybe_schedule_user_command(interactive_t *user) {
// If user has a complete command, schedule a command execution.
if (user->iflags & CMD_IN_BUF) {
struct timeval zero_sec = {0, 0};
evtimer_del(user->ev_command);
evtimer_add(user->ev_command, &zero_sec);
}
}
void on_user_command(evutil_socket_t fd, short what, void *arg) {
debug(event, "User has an full command ready: %d:%s%s%s%s \n", (int)fd,
(what & EV_TIMEOUT) ? " timeout" : "", (what & EV_READ) ? " read" : "",
(what & EV_WRITE) ? " write" : "", (what & EV_SIGNAL) ? " signal" : "");
auto user = reinterpret_cast<interactive_t *>(arg);
if (user == nullptr) {
DEBUG_FATAL("on_user_command: user == NULL, Driver BUG.");
return;
}
// FIXME: this function currently calls into mudlib and will throw errors
// This catch block should be moved one level down.
error_context_t econ;
save_context(&econ);
set_eval(max_eval_cost);
try {
process_user_command(user);
} catch (const char *) {
restore_context(&econ);
}
pop_context(&econ);
/* Has to be cleared if we jumped out of process_user_command() */
current_interactive = nullptr;
// if user still have pending command, continue to schedule it.
//
// NOTE: It is important to only execute one command here, then schedule next
// command at the tail, This ensure users have a fair chance that no one can
// keep running commands.
//
// currently command scehduling is done inside process_user_command().
//
// maybe_schedule_user_command(user);
}
void on_user_read(bufferevent *bev, void *arg) {
auto user = reinterpret_cast<interactive_t *>(arg);
if (user == nullptr) {
DEBUG_FATAL("on_user_read: user == NULL, Driver BUG.");
return;
}
// Read user input
get_user_data(user);
// TODO: currently get_user_data() will schedule command execution.
// should probably move it here.
}
void on_user_write(bufferevent *bev, void *arg) {
auto user = reinterpret_cast<interactive_t *>(arg);
if (user == nullptr) {
DEBUG_FATAL("on_user_write: user == NULL, Driver BUG.");
return;
}
// nothing to do.
}
void on_user_events(bufferevent *bev, short events, void *arg) {
auto user = reinterpret_cast<interactive_t *>(arg);
if (user == nullptr) {
DEBUG_FATAL("on_user_events: user == NULL, Driver BUG.");
return;
}
if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
user->iflags |= NET_DEAD;
remove_interactive(user->ob, 0);
} else {
debug(event, "on_user_events: ignored unknown events: %d\n", events);
}
}
void new_user_event_listener(event_base *base, interactive_t *user) {
auto options = BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS;
auto *bev = user->ssl ? bufferevent_openssl_socket_new(base, user->fd, user->ssl,
BUFFEREVENT_SSL_ACCEPTING, options)
: bufferevent_socket_new(base, user->fd, options);
bufferevent_setcb(bev, on_user_read, on_user_write, on_user_events, user);
bufferevent_enable(bev, EV_READ | EV_WRITE);
bufferevent_set_timeouts(bev, nullptr, nullptr);
user->ev_buffer = bev;
}
/*
* This is the new user connection handler. This function is called by the
* event handler when data is pending on the listening socket (new_user_fd).
* If space is available, an interactive data structure is initialized and
* the user is connected.
*/
void new_conn_handler(evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr,
int addrlen, void *arg) {
debug(connections, "New connection from %s.\n", sockaddr_to_string(addr, addrlen));
// TODO: we don't really need to pass in port, we can figure out by
// evconnlistener_get_fd and compare it
auto *port = reinterpret_cast<port_def_t *>(arg);
{
int one = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
#ifndef _WIN32
&one,
#else
(const char *)&one,
#endif
sizeof(one)) == -1) {
debug(connections,
"new_conn_handler: user fd %" FMT_SOCKET_FD ", set_socket_tcp_nodelay error: %s.\n", fd,
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
}
}
if (port->kind == PORT_WEBSOCKET) {
// For websocket connections, wait until they are handshake finished.
init_user_websocket(port->lws_context, fd);
return;
} else {
// For other connections go straight to no handshake necessary, schedule to logon.
auto base = evconnlistener_get_base(listener);
auto user = new_user(port, fd, addr, addrlen);
new_user_event_listener(base, user);
if (user->connection_type == PORT_TELNET) {
user->telnet = net_telnet_init(user);
send_initial_telnet_negotiations(user);
}
event_base_once(
base, -1, EV_TIMEOUT,
[](evutil_socket_t fd, short what, void *arg) {
auto user = reinterpret_cast<interactive_t *>(arg);
on_user_logon(user);
},
(void *)user, nullptr);
}
debug(connections, ("new_conn_handler: end\n"));
} /* new_conn_handler() */
} // namespace
// Initialize an new user
interactive_t *new_user(port_def_t *port, evutil_socket_t fd, sockaddr *addr,
ev_socklen_t addrlen) {
/*
* initialize new user interactive data structure.
*/
auto user = user_add();
user->connection_type = port->kind;
user->ob = master_ob;
user->last_time = get_current_time();
user->trans = nullptr;
user->fd = fd;
user->local_port = port->port;
user->external_port = (port - external_port); // FIXME: pointer arith
memcpy(&user->addr, addr, addrlen);
user->addrlen = addrlen;
if (port->ssl) {
user->ssl = tls_get_client_ctx(port->ssl);
}
// Command handler
auto base = evconnlistener_get_base(port->ev_conn);
user->ev_command = evtimer_new(base, on_user_command, user);
return user;
}
// Called upon user, when he's finished negotiations , and ready to logon
void on_user_logon(interactive_t *user) {
set_command_giver(master_ob);
master_ob->flags |= O_ONCE_INTERACTIVE;
master_ob->interactive = user;
/*
* The user object has one extra reference. It is asserted that the
* master_ob is loaded. Save a pointer to the master ob incase it
* changes during APPLY_CONNECT. We want to free the reference on
* the right copy of the object.
*/
object_t *master, *ob;
svalue_t *ret;
master = master_ob;
add_ref(master_ob, "new_user");
push_number(user->local_port);
set_eval(max_eval_cost);
ret = safe_apply_master_ob(APPLY_CONNECT, 1);
/* master_ob->interactive can be zero if the master object self
destructed in the above (don't ask) */
set_command_giver(nullptr);
if (ret == nullptr || ret == (svalue_t *)-1 || ret->type != T_OBJECT || !master_ob->interactive) {
debug_message("Can not accept connection from %s due to error in connect().\n",
sockaddr_to_string(reinterpret_cast<sockaddr *>(&user->addr), user->addrlen));
if (master_ob->interactive) {
remove_interactive(master_ob, 0);
}
return;
}
/*
* There was an object returned from connect(). Use this as the user
* object.
*/
ob = ret->u.ob;
ob->interactive = master_ob->interactive;
ob->interactive->ob = ob;
ob->flags |= O_ONCE_INTERACTIVE;
/*
* assume the existance of write_prompt and process_input in user.c
* until proven wrong (after trying to call them).
*/
ob->interactive->iflags |= (HAS_WRITE_PROMPT | HAS_PROCESS_INPUT);
free_object(&master, "new_user");
master_ob->flags &= ~O_ONCE_INTERACTIVE;
master_ob->interactive = nullptr;
add_ref(ob, "new_user");
// start reverse DNS probing.
query_name_by_addr(ob);
set_command_giver(ob);
set_prompt("> ");
// Call logon() on the object.
set_eval(max_eval_cost);
ret = safe_apply(APPLY_LOGON, ob, 0, ORIGIN_DRIVER);
if (ret == nullptr) {
debug_message("new_conn_handler: logon() on object %s has failed, the user is disconnected.\n",
ob->obname);
remove_interactive(ob, false);
} else if (ob->flags & O_DESTRUCTED) {
// logon() may decide not to allow user connect by destroying objects.
remove_interactive(ob, true);
}
set_command_giver(nullptr);
}
/*
* Initialize new user connection socket.
*/
bool init_user_conn() {
for (auto &port : external_port) {
#ifdef F_NETWORK_STATS
port.in_packets = 0;
port.in_volume = 0;
port.out_packets = 0;
port.out_volume = 0;
#endif
if (!port.port) continue;
#ifdef IPV6
auto fd = socket(AF_INET6, SOCK_STREAM, 0);
#else
auto fd = socket(AF_INET, SOCK_STREAM, 0);
#endif
if (fd == -1) {
debug_message("socket_create: socket error: %s.\n",
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
return false;
}
if (evutil_make_socket_nonblocking(fd) == -1) {
debug(sockets, "socket_accept: set_socket_nonblocking error: %s.\n",
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
evutil_closesocket(fd);
return false;
}
if (evutil_make_socket_closeonexec(fd) == -1) {
debug(sockets, "socket_accept: make_socket_closeonexec error: %s.\n",
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
evutil_closesocket(fd);
return false;
}
{
int one = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE,
#ifndef _WIN32
(void *)&one,
#else
(const char *)&one,
#endif
sizeof(one)) < 0) {
evutil_closesocket(fd);
return false;
}
}
if (evutil_make_listen_socket_reuseable(fd) < 0) {
evutil_closesocket(fd);
return false;
}
#ifdef __CYGWIN__
#ifdef IPV6
// On windows, IPv6 sockets are IPv6 only by default. We have to change it.
{
auto zero = 0;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&zero, sizeof(zero)) == -1) {
debug_message("socket_create: setsockopt error: %s.\n",
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
evutil_closesocket(fd);
return false;
}
}
#endif
#endif
// Enable TLS
{
if (!port.tls_cert.empty() && !port.tls_key.empty()) {
debug_message("Processing TLS config for port %d...\n", port.port);
fs::path mudlib_path(CONFIG_STR(__MUD_LIB_DIR__));
auto real_cert_path = mudlib_path / port.tls_cert;
auto real_key_path = mudlib_path / port.tls_key;
try {
if (!fs::exists(real_cert_path)) {
debug_message("cert file missing: %s.\n", real_cert_path.c_str());
return false;
}
if (!fs::exists(real_key_path)) {
debug_message("key file missing: %s.\n", real_key_path.c_str());
return false;
}
port.tls_cert = fs::absolute(real_cert_path).string();
port.tls_key = fs::absolute(real_key_path).string();
} catch (fs::filesystem_error &e) {
debug_message("Error: %s (%d).\n", e.what(), e.code().value());
return false;
}
}
}
{
/*
* fill in socket address information.
*/
struct addrinfo *res;
char service[NI_MAXSERV];
snprintf(service, sizeof(service), "%u", port.port);
// Must be initialized to all zero.
struct addrinfo hints = {0};
#ifdef IPV6
hints.ai_family = AF_INET6;
hints.ai_flags |= AI_V4MAPPED;
#else
hints.ai_family = AF_INET;
#endif
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags |= AI_PASSIVE | AI_NUMERICSERV;
int ret;
auto mudip = CONFIG_STR(__MUD_IP__);
if (mudip != nullptr && strlen(mudip) > 0) {
ret = evutil_getaddrinfo(mudip, service, &hints, &res);
} else {
ret = evutil_getaddrinfo(nullptr, service, &hints, &res);
}
if (ret) {
debug_message("init_user_conn: getaddrinfo error: %s \n", evutil_gai_strerror(ret));
return false;
}
if (bind(fd, res->ai_addr, res->ai_addrlen) == -1) {
debug_message("init_user_conn: bind error: %s.\n",
evutil_socket_error_to_string(evutil_socket_geterror(fd)));
evutil_closesocket(fd);
evutil_freeaddrinfo(res);
return false;
}
// Websocket TLS is handled in init_websocket_context
if (!port.tls_cert.empty() && port.kind != PORT_WEBSOCKET) {
SSL_CTX *ctx = tls_server_init(port.tls_cert, port.tls_key);
if (!ctx) {
debug_message("Unable to create TLS context.\n");
evutil_closesocket(fd);
return false;
}
port.ssl = ctx;
}
debug_message("Accepting %s%s connections on %s.\n", port_kind_name(port.kind),
!port.tls_cert.empty() ? "(TLS)" : "",
sockaddr_to_string(res->ai_addr, res->ai_addrlen));
evutil_freeaddrinfo(res);
}
// Listen on connection event
auto conn = evconnlistener_new(
g_event_base, new_conn_handler, &port,
LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE | LEV_OPT_CLOSE_ON_EXEC, 1024, fd);
if (conn == nullptr) {
debug_message("listening failed: %s !", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
return false;
}
port.ev_conn = conn;
port.fd = fd;
if (port.kind == PORT_WEBSOCKET) {
port.lws_context = init_websocket_context(g_event_base, &port);
}
}
return true;
}
/*
* Shut down new user accept file descriptor.
*/
void shutdown_external_ports() {
for (auto &port : external_port) {
if (!port.port) {
continue;
}
// will also close the FD.
if (port.ev_conn) evconnlistener_free(port.ev_conn);
if (port.lws_context) close_websocket_context(port.lws_context);
}
debug_message("closed external ports\n");
}
/*
* If there is a shadow for this object, then the message should be
* sent to it. But only if catch_tell() is defined. Beware that one of the
* shadows may be the originator of the message, which means that we must
* not send the message to that shadow, or any shadows in the linked list
* before that shadow.
*
* Also note that we don't need to do this in the case of
* INTERACTIVE_CATCH_TELL, since catch_tell() was already called
* _instead of_ add_message(), and shadows got their chance then.
*/
#if !defined(NO_SHADOWS)
#define SHADOW_CATCH_MESSAGE
#endif
#ifdef SHADOW_CATCH_MESSAGE
static int shadow_catch_message(object_t *ob, const char *str) {
if (CONFIG_INT(__RC_INTERACTIVE_CATCH_TELL__)) {
return 0;
}
if (!ob->shadowed) {
return 0;
}
while (ob->shadowed != nullptr && ob->shadowed != current_object) {
ob = ob->shadowed;
}
while (ob->shadowing) {
copy_and_push_string(str);
if (apply(APPLY_CATCH_TELL, ob, 1, ORIGIN_DRIVER))
/* this will work, since we know the */
/* function is defined */
{
return 1;
}
ob = ob->shadowing;
}
return 0;
}
#endif
/*
* Send a message to an interactive object. If that object is shadowed,
* special handling is done.
*/
void add_message(object_t *who, const char *data, int len) {
/*
* if who->interactive is not valid, write message on stderr.
* (maybe)
*/
if (!who || (who->flags & O_DESTRUCTED) || !who->interactive ||
(who->interactive->iflags & (NET_DEAD | CLOSING))) {
if (CONFIG_INT(__RC_NONINTERACTIVE_STDERR_WRITE__)) {
putc(']', stderr);
fwrite(data, len, 1, stderr);
}
return;
}
inet_packets++;
auto ip = who->interactive;
switch (ip->connection_type) {
case PORT_ASCII:
case PORT_TELNET: {
// Handle charset transcoding
auto transdata = const_cast<char *>(data);
auto translen = len;
if (ip->trans) {
UErrorCode error_code = U_ZERO_ERROR;
auto required = ucnv_fromAlgorithmic(ip->trans, UConverterType::UCNV_UTF8, nullptr, 0, data,
len, &error_code);
if (error_code == U_BUFFER_OVERFLOW_ERROR) {
translen = required;
transdata = (char *)DMALLOC(translen, TAG_TEMPORARY, "add_message (translate)");
error_code = U_ZERO_ERROR;
auto written = ucnv_fromAlgorithmic(ip->trans, UConverterType::UCNV_UTF8, transdata,
translen, data, len, &error_code);
DEBUG_CHECK(written != translen, "Bug: translation buffer size calculation error");
if (U_FAILURE(error_code)) {
debug_message("add_message: Translation failed!");
transdata = const_cast<char *>(data);
translen = len;
};
}
}
inet_volume += translen;
if (ip->connection_type == PORT_TELNET) {
telnet_send_text(ip->telnet, transdata, translen);
} else {
bufferevent_write(ip->ev_buffer, data, len);
}
if (transdata != data) {
FREE(transdata);
}
} break;
case PORT_WEBSOCKET: {
if (ip->iflags & HANDSHAKE_COMPLETE) {
websocket_send_text(ip->lws, data, len);
} else {
debug_message("User hasn't completed websocket upgrade! can't send message.\n");
}
break;
}
default: {
inet_volume += len;
bufferevent_write(ip->ev_buffer, data, len);
break;
}
}
#ifdef SHADOW_CATCH_MESSAGE
/*
* shadow handling.
*/
if (shadow_catch_message(who, data)) {
if (CONFIG_INT(__RC_SNOOP_SHADOWED__)) {
handle_snoop(data, len, ip);
}
return;
}
#endif /* NO_SHADOWS */
handle_snoop(data, len, ip);
add_message_calls++;
} /* add_message() */
void add_vmessage(object_t *who, const char *format, ...) {
va_list args, args2;
va_start(args, format);
va_copy(args2, args);
static char buf[LARGEST_PRINTABLE_STRING + 1];
do {
auto result = vsnprintf(buf, sizeof(buf), format, args);
if (result < 0) {
DEBUG_CHECK(result < 0, "Invalid format string: add_vmessage");
break;
}
if (result <= sizeof(buf)) {
add_message(who, buf, result);
} else {
std::unique_ptr<char[]> msg(new char[result + 1]);
result = vsnprintf(msg.get(), result + 1, format, args2);
if (result < 0) break;
add_message(who, msg.get(), result);
}
} while (false);
va_end(args2);
va_end(args);
}
/*
* Flush outgoing message buffer of current interactive object.
*/
int flush_message(interactive_t *ip) {
/*
* if ip is not valid, do nothing.
*/
if (!ip) {
debug(connections, ("flush_message: invalid target!\n"));
return 0;
}
// Currently only support Libevent based connections, for websocket based connections, they use ip->lws.
if (ip->ev_buffer) {
// Try to flush things normally
if (bufferevent_flush(ip->ev_buffer, EV_WRITE, BEV_FLUSH) == -1) return 0;
// For socket based bufferevent, bufferevent_flush is actually a no-op, thus we have to
// implement our own.
if (ip->ssl) {
// TODO: implement this
} else {
auto fd = bufferevent_getfd(ip->ev_buffer);
if (fd == -1) {
return 0;
}
auto output = bufferevent_get_output(ip->ev_buffer);
auto total = evbuffer_get_length(output);
if (total > 0) {
evbuffer_unfreeze(output, 1);
auto wrote = evbuffer_write(output, fd);
evbuffer_freeze(output, 1);
return wrote != -1;
}
}
}
return 0;
}
void flush_message_all() {
users_foreach([](interactive_t *user) { flush_message(user); });
}
/*
* Read pending data for a user into user->interactive->text.
* This also does telnet negotiation.
*/
void get_user_data(interactive_t *ip) {
int num_bytes, text_space;
unsigned char buf[MAX_TEXT];
text_space = sizeof(buf);
debug(connections, "get_user_data: USER %d\n", ip->fd);
/* compute how much data we can read right now */
switch (ip->connection_type) {
case PORT_WEBSOCKET:
// Impossible, we don't handle it here.
break;
case PORT_TELNET:
text_space = sizeof(ip->text) - ip->text_end;
/* check if we need more space */
if (text_space < sizeof(ip->text) / 16) {
if (ip->text_start > 0) {
memmove(ip->text, ip->text + ip->text_start, ip->text_end - ip->text_start);
text_space += ip->text_start;
ip->text_end -= ip->text_start;
ip->text_start = 0;
}
if (text_space < sizeof(ip->text) / 16) {
ip->iflags |= SKIP_COMMAND;
ip->text_start = ip->text_end = 0;
text_space = sizeof(ip->text);
}
}
break;
case PORT_MUD:
if (ip->text_end < 4) {
text_space = 4 - ip->text_end;
} else {
text_space = *reinterpret_cast<volatile int *>(ip->text) - ip->text_end + 4;
}
break;
default:
text_space = sizeof(buf);
break;
}
/* read the data from the socket */
debug(connections, "get_user_data: read on fd %d\n", ip->fd);
num_bytes = bufferevent_read(ip->ev_buffer, buf, text_space);
if (num_bytes == -1) {
debug(connections, "get_user_data: fd %d, read error: %s.\n", ip->fd,
evutil_socket_error_to_string(evutil_socket_geterror(ip->fd)));
ip->iflags |= NET_DEAD;
remove_interactive(ip->ob, 0);
return;
}
#ifdef F_NETWORK_STATS
inet_in_packets++;
inet_in_volume += num_bytes;
external_port[ip->external_port].in_packets++;
external_port[ip->external_port].in_volume += num_bytes;
#endif
/* process the data that we've just read */
switch (ip->connection_type) {
case PORT_WEBSOCKET:
// Impossible, we don't handle it here
break;
case PORT_TELNET: {
int start = ip->text_end;
// this will read data into ip->text
telnet_recv(ip->telnet, reinterpret_cast<const char *>(&buf[0]), num_bytes);
// If we read something
if (ip->text_end > start) {
/* handle snooping - snooper does not see type-ahead due to
telnet being in linemode */
if (!(ip->iflags & NOECHO)) {
handle_snoop(ip->text + start, ip->text_end - start, ip);
}
// search for command.
if (cmd_in_buf(ip)) {
ip->iflags |= CMD_IN_BUF;
struct timeval zero_sec = {0, 0};
evtimer_del(ip->ev_command);
evtimer_add(ip->ev_command, &zero_sec);
}
}
break;
}
case PORT_MUD:
memcpy(ip->text + ip->text_end, buf, num_bytes);
ip->text_end += num_bytes;
if (num_bytes == text_space) {
if (ip->text_end == 4) {
*reinterpret_cast<volatile int *>(ip->text) = ntohl(*reinterpret_cast<int *>(ip->text));
if (*reinterpret_cast<volatile int *>(ip->text) > MAX_TEXT - 5) {
remove_interactive(ip->ob, 0);
}
} else {
svalue_t value;
ip->text[ip->text_end] = 0;
if (restore_svalue(ip->text + 4, &value) == 0) {
STACK_INC;
*sp = value;
} else {
push_undefined();
}
ip->text_end = 0;
set_eval(max_eval_cost);
safe_apply(APPLY_PROCESS_INPUT, ip->ob, 1, ORIGIN_DRIVER);
}
}
break;
case PORT_ASCII: {
char *nl, *p;
memcpy(ip->text + ip->text_end, buf, num_bytes);
ip->text_end += num_bytes;
p = ip->text + ip->text_start;
while ((nl = reinterpret_cast<char *>(memchr(p, '\n', ip->text_end - ip->text_start)))) {
ip->text_start = (nl + 1) - ip->text;
*nl = 0;
if (*(nl - 1) == '\r') {
*--nl = 0;
}
if (!(ip->ob->flags & O_DESTRUCTED)) {
char *str;
str = new_string(nl - p, "PORT_ASCII");
memcpy(str, p, nl - p + 1);
push_malloced_string(str);
set_eval(max_eval_cost);
safe_apply(APPLY_PROCESS_INPUT, ip->ob, 1, ORIGIN_DRIVER);
}
if (ip->text_start == ip->text_end) {
ip->text_start = ip->text_end = 0;
break;
}
p = nl + 1;
}
} break;
case PORT_BINARY: {
buffer_t *buffer;
buffer = allocate_buffer(num_bytes);
memcpy(buffer->item, buf, num_bytes);
push_refed_buffer(buffer);
set_eval(max_eval_cost);
safe_apply(APPLY_PROCESS_INPUT, ip->ob, 1, ORIGIN_DRIVER);
} break;
}
}
static int clean_buf(interactive_t *ip) {
/* skip null input */
while (ip->text_start < ip->text_end && !*(ip->text + ip->text_start)) {
ip->text_start++;
}
/* if we've advanced beyond the end of the buffer, reset it */
if (ip->text_start >= ip->text_end) {
ip->text_start = ip->text_end = 0;
}
/* if we're skipping the current command, check to see if it has been
completed yet. if it has, flush it and clear the skip bit */
if (ip->iflags & SKIP_COMMAND) {
char *p;
for (p = ip->text + ip->text_start; p < ip->text + ip->text_end; p++) {
if (*p == '\r' || *p == '\n') {
ip->text_start += p - (ip->text + ip->text_start) + 1;
ip->iflags &= ~SKIP_COMMAND;
return clean_buf(ip);
}
}
}
return (ip->text_end > ip->text_start);
}
void on_user_websocket_received(interactive_t *ip, const char *data, size_t len) {
if (!len) {
return;
}
auto text_space = sizeof(ip->text) - ip->text_end;
/* check if we need more space */
if (text_space < len) {
if (ip->text_start > 0) {
memmove(ip->text, ip->text + ip->text_start, ip->text_end - ip->text_start);
text_space += ip->text_start;
ip->text_end -= ip->text_start;
ip->text_start = 0;
}
if (text_space < len) {
ip->iflags |= SKIP_COMMAND;
ip->text_start = ip->text_end = 0;
text_space = sizeof(ip->text);
}
}
on_user_input(ip, data, len);
if (cmd_in_buf(ip)) {
ip->iflags |= CMD_IN_BUF;
maybe_schedule_user_command(ip);
}
}
// ANSI
static const int ANSI_SUBSTITUTE = 0x20;
// Used by both telnet and ws_ascii, in case of telnet, default is linemode, which means
// client will actually send entire line. In ascii mode, we will get an single char input
// each time.
void on_user_input(interactive_t *ip, const char *data, size_t len) {
for (int i = 0; i < len; i++) {
if (ip->text_end == sizeof(ip->text) - 1) {
// No more space
break;
}
auto c = static_cast<unsigned char>(data[i]);
switch (c) {
case 0x08: // BACKSPACE
case 0x7f: // DEL
if (ip->iflags & SINGLE_CHAR) {
ip->text[ip->text_end++] = c;
} else {
if (ip->text_end > 0) {
ip->text_end--;
}
}
break;
case 0x1b:
if (CONFIG_INT(__RC_NO_ANSI__) && CONFIG_INT(__RC_STRIP_BEFORE_PROCESS_INPUT__)) {
ip->text[ip->text_end++] = ANSI_SUBSTITUTE;
break;
}
// fallthrough
default:
ip->text[ip->text_end++] = c;
break;
}
}
}
// Also used by ws_ascii.
int cmd_in_buf(interactive_t *ip) {
char *p;
/* do standard input buffer cleanup */
if (!clean_buf(ip)) {
return 0;
}
/* if we're in single character mode, we've got input */
if (ip->iflags & SINGLE_CHAR) {
return 1;
}
for (p = ip->text + ip->text_start; p < ip->text + ip->text_end; p++) {
if (*p == '\r' || *p == '\n') {
return 1;
}
}
/* duh, no command */
return 0;