1 // Client network command handlers for HaxServ
3 // Written by: Test_User <hax@andrewyu.org>
5 // This is free and unencumbered software released into the public
8 // Anyone is free to copy, modify, publish, use, compile, sell, or
9 // distribute this software, either in source code form or as a compiled
10 // binary, for any purpose, commercial or non-commercial, and by any
13 // In jurisdictions that recognize copyright laws, the author or authors
14 // of this software dedicate any and all copyright interest in the
15 // software to the public domain. We make this dedication for the benefit
16 // of the public at large and to the detriment of our heirs and
17 // successors. We intend this dedication to be an overt act of
18 // relinquishment in perpetuity of all present and future rights to this
19 // software under copyright law.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24 // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25 // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 // OTHER DEALINGS IN THE SOFTWARE.
29 #include <gnutls/gnutls.h>
31 #include <arpa/inet.h>
32 #include <sys/types.h>
33 #include <netinet/in.h>
34 #include <sys/socket.h>
46 struct table client_network_commands
= {0};
47 struct string client_nick
= {0};
48 uint8_t client_connected
;
50 int add_local_client(struct string uid
, struct string nick_arg
, struct string vhost_arg
, struct string ident_arg
, struct string realname_arg
, time_t timestamp
, char fake_cert
) {
51 if (has_table_index(user_list
, uid
))
54 struct user_info
*user
= malloc(sizeof(*user
));
56 goto add_local_client_fail_user
;
58 struct string server
= {
63 goto add_local_client_fail_server
;
65 memcpy(server
.data
, "1HC", 3);
67 struct string hostname
= {
68 .data
= malloc(vhost_arg
.len
),
72 goto add_local_client_fail_hostname
;
74 memcpy(hostname
.data
, vhost_arg
.data
, hostname
.len
);
76 struct string vhost
= {
77 .data
= malloc(vhost_arg
.len
),
81 goto add_local_client_fail_vhost
;
83 memcpy(vhost
.data
, vhost_arg
.data
, vhost
.len
);
85 struct string ident
= {
86 .data
= malloc(ident_arg
.len
),
90 goto add_local_client_fail_ident
;
92 memcpy(ident
.data
, ident_arg
.data
, ident
.len
);
99 goto add_local_client_fail_ip
;
101 memcpy(ip
.data
, "/dev/null", 9);
103 struct string realname
= {
104 .data
= malloc(realname_arg
.len
),
105 .len
= realname_arg
.len
,
108 goto add_local_client_fail_realname
;
110 memcpy(realname
.data
, realname_arg
.data
, realname
.len
);
112 struct string nick
= {
113 .data
= malloc(nick_arg
.len
),
117 goto add_local_client_fail_nick
;
119 memcpy(nick
.data
, nick_arg
.data
, nick
.len
);
121 *user
= (struct user_info
){
122 .nick_ts
= (uint64_t)timestamp
,
123 .user_ts
= (uint64_t)timestamp
,
126 .hostname
= hostname
,
130 .realname
= realname
,
131 .opertype
= {.data
= malloc(0), .len
= 0},
132 .metadata
= {.array
= malloc(0), .len
= 0},
135 set_table_index(&user_list
, uid
, user
);
137 SEND(STRING("GLOADMODULE m_servprotect\n")); // required for the +k we're about to use
139 char string_time
[21];
140 snprintf(string_time
, 21, "%ld", timestamp
);
141 SEND(STRING("UID "));
144 SEND(NULSTR(string_time
));
156 SEND(NULSTR(string_time
));
157 SEND(STRING(" +k :"));
161 SEND(STRING(":1HC METADATA "));
163 SEND(STRING(" ssl_cert :vTrse "));
167 if (!STRING_EQ(uid
, STRING("1HC000000"))) { // Don't oper haxserv, because echo is unprivileged
170 SEND(STRING(" OPERTYPE "));
177 add_local_client_fail_nick
:
179 add_local_client_fail_realname
:
181 add_local_client_fail_ip
:
183 add_local_client_fail_ident
:
185 add_local_client_fail_vhost
:
187 add_local_client_fail_hostname
:
189 add_local_client_fail_server
:
191 add_local_client_fail_user
:
193 WRITES(2, STRING("OOM! (add_local_client)\n"));
198 int client_nick_handler(uint64_t argc
, struct string
*argv
) {
202 void *tmp
= malloc(argv
[0].len
);
206 void *name_for_tables
;
207 if (client_connected
) {
208 name_for_tables
= malloc(argv
[0].len
);
209 if (!name_for_tables
) {
214 memcpy(name_for_tables
, argv
[0].data
, argv
[0].len
);
217 memcpy(tmp
, argv
[0].data
, argv
[0].len
);
219 if (client_connected
) {
220 SENDCLIENT(STRING(":"));
221 SENDCLIENT(client_nick
);
222 SENDCLIENT(STRING("!e@e NICK :"));
224 SENDCLIENT(STRING("\r\n"));
227 free(client_nick
.data
);
229 client_nick
.data
= tmp
;
230 client_nick
.len
= argv
[0].len
;
232 if (client_connected
) {
233 struct user_info
*client
= get_table_index(user_list
, STRING("1HC000001"));
235 free(client
->nick
.data
);
236 #pragma GCC diagnostic push
237 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
238 client
->nick
.data
= name_for_tables
; // Will not be used uninitialized, ignore the compiler's complaint here
239 #pragma GCC diagnostic pop // Compiler sees global variable and assumes it may be changed in one of the functions called between checks
240 client
->nick
.len
= argv
[0].len
;
242 free(name_for_tables
);
243 WRITES(2, STRING("Client connected but client data missing!\n"));
247 SEND(STRING(":1HC000001 NICK "));
250 char current_time
[22];
251 snprintf(current_time
, 22, "%ld", time(NULL
));
252 SEND(NULSTR(current_time
));
259 int client_user_handler(uint64_t argc
, struct string
*argv
) {
263 if (client_nick
.len
== 0)
266 if (add_local_client(STRING("1HC000001"), client_nick
, client_hostmask
, argv
[0], argv
[3], time(NULL
), 1) != 0)
269 SENDCLIENT(STRING(":"));
270 SENDCLIENT(server_name
);
271 SENDCLIENT(STRING(" 001 "));
272 SENDCLIENT(client_nick
);
273 SENDCLIENT(STRING(" :Welcome to the Rexnet IRC Network\r\n:"));
274 SENDCLIENT(server_name
);
275 SENDCLIENT(STRING(" 002 "));
276 SENDCLIENT(client_nick
);
277 SENDCLIENT(STRING(" :Your host is "));
278 SENDCLIENT(server_name
);
279 SENDCLIENT(STRING(", running a totally not sus IRCd\r\n:"));
280 SENDCLIENT(server_name
);
281 SENDCLIENT(STRING(" 003 "));
282 SENDCLIENT(client_nick
);
283 SENDCLIENT(STRING(" :This server was created 02:51:36 Apr 03 2023\r\n:"));
284 SENDCLIENT(server_name
);
285 SENDCLIENT(STRING(" 004 "));
286 SENDCLIENT(client_nick
);
287 SENDCLIENT(STRING(" "));
288 SENDCLIENT(server_name
);
289 SENDCLIENT(STRING(" InspIRCd-3 BDGHILNORSTWcdghikorswxz ABCDEFGHIJKLMNOPQRSTXYZabcdefghijklmnopqrstuvwz :BEFHIJLXYZabdefghjkloqvw\r\n:"));
290 SENDCLIENT(server_name
);
291 SENDCLIENT(STRING(" 005 "));
292 SENDCLIENT(client_nick
);
293 SENDCLIENT(STRING(" ACCEPT=100 AWAYLEN=200 BOT=B CALLERID=g CASEMAPPING=ascii CHANLIMIT=#:20 CHANMODES=IXZbegw,k,BEFHJLdfjl,ACDGKMNOPQRSTcimnprstuz CHANNELLEN=60 CHANTYPES=# ELIST=CMNTU ESILENCE=CcdiNnPpTtx EXCEPTS=e :are supported by this server\r\n:"));
294 SENDCLIENT(server_name
);
295 SENDCLIENT(STRING(" 005 "));
296 SENDCLIENT(client_nick
);
297 SENDCLIENT(STRING(" EXTBAN=,ACNOQRSTUacjmnprswz HOSTLEN=64 INVEX=I KEYLEN=32 KICKLEN=300 LINELEN=512 MAXLIST=I:1000,X:1000,b:1000,e:1000,g:1000,w:1000 MAXTARGETS=20 MODES=20 MONITOR=30 NAMELEN=130 NAMESX NETWORK=LibreIRC :are supported by this server\r\n:"));
298 SENDCLIENT(server_name
);
299 SENDCLIENT(STRING(" 005 "));
300 SENDCLIENT(client_nick
);
301 SENDCLIENT(STRING(" NICKLEN=30 OVERRIDE=O PREFIX=(Yqaohv)!~&@%+ REMOVE SAFELIST SECURELIST=60 SILENCE=100 STATUSMSG=!~&@%+ TOPICLEN=330 UHNAMES USERIP USERLEN=10 USERMODES=,,s,BDGHILNORSTWcdghikorwxz :are supported by this server\r\n:"));
302 SENDCLIENT(server_name
);
303 SENDCLIENT(STRING(" 005 "));
304 SENDCLIENT(client_nick
);
305 SENDCLIENT(STRING(" WATCH=32 WHOX :are supported by this server\r\n"));
307 client_connected
= 1;
312 int client_join_handler(uint64_t argc
, struct string
*argv
) {
316 time_t ctime
= time(NULL
);
318 struct string channels
= argv
[0];
320 char current_time_nulstr
[22];
321 uint64_t current_time
;
323 WRITES(2, STRING("Please check your clock.\n"));
326 current_time
= (uint64_t)ctime
;
330 while (offset
< channels
.len
&& channels
.data
[offset
] != ',')
333 uint64_t oldlen
= channels
.len
;
334 channels
.len
= offset
;
336 struct channel_info
*channel_info
= get_table_index(channel_list
, channels
);
338 channel_info
= malloc(sizeof(*channel_info
));
340 WRITES(2, STRING("OOM! (client_join)\n"));
343 *channel_info
= (struct channel_info
){
345 .topic
= {.data
= malloc(0), .len
= 0},
347 .modes
= {.array
= malloc(0), .len
= 0},
348 .user_list
= {.array
= malloc(0), .len
= 0},
349 .metadata
= {.array
= malloc(0), .len
= 0},
352 set_table_index(&channel_list
, channels
, channel_info
);
354 if (channel_info
->ts
< current_time
)
355 current_time
= channel_info
->ts
;
357 snprintf(current_time_nulstr
, 22, "%lu", current_time
);
359 SENDCLIENT(STRING(":"));
360 SENDCLIENT(client_nick
);
361 SENDCLIENT(STRING("!e@e JOIN :"));
362 SENDCLIENT(channels
);
363 for (uint64_t i
= 0; i
< channel_info
->user_list
.len
; i
++) {
365 struct user_info
*info
= channel_info
->user_list
.array
[i
].ptr
;
369 user
= user_list
.array
[i
].name
;
372 SENDCLIENT(STRING(" "));
375 SENDCLIENT(STRING("\r\n:"));
376 SENDCLIENT(server_name
);
377 SENDCLIENT(STRING(" 353 "));
378 SENDCLIENT(client_nick
);
379 SENDCLIENT(STRING(" = "));
380 SENDCLIENT(channels
);
381 SENDCLIENT(STRING(" :"));
385 SENDCLIENT(STRING("\r\n:"));
386 SENDCLIENT(server_name
);
387 SENDCLIENT(STRING(" 366 "));
388 SENDCLIENT(client_nick
);
389 SENDCLIENT(STRING(" "));
390 SENDCLIENT(channels
);
391 SENDCLIENT(STRING(" :End of /NAMES list.\r\n"));
393 SEND(STRING(":1HC FJOIN "));
396 SEND(NULSTR(current_time_nulstr
));
397 SEND(STRING(" + :,1HC000001\n"));
399 set_table_index(&(channel_info
->user_list
), STRING("1HC000001"), get_table_index(user_list
, STRING("1HC000001"))); // TODO: Actually add local users to user_list
401 channels
.len
= oldlen
;
403 if (channels
.len
<= offset
+1)
406 channels
.data
+= offset
+ 1;
407 channels
.len
-= offset
+ 1;
413 int client_privmsg_handler(uint64_t argc
, struct string
*argv
) {
417 SEND(STRING(":1HC000001 PRIVMSG "));
424 if (argv
[0].data
[0] == '#') {
425 if (argv
[1].len
< command_prefix
.len
|| memcmp(argv
[1].data
, command_prefix
.data
, command_prefix
.len
) != 0)
428 offset
= command_prefix
.len
;
433 if (offset
>= argv
[1].len
|| argv
[1].data
[offset
] == ' ')
436 uint64_t command_argc
= 0;
437 uint64_t old_offset
= offset
;
438 while (offset
< argv
[1].len
) {
439 while (offset
< argv
[1].len
&& argv
[1].data
[offset
] != ' ')
444 while (offset
< argv
[1].len
&& argv
[1].data
[offset
] == ' ')
449 struct string command_argv
[command_argc
]; // argv[0] in this case is the command itself, unlike network command handlers... might change one of these two later to match
451 while (offset
< argv
[1].len
) {
452 command_argv
[i
].data
= argv
[1].data
+offset
;
453 uint64_t start
= offset
;
455 while (offset
< argv
[1].len
&& argv
[1].data
[offset
] != ' ')
458 command_argv
[i
].len
= offset
- start
;
460 while (offset
< argv
[1].len
&& argv
[1].data
[offset
] == ' ')
466 argv
[1].data
+= old_offset
;
467 argv
[1].len
-= old_offset
;
468 struct command_def
*cmd
= get_table_index(user_commands
, command_argv
[0]);
470 struct string message
[] = {
471 STRING("Local user "),
473 STRING(" executes `"),
478 privmsg(STRING("1HC000000"), log_channel
, sizeof(message
)/sizeof(*message
), message
);
480 return cmd
->func(STRING("1HC000001"), argv
[1], argv
[0], command_argc
, command_argv
, 1);
482 if (argv
[0].data
[0] == '#') {
483 SEND(STRING(":1HC000000 NOTICE "));
485 SEND(STRING(" :Unknown command: "));
486 SEND(command_prefix
);
487 SEND(command_argv
[0]);
496 int client_raw_handler(uint64_t argc
, struct string
*argv
) {
506 int client_ping_handler(uint64_t argc
, struct string
*argv
) {
510 SENDCLIENT(STRING(":"));
511 SENDCLIENT(server_name
);
512 SENDCLIENT(STRING(" PONG :"));
514 SENDCLIENT(STRING("\r\n"));
519 int client_mode_handler(uint64_t argc
, struct string
*argv
) {
521 return 0; // Mode querying not supported yet
523 SEND(STRING(":1HC000001 MODE "));
524 for (uint64_t i
= 0; i
< argc
- 1; i
++) {
529 SEND(argv
[argc
- 1]);
537 int client_listen_fd
;
538 int initclientnetwork(void) {
539 client_network_commands
.array
= malloc(0);
541 set_table_index(&client_network_commands
, STRING("NICK"), &client_nick_handler
);
542 set_table_index(&client_network_commands
, STRING("USER"), &client_user_handler
);
543 set_table_index(&client_network_commands
, STRING("JOIN"), &client_join_handler
);
544 set_table_index(&client_network_commands
, STRING("PRIVMSG"), &client_privmsg_handler
);
545 set_table_index(&client_network_commands
, STRING("RAW"), &client_raw_handler
);
546 set_table_index(&client_network_commands
, STRING("PING"), &client_ping_handler
);
547 set_table_index(&client_network_commands
, STRING("MODE"), &client_mode_handler
);
549 client_nick
.data
= malloc(0);
551 client_listen_fd
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
552 if (client_listen_fd
< 0)
556 setsockopt(client_listen_fd
, SOL_SOCKET
, SO_REUSEADDR
, &one
, sizeof(one
));
558 struct sockaddr_in localhost
= {
559 .sin_family
= AF_INET
,
560 .sin_port
= htons(6667),
562 inet_pton(AF_INET
, "127.0.0.1", &localhost
.sin_addr
); // this is indeed localhost for mine, and I have no intent to change this
563 bind(client_listen_fd
, (struct sockaddr
*)&localhost
, sizeof(localhost
));
565 listen(client_listen_fd
, 1);
571 ssize_t
SENDCLIENT(struct string msg
) {
572 if (msg
.len
== 0 || client_fd
== -1)
575 static char printprefix
= 1;
578 WRITES(1, STRING("\x1b[31m[Us->Client] \x1b[32m"));
580 WRITES(1, STRING("[Us->Client] "));
588 if (msg
.data
[msg
.len
- 1] == '\n') {
591 WRITES(1, STRING("\x1b[0m\n"));
593 WRITES(1, STRING("\n"));
598 return WRITES(client_fd
, msg
);