├── README.md ├── json.c ├── json.h ├── makefile ├── rcirc.c ├── util.c └── util.h /README.md: -------------------------------------------------------------------------------- 1 | # rcirc 2 | 3 | Tunnel that exposes RocketChat as an IRC server. 4 | 5 | ## Requirements 6 | 7 | - json-c 8 | - libwebsockets 9 | 10 | OpenSUSE: `zypper install libjson-c-devel 'libwebsockets-devel>4' libopenssl-devel` 11 | 12 | ## Building 13 | 14 | make 15 | 16 | ## Running 17 | 18 | ./rcirc -l 19 | 20 | Then connect with IRC client, for example: 21 | 22 | irssi -c localhost -p -w 23 | 24 | 25 | -------------------------------------------------------------------------------- /json.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "util.h" 13 | 14 | int json_read(void *data, struct json_object *object, const char *fmt, ...) 15 | { 16 | va_list args; 17 | struct jc_stack { 18 | struct json_object *o; 19 | int isarray; 20 | struct jc_stack *firstchild, *parent; 21 | }; 22 | 23 | const char *c; 24 | char z; 25 | int cnt = 0; 26 | int mode = 0; 27 | char objname[32]; 28 | int on_i = 0; 29 | 30 | struct jc_stack *root = alloca(sizeof(struct jc_stack)), *top = 31 | root, *n; 32 | top->parent = NULL; 33 | top->o = object; 34 | struct json_object *curr = NULL; 35 | 36 | va_start(args, fmt); 37 | 38 | for (c = fmt; (z = *c); c++) { 39 | if (mode == 5) { 40 | mode = 0; 41 | va_arg(args, void*); 42 | continue; 43 | } else if (mode == 3) { 44 | 45 | switch (z) { 46 | case 'o': 47 | { 48 | json_object *v = curr; 49 | *(json_object **) va_arg(args, 50 | json_object **) 51 | = v; 52 | cnt++; 53 | } 54 | break; 55 | case 'b': 56 | { 57 | int v = json_object_get_boolean(curr); 58 | *(int *)va_arg(args, int *) = v; 59 | cnt++; 60 | } 61 | break; 62 | case 'd': 63 | { 64 | int v = json_object_get_int(curr); 65 | *(int *)va_arg(args, int *) = v; 66 | cnt++; 67 | } 68 | break; 69 | case 's': 70 | { 71 | const char *v = 72 | json_object_get_string(curr); 73 | *(const char **)va_arg(args, 74 | const char **) = 75 | strdup(v); 76 | cnt++; 77 | } 78 | break; 79 | default: 80 | logg(ERR, "Wrong usage\n"); 81 | goto error; 82 | } 83 | while (top->o == curr && top->parent) 84 | top = top->parent; 85 | curr = top->o; 86 | mode = 0; 87 | continue; 88 | } else if (mode == 7) { 89 | int r = 0; 90 | 91 | switch (z) { 92 | case 'b': 93 | { 94 | int v = json_object_get_boolean(curr); 95 | if ((int)va_arg(args, int) != v) 96 | r = -1; 97 | } 98 | break; 99 | case 'd': 100 | { 101 | int v = json_object_get_int(curr); 102 | if ((int)va_arg(args, int) != v) 103 | r = -1; 104 | } 105 | break; 106 | case 's': 107 | { 108 | const char *v = 109 | json_object_get_string(curr); 110 | if (strcmp 111 | ((const char *) 112 | va_arg(args, const char *), v)) 113 | r = -1; 114 | } 115 | break; 116 | default: 117 | fprintf(stderr, "Wrong usage\n"); 118 | goto error; 119 | } 120 | if (r == -1) 121 | goto error; 122 | while (top->o == curr && top->parent) 123 | top = top->parent; 124 | curr = top->o; 125 | mode = 0; 126 | continue; 127 | } 128 | 129 | switch (z) { 130 | case '[': 131 | n = alloca(sizeof(struct jc_stack)); 132 | n->parent = top; 133 | if (!curr) 134 | curr = object; 135 | n->o = curr; 136 | n->isarray = 1; 137 | top->firstchild = n; 138 | top = n; 139 | break; 140 | case ']': 141 | top = top->parent; 142 | curr = top->o; 143 | break; 144 | case '{': 145 | n = alloca(sizeof(struct jc_stack)); 146 | n->parent = top; 147 | if (!curr) 148 | curr = object; 149 | n->o = curr; 150 | n->isarray = 0; 151 | top->firstchild = n; 152 | top = n; 153 | break; 154 | case '}': 155 | while (top->o == curr && top->parent) 156 | top = top->parent; 157 | curr = top->o; 158 | break; 159 | case ' ': 160 | break; 161 | 162 | case '?': 163 | case '=': 164 | case ':': 165 | objname[on_i] = '\0'; 166 | 167 | mode = (z == '=') ? 6 : 1; 168 | 169 | on_i = 0; 170 | 171 | n = alloca(sizeof(struct jc_stack)); 172 | n->parent = top; 173 | 174 | if (top->isarray) { 175 | n->o = 176 | json_object_array_get_idx(curr, 177 | (top->isarray - 178 | 1)); 179 | top->isarray++; 180 | } else { 181 | n->o = json_object_object_get(curr, objname); 182 | } 183 | if (!n->o) { 184 | logg(DBG3, "No member named [%p] \'%s\'\n", 185 | curr, objname); 186 | if (z == '?') { 187 | mode = 4; 188 | break; 189 | } else 190 | goto error; 191 | } 192 | 193 | if (!top->firstchild) 194 | top->firstchild = n; 195 | n->isarray = 0; 196 | top = n; 197 | curr = n->o; 198 | 199 | break; 200 | case '%': 201 | if (mode == 4) 202 | mode = 5; 203 | else if (mode == 6) 204 | mode = 7; 205 | else 206 | mode = 3; 207 | break; 208 | default: 209 | if (on_i >= sizeof(objname)) { 210 | logg(DBG1, "Too long objname"); 211 | goto error; 212 | } 213 | objname[on_i++] = z; 214 | } 215 | } 216 | va_end(args); 217 | 218 | return (cnt); 219 | error: 220 | va_end(args); 221 | return -1; 222 | 223 | } 224 | 225 | struct json_object *json_create(void *data, const char *fmt, ...) 226 | { 227 | va_list args; 228 | struct jc_stack { 229 | json_object *o; 230 | int isarray; 231 | char *name; 232 | struct jc_stack *firstchild, *parent; 233 | }; 234 | 235 | const char *c; 236 | char z; 237 | int mode = 0; 238 | char objname[32]; 239 | int on_i = 0; 240 | 241 | struct jc_stack *root = alloca(sizeof(struct jc_stack)), *top = 242 | root, *n; 243 | top->parent = NULL; 244 | top->o = NULL; 245 | 246 | va_start(args, fmt); 247 | 248 | for (c = fmt; (z = *c); c++) { 249 | if (mode == 3) { 250 | struct json_object *s; 251 | 252 | switch (z) { 253 | case 'b': 254 | s = json_object_new_boolean(va_arg(args, int)); 255 | break; 256 | case 'd': 257 | s = json_object_new_int(va_arg(args, int)); 258 | break; 259 | case 's': 260 | s = json_object_new_string(va_arg 261 | (args, char *)); 262 | break; 263 | case 'o': 264 | s = va_arg(args, json_object *); 265 | break; 266 | default: 267 | logg(DBG1, "Wrong usage\n"); 268 | goto error; 269 | } 270 | if (top->isarray) 271 | json_object_array_add(top->o, s); 272 | else 273 | json_object_object_add(top->o, top->name, s); 274 | mode = 0; 275 | continue; 276 | } 277 | switch (z) { 278 | case '[': 279 | n = alloca(sizeof(struct jc_stack)); 280 | n->parent = top; 281 | n->name = NULL; 282 | n->o = json_object_new_array(); 283 | n->isarray = 1; 284 | top->firstchild = n; 285 | top = n; 286 | break; 287 | case ']': 288 | if (top->parent->o) { 289 | if (top->parent->isarray) 290 | json_object_array_add(top->parent->o, 291 | top->o); 292 | else 293 | json_object_object_add(top->parent->o, 294 | top->parent-> 295 | name, top->o); 296 | } 297 | top = top->parent; 298 | break; 299 | case '{': 300 | n = alloca(sizeof(struct jc_stack)); 301 | n->parent = top; 302 | n->name = NULL; 303 | n->o = json_object_new_object(); 304 | n->isarray = 0; 305 | top->firstchild = n; 306 | top = n; 307 | break; 308 | case '}': 309 | if (top->parent->o) { 310 | if (top->parent->isarray) 311 | json_object_array_add(top->parent->o, 312 | top->o); 313 | else 314 | json_object_object_add(top->parent->o, 315 | top->parent-> 316 | name, top->o); 317 | } 318 | top = top->parent; 319 | break; 320 | case ' ': 321 | break; 322 | 323 | case ':': 324 | objname[on_i] = '\0'; 325 | top->name = strdupa(objname); 326 | mode = 1; 327 | on_i = 0; 328 | break; 329 | case '%': 330 | mode = 3; 331 | break; 332 | default: 333 | if (on_i >= sizeof(objname)) { 334 | logg(DBG3, "Too long objname"); 335 | goto error; 336 | } 337 | objname[on_i++] = z; 338 | } 339 | } 340 | va_end(args); 341 | 342 | while (top->parent && top->parent->o) { 343 | top = top->parent; 344 | } 345 | return (root->firstchild->o); 346 | error: 347 | va_end(args); 348 | return NULL; 349 | } 350 | 351 | char *reactions2string(struct json_object *jo) 352 | { 353 | int len = 0; 354 | char *str, *p; 355 | 356 | if (!jo || json_object_get_type(jo) != json_type_object) return (NULL); 357 | 358 | json_object_object_foreach(jo, key, value) { 359 | if (len) len ++; 360 | len += strlen(key); 361 | len ++; 362 | 363 | struct json_object *un; 364 | if (json_object_object_get_ex(value, "usernames", &un)) { 365 | int cnt = json_object_array_length(un); 366 | for (int i = 0; i < cnt; i++) { 367 | if (i) len ++; 368 | len += strlen(json_object_get_string(json_object_array_get_idx(un, i))); 369 | } 370 | } 371 | } 372 | 373 | if (len == 0) { 374 | return NULL; 375 | } 376 | len ++; 377 | 378 | str = malloc(len); 379 | p = str; 380 | *p = '\0'; 381 | json_object_object_foreach(jo, keyx, valuex) { 382 | if (p != str) { 383 | *(p++) = ' '; *p = '\0'; 384 | } 385 | p = stpcpy(p, keyx); 386 | *(p++) = '='; *p = '\0'; 387 | 388 | struct json_object *un; 389 | if (json_object_object_get_ex(valuex, "usernames", &un)) { 390 | int cnt = json_object_array_length(un); 391 | for (int i = 0; i < cnt; i++) { 392 | if (i) { 393 | *(p++) = ','; 394 | *p = '\0'; 395 | } 396 | p = stpcpy(p, 397 | json_object_get_string(json_object_array_get_idx(un, i))); 398 | } 399 | } 400 | 401 | } 402 | return (str); 403 | } 404 | 405 | 406 | #ifdef _JSON_TEST 407 | int main(int argc, char **argv) 408 | { 409 | 410 | struct json_object *o; 411 | 412 | printf("==== TEST 1 ====\n"); 413 | o = json_create(NULL, 414 | "{ahoj:%s id:%d arr:[%b [] %b] o:{p1:%d p2:%s p3:%o}}", 415 | "cau", 4, 6, 0, 456, "sst", NULL); 416 | 417 | printf("S=%s\n", json_object_to_json_string(o)); 418 | 419 | char *s = NULL, *p2 = NULL; 420 | 421 | printf("==== TEST 2 ====\n"); 422 | int r = json_read(NULL, o, "{ahoj:%s o:{p2:%s}}", &s, &p2); 423 | 424 | printf("r=%d,s=%s,p2=%s\n", r, s, p2); 425 | 426 | json_object *jo = 427 | json_tokener_parse 428 | ("{\"msg\":\"changed\",\"collection\":\"stream-notify-user\",\"id\":\"id\",\"fields\":{\"eventName\":\"RCdeuvCAQb7sFwbPA/notification\",\"args\":[{\"title\":\"Lesslie Nengu\",\"text\":\"Basically they want to close the case (to which bug is connected).\",\"payload\":{\"_id\":\"EXBbjGxve9ha5Bpwv\",\"rid\":\"RCdeuvCAQb7sFwbPAWEjesNXpKc9HzxKYZ\",\"sender\":{\"_id\":\"WEjesNXpKc9HzxKYZ\",\"username\":\"amuddy2\"},\"type\":\"d\",\"message\":{\"msg\":\"Basically the...\"}}}]}}"); 429 | 430 | char *msg = NULL, *collection = NULL, *fields = NULL, *rid = NULL; 431 | 432 | printf("==== TEST 3 ====\n"); 433 | 434 | if ((r = 435 | json_read(NULL, jo, "{msg:%s collection:%s fields:{args:%o}}", 436 | &msg, &collection, &fields, &rid)) == 3) { 437 | printf("ok\n"); 438 | printf("len=%d\n", json_object_array_length(fields)); 439 | } else { 440 | printf("err %d\n", r); 441 | } 442 | 443 | jo = json_tokener_parse("{" 444 | " \"collection\": \"stream-notify-user\"," 445 | " \"fields\": {" 446 | " \"args\": [" 447 | " \"removed\"," 448 | " {" 449 | " \"_id\": \"hiZESKwWkeJFsCMxH\"," 450 | " \"rid\": \"HaHmdy4mmfmStKvQR\"," 451 | " \"u\": {" 452 | " \"_id\": \"RCdeuvCAQb7sFwbPA\"," 453 | " \"name\": \"Bilbo Baggins\"," 454 | " \"username\": \"merkele\"" 455 | " }" 456 | " }" 457 | " ]," 458 | " \"eventName\": \"RCdeuvCAQb7sFwbPA/subscriptions-changed\"" 459 | " }," 460 | " \"id\": \"id\"," 461 | " \"msg\": \"changed\"" "}"); 462 | 463 | rid = NULL; 464 | printf("==== TEST 4 ====\n"); 465 | 466 | r = json_read(NULL, jo, "{msg=%s fields:{args:[=%s :{rid:%s}]} }", 467 | "changed", "removed", &rid); 468 | printf("R=%d,rid=%s\n", r, rid); 469 | 470 | jo = json_tokener_parse("{\n" 471 | " \"collection\": \"stream-room-messages\",\n" 472 | " \"fields\": {\n" 473 | " \"args\": [\n" 474 | " {\n" 475 | " \"_id\": \"BtMxjQzX7TGQhRNxN\",\n" 476 | " \"_updatedAt\": {\n" 477 | " \"$date\": 1602361705261\n" 478 | " },\n" 479 | " \"groupable\": false,\n" 480 | " \"msg\": \"zelele\",\n" 481 | " \"rid\": \"HaHmdk4mmzmStKvQR\",\n" 482 | " \"t\": \"uj\",\n" 483 | " \"ts\": {\n" 484 | " \"$date\": 1602361705241\n" 485 | " },\n" 486 | " \"u\": {\n" 487 | " \"_id\": \"farmZrGcK269cKCP2\",\n" 488 | " \"name\": \"Miklos Vajna\",\n" 489 | " \"username\": \"zelele\"\n" 490 | " }\n" 491 | " },\n" 492 | " {\n" 493 | " \"roomName\": \"rc-learn-aanovak-4\",\n" 494 | " \"roomParticipant\": true,\n" 495 | " \"roomType\": \"c\"\n" 496 | " }\n" 497 | " ],\n" 498 | " \"eventName\": \"__my_messages__\"\n" 499 | " },\n" 500 | " \"id\": \"id\",\n" 501 | " \"msg\": \"changed\"\n" "}\n"); 502 | 503 | printf("==== TEST 5 ====\n"); 504 | char *username, *roomName; 505 | r = json_read(NULL, jo, 506 | "{collection=%s msg=%s fields:{args:[:{t=%s u:{username:%s}} :{roomName:%s}]}}", 507 | "stream-room-messages", "changed", "uj", &username, 508 | &roomName); 509 | printf("R=%d,username=%s,roomName=%s\n", r, username, roomName); 510 | 511 | printf("==== TEST 6 ====\n"); 512 | jo = json_tokener_parse("{\n" 513 | " \"_id\": \"Rs9iRsadaasdhGHx\",\n" 514 | " \"_updatedAt\": {\n" 515 | " \"$date\": 1602491275690\n" 516 | " },\n" 517 | " \"channels\": [],\n" 518 | " \"mentions\": [],\n" 519 | " \"msg\": \"down\",\n" 520 | " \"reactions\": {\n" 521 | " \":flushed:\": {\n" 522 | " \"usernames\": [\n" 523 | " \"amusta\"\n" 524 | " ]\n" 525 | " }\n" 526 | " },\n" 527 | " \"rid\": \"GisRHFYEMmWoM5\",\n" 528 | " \"tmid\": \"somX89q4vHjavp\",\n" 529 | " \"ts\": {\n" 530 | " \"$date\": 1602491242261\n" 531 | " },\n" 532 | " \"u\": {\n" 533 | " \"_id\": \"n2bzjQNCcEuq2FDWi\",\n" 534 | " \"name\": \"Harty\",\n" 535 | " \"username\": \"gonzo\"\n" 536 | " }\n" 537 | " }\n" 538 | ); 539 | { 540 | char *msg = NULL, *rid = NULL, *_id = NULL, *username = NULL, *tmid = NULL; 541 | json_object *attachments = NULL, *reactions = NULL; 542 | r = json_read(NULL, jo, 543 | "{msg:%s rid:%s _id:%s u:{username:%s} attachments?%o reactions?%o tmid?%s}", 544 | &msg, &rid, &_id, &username, &attachments, &reactions, &tmid); 545 | printf("r=%d\n", r); 546 | } 547 | 548 | jo = json_tokener_parse("{" 549 | "\":flushed:\": {\n" 550 | "\"usernames\": [\n" 551 | " \"ahmie\"\n" 552 | "]\n" 553 | "},\n" 554 | "\":bedazzled:\":{\"usernames\":[\"eva\",\"vasek\"]}" 555 | "}"); 556 | 557 | printf (reactions2string(jo)); 558 | return 0; 559 | } 560 | #endif 561 | -------------------------------------------------------------------------------- /json.h: -------------------------------------------------------------------------------- 1 | #ifndef __json_h__ 2 | #define __json_h__ 3 | 4 | #include 5 | 6 | int json_read(void *data, struct json_object *object, const char *fmt, ...); 7 | struct json_object *json_create(void *data, const char *fmt, ...); 8 | char *reactions2string(struct json_object *jo); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | TARGET = rcirc 2 | 3 | all: $(TARGET) 4 | 5 | .SUFFIXES: .c .h .o .html 6 | 7 | OBJS = rcirc.o util.o json.o 8 | 9 | CPPFLAGS = -g -std=c99 -I. -pedantic -Wall 10 | LDFLAGS = -g -lpthread -lwebsockets -ljson-c 11 | 12 | clean: 13 | rm -f $(OBJS) $(TARGET) 14 | 15 | install: 16 | mkdir -p $(DESTDIR)$(PREFIX)/bin 17 | cp -f $(TARGET) $(DESTDIR)$(PREFIX)/bin 18 | 19 | 20 | $(TARGET): $(OBJS) 21 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 22 | -------------------------------------------------------------------------------- /rcirc.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include "json.h" 23 | #include "util.h" 24 | 25 | #define MALLOC malloc 26 | #define NEW(x) (x *)memset(MALLOC(sizeof(x)), 0, sizeof(x)) 27 | #define IFFREE(x) if((x)) {free (x); (x) = NULL;} 28 | #define STRDUP(x) ((x)?(strdup((x))):NULL) 29 | 30 | #define EROOMNOTFOUND -1 31 | 32 | #define STATE_SHUTTING_DOWN 1 33 | 34 | #define MAXFD 512 35 | 36 | static char *MSG_edited = "[edited]"; 37 | static char *MSG_removed = "[removed]"; 38 | 39 | static char *server_name = NULL; 40 | 41 | static int interrupted; 42 | 43 | typedef struct t_buff { 44 | char *buff; 45 | char *start; 46 | int left; 47 | struct t_buff *next; 48 | } t_buff; 49 | 50 | typedef struct t_rc_room { 51 | char *rid; 52 | char *fname; 53 | char *name; 54 | char *topic; 55 | unsigned long long ls; 56 | char t; 57 | 58 | struct t_rc_room *next; 59 | } t_rc_room; 60 | 61 | typedef char t_rc_id[16]; 62 | struct t_sess; 63 | 64 | typedef struct t_rc_command { 65 | t_rc_id id; 66 | void *data; 67 | int (*fce)(struct t_sess * s, void *data, struct json_object * json); 68 | 69 | struct t_rc_command *next; 70 | } t_rc_command; 71 | 72 | typedef struct t_rc_message { 73 | const char *id; 74 | char t; 75 | char *dstname; 76 | char *msg; 77 | char *sender; 78 | char *reactions; 79 | time_t tim; 80 | 81 | struct t_rc_message *next; 82 | } t_rc_message; 83 | 84 | typedef struct t_rc_sndmsg { 85 | struct t_rc_sndmsg *next; 86 | char id[]; 87 | } t_rc_sndmsg; 88 | 89 | typedef struct t_sess { 90 | int irc_fd; 91 | int rch_fd; 92 | int state; 93 | struct { 94 | char *nick; 95 | } irc; 96 | char irc_buff[513]; 97 | int irc_buff_head; 98 | struct lws_pollfd *poll; 99 | struct lws_pollfd *rc_poll; 100 | t_buff *irc_out_buff, **irc_out_buff_tail; 101 | 102 | struct { 103 | struct lws_context *context; 104 | struct lws_vhost *vhost; 105 | const struct lws_protocols *protocol; 106 | pthread_t pthread_spam[2]; 107 | 108 | t_buff *out_buff, **out_buff_tail; 109 | uint32_t tail; 110 | 111 | struct lws_client_connect_info i; 112 | struct lws *client_wsi; 113 | 114 | int counter; 115 | int fd_idx; 116 | char finished; 117 | char established; 118 | 119 | time_t last_ping; 120 | 121 | char *self_id; 122 | char *token; 123 | 124 | t_rc_command *commands; 125 | t_rc_room *rooms; 126 | t_rc_message *messages; 127 | t_rc_message *in_messages; 128 | void *in_messages_tree; 129 | t_rc_sndmsg *sent; 130 | } rc; 131 | 132 | struct t_sess *next; 133 | } t_sess; 134 | 135 | struct lws_pollfd pollfds[MAXFD]; 136 | struct t_sess *sessions[MAXFD]; 137 | int maxfds = 0; 138 | static int rc_timeout = 60*5; 139 | const char *selfident = "myserver"; 140 | static struct t_sess *session_list = NULL; 141 | 142 | #define SWAPC(a, b) {char *__c; __c = a; a = b; b = __c; } 143 | 144 | void poll_removefd(int i) 145 | { 146 | for (t_sess *s = session_list; s; s = s->next) { 147 | if (s->rc_poll >= &pollfds[i]) s->rc_poll--; 148 | if (s->poll >= &pollfds[i]) s->poll--; 149 | } 150 | 151 | memcpy(&pollfds[i], &pollfds[i + 1], 152 | sizeof(struct lws_pollfd) * (maxfds - i)); 153 | memcpy(&sessions[i], &sessions[i + 1], sizeof(t_sess *) * (maxfds - i)); 154 | maxfds--; 155 | } 156 | 157 | int poll_addfd(int fd, t_sess * s, int events) 158 | { 159 | pollfds[maxfds].fd = fd; 160 | pollfds[maxfds].events = events; 161 | sessions[maxfds] = s; 162 | s->rc.fd_idx = maxfds; 163 | 164 | return (maxfds++); 165 | } 166 | 167 | t_buff *buff__sprintf(const char *fmt, ...); 168 | t_buff *buff__new(); 169 | void buff__free(t_buff *b); 170 | 171 | void sess__add_irc_out(t_sess * s, t_buff * b); 172 | int sess__irc_nick_set(t_sess * s, const char *newnick); 173 | int sess__irc_send_message(t_sess * s, char t, const char *srcname, 174 | const char *name, char *pfx, char *msg); 175 | 176 | void sess__add_sent_message(t_sess * s, const char *id); 177 | int sess__find_sent_message(t_sess * s, const char *id); 178 | 179 | int sess__rc_send_message(t_sess * s, char t, const char *name, 180 | const char *msg); 181 | void gen_id(t_rc_id id); 182 | int sess__rc_command_call(t_sess * s, void *data, json_object * json, 183 | int (*fce)(t_sess * s, void *data, json_object * j)); 184 | 185 | t_rc_room *sess__rc_room_add(t_sess * s, char t, const char *name, 186 | const char *rid, const char *fname, 187 | const char *topic); 188 | t_rc_room *sess__rc_room_by_rid(t_sess * s, const char *rid); 189 | t_rc_room *sess__rc_room_by_name(t_sess * s, char t, const char *name); 190 | int sess__rc_command_call_(t_sess * s, void *data, json_object * json, 191 | int (*fce)(t_sess * s, void *data, json_object * j)); 192 | int sess__rc_json_send_(t_sess * s, json_object * json); 193 | int sess__rc_start(t_sess * s, struct lws_context *ctx); 194 | int sess__rc_join_room(t_sess * s, char t, const char *name); 195 | int sess__rc_queue_message(t_sess * s, char t, const char *name, 196 | const char *msgs); 197 | void rc_message__free(t_rc_message * m); 198 | t_rc_message *sess__rc_find_message(t_sess *s, const char *id); 199 | t_rc_message *sess__rc_add_message(t_sess *s, const char *id, const char *msg, const char *sender, const char *reactions); 200 | int sess__rc_queue_process(t_sess * s); 201 | int sess__rc_set_away(t_sess * s, const char *msg); 202 | int sess__rc_set_back(t_sess * s); 203 | 204 | 205 | int sess__cb_rc_getusers(t_sess * s, void *data, json_object * j); 206 | int sess__cb_rc_joinroom(t_sess * s, void *data, json_object * j); 207 | int sess__cb_rc_getroom(t_sess * s, void *data, json_object * j); 208 | int sess__cb_rc_sendmessage(t_sess * s, void *data, json_object * j); 209 | int sess__cb_rc_getsubscriptions(t_sess * s, void *data, json_object * j); 210 | int sess__cb_rc_login(t_sess * s, void *data, json_object * j); 211 | int sess__cb_rc_createdirect(t_sess * s, void *data, json_object * j); 212 | int sess__cb_rc_set_away(t_sess * s, void *data, json_object * j); 213 | int sess__cb_rc_set_back(t_sess * s, void *data, json_object * j); 214 | 215 | int sess__close(t_sess * s); 216 | int irc__process(t_sess * s, struct lws_context *ctx); 217 | 218 | int sess__add_rc_out(t_sess * s, int len, const char *c) 219 | { 220 | if (len == -1) 221 | len = strlen(c); 222 | t_buff *buff = buff__new(); 223 | buff->buff = malloc(LWS_PRE + len + 1); 224 | memcpy(buff->buff + LWS_PRE, c, len); 225 | buff->buff[LWS_PRE + len] = '\0'; 226 | buff->left = len; 227 | 228 | *s->rc.out_buff_tail = buff; 229 | s->rc.out_buff_tail = &buff->next; 230 | 231 | if (s->rc.client_wsi) { 232 | lws_callback_on_writable(s->rc.client_wsi); 233 | s->rc_poll->events |= POLLOUT; 234 | } 235 | return 0; 236 | } 237 | 238 | int sess__add_rc_out_(t_sess * s, const char *c) 239 | { 240 | int r = sess__add_rc_out(s, -1, c); 241 | free((char *)c); 242 | return (r); 243 | } 244 | 245 | void sess__rc_process_message(t_sess *s, char *_id, char *username, char *msg, char *roomName, char *mt, char *t, t_rc_room *room, struct json_object *reactions, struct json_object *attachments) 246 | { 247 | /* 248 | * was this message sent by us? */ 249 | if (sess__find_sent_message(s, _id)) 250 | return; 251 | 252 | char *s_reactions = reactions2string(reactions); 253 | 254 | char *_pfx = NULL; 255 | char *_msg = msg; 256 | 257 | /* 258 | * have we already seen this message? */ 259 | t_rc_message *m = sess__rc_find_message(s, _id); 260 | 261 | if (m) { 262 | logg(DBG4, "Repeated message %s\n", _id); 263 | 264 | if(s_reactions && (!m->reactions || strcmp(m->reactions, s_reactions))) { 265 | char *tmp = m->reactions; 266 | m->reactions = s_reactions; 267 | free(tmp); 268 | _pfx = m->reactions; 269 | } 270 | 271 | if(strcmp(m->msg, msg)) { 272 | char *tmp = m->msg; 273 | m->msg = STRDUP(msg); 274 | free(tmp); 275 | _pfx = MSG_edited; 276 | 277 | if (mt && !strcmp(mt, "rm")) { 278 | _pfx = MSG_removed; 279 | _msg = msg; 280 | } 281 | } 282 | 283 | if (!_pfx) 284 | return; 285 | } else { 286 | sess__rc_add_message(s, _id, msg, username, s_reactions); 287 | } 288 | 289 | char *_dest = roomName; 290 | char _t = 'd'; 291 | 292 | if (t && (*t == 'c' || *t == 'p') 293 | && roomName) { 294 | _t = 'c'; 295 | } else if (room 296 | && !strcmp(username, 297 | s->irc.nick)) { 298 | _dest = room->name; 299 | } else { 300 | _dest = s->irc.nick; 301 | } 302 | 303 | sess__irc_send_message(s, _t, 304 | username, 305 | _dest, 306 | _pfx, 307 | _msg); 308 | } 309 | 310 | 311 | 312 | t_rc_command *sess__rc_command_by_id(t_sess * s, const t_rc_id id) 313 | { 314 | t_rc_command *cmd; 315 | 316 | for (cmd = s->rc.commands; cmd && strcmp(cmd->id, id); 317 | cmd = cmd->next) ; 318 | 319 | return cmd; 320 | } 321 | 322 | int sess__rc_work(t_sess * s, const char *in) 323 | { 324 | int r; 325 | char *msg = NULL, *collection = NULL, *selfuser = NULL, *userid = NULL, 326 | *_id = NULL, *username = NULL, *roomName = NULL; 327 | json_object *args = NULL; 328 | struct json_object *jo, *jobj = json_tokener_parse(in); 329 | 330 | if (!jobj) { 331 | logg(ERR, "Can\'t json_tokener_parse()\n"); 332 | return (-1); 333 | 334 | } 335 | 336 | if (json_object_object_get_ex(jobj, "msg", &jo)) { 337 | const char *msg = json_object_get_string(jo); 338 | if (!strcmp(msg, "ping")) { 339 | msg = NULL; 340 | s->rc.last_ping = time(NULL); 341 | sess__add_rc_out(s, -1, "{\"msg\":\"pong\"}"); 342 | goto finished; 343 | } 344 | msg = NULL; 345 | } else jo = NULL; 346 | 347 | if (json_object_object_get_ex(jobj, "id", &jo)) { 348 | const char *id = json_object_get_string(jo); 349 | t_rc_command *cb = sess__rc_command_by_id(s, id); 350 | if (cb) { 351 | logg(DBG1, "I've GOT REPLY %s!\n", id); 352 | 353 | if (cb->fce) 354 | r = cb->fce(s, cb->data, jobj); 355 | 356 | /* TODO: delete command struct */ 357 | 358 | goto finished; 359 | } 360 | } else jo = NULL; 361 | 362 | if (json_read 363 | (NULL, jobj, "{msg=%s collection=%s id:%s fields:{username:%s}}", 364 | "added", "users", &userid, &selfuser) == 2) { 365 | sess__irc_nick_set(s, selfuser); 366 | s->rc.self_id = userid; 367 | userid = NULL; 368 | 369 | char *allsubs[] = 370 | { "notification", "rooms-changed", "subscriptions-changed", 371 | "otr", NULL }; 372 | 373 | for (char **subs = allsubs; *subs; subs++) { 374 | char su[64]; 375 | 376 | snprintf(su, sizeof(su), "%s/%s", s->rc.self_id, *subs); 377 | 378 | sess__rc_command_call_(s, NULL, 379 | json_create(NULL, 380 | "{msg:%s name:%s params:[%s %b]}", 381 | "sub", 382 | "stream-notify-user", 383 | su, 0), NULL); 384 | } 385 | 386 | goto finished; 387 | } 388 | if (json_read 389 | (NULL, jobj, 390 | "{collection=%s msg=%s fields:{args:[:{t=%s u:{username:%s}} :{roomName:%s}]}}", 391 | "stream-room-messages", "changed", "uj", &username, 392 | &roomName) == 2) { 393 | 394 | sess__add_irc_out(s, 395 | buff__sprintf(":%s JOIN #%s\r\n", username, 396 | roomName)); 397 | 398 | goto finished; 399 | } 400 | /* 401 | if (json_read(NULL, jobj, "{collection=%s msg=%s fields:{args:[:{t=%s u:{username:%s}} :{roomName:%s}]}}", 402 | "stream-room-messages", "changed", "ru", &username, &roomName) == 2) { 403 | 404 | sess__add_irc_out (s, buff__sprintf (":%s PART #%s\r\n", username, roomName)); 405 | 406 | goto finished; 407 | } */ 408 | 409 | if (json_read(NULL, jobj, "{msg:%s collection:%s fields:{args:%o}}", 410 | &msg, &collection, &args) == 3) { 411 | 412 | if (!strcmp(collection, "stream-notify-user")) { 413 | char *name = NULL, *fname = NULL, *rid = NULL, *t = 414 | NULL; 415 | 416 | if (json_read 417 | (NULL, args, 418 | "[=%s :{name:%s fname:%s rid:%s t:%s}]", 419 | "inserted", &name, &fname, &rid, &t) == 4) { 420 | sess__rc_room_add(s, t[0], name, rid, fname, 421 | NULL); 422 | 423 | IFFREE(fname); 424 | IFFREE(name); 425 | IFFREE(rid); 426 | IFFREE(t); 427 | sess__rc_queue_process(s); 428 | } 429 | } else { 430 | int cnt_args = json_object_array_length(args); 431 | for (int i = 0; i < cnt_args; i++) { 432 | json_object *attachments = NULL, *reactions = NULL; 433 | json_object *p = 434 | json_object_array_get_idx(args, i); 435 | 436 | char *msg = NULL, *rid = NULL, *username = 437 | NULL, *roomName = NULL, *t = NULL, *tmid = NULL, 438 | *mt = NULL; 439 | int roomParticipant = 0; 440 | 441 | if ((r = json_read(NULL, p, 442 | "{payload:{message:{msg:%s} rid:%s sender:{username:%s} type:%s _id:%s}}", 443 | &msg, &rid, &username, &t, 444 | &_id)) >= 5) { 445 | 446 | char *dst = s->irc.nick; 447 | if (!strcmp(dst, username)) { 448 | t_rc_room *r = 449 | sess__rc_room_by_rid(s, 450 | rid); 451 | if (r) 452 | dst = r->name; 453 | } 454 | if (sess__find_sent_message(s, _id)) { 455 | IFFREE(msg); 456 | IFFREE(rid); 457 | IFFREE(username); 458 | IFFREE(t); 459 | continue; 460 | 461 | } 462 | 463 | sess__irc_send_message(s, 'd', username, 464 | dst, NULL, msg); 465 | 466 | } else { 467 | 468 | if ((i + 1) < cnt_args) { 469 | i += 1; 470 | json_object *op = 471 | json_object_array_get_idx 472 | (args, i); 473 | 474 | r = json_read(NULL, op, 475 | "{roomType:%s roomName?%s roomParticipant?%b}", 476 | &t, &roomName, 477 | &roomParticipant); 478 | 479 | } 480 | 481 | 482 | if (json_object_is_type(p, json_type_array)) { 483 | int cnt_msgs = json_object_array_length(p); 484 | for (int ii = 0; ii < cnt_msgs; ii++) { 485 | json_object *p2 = json_object_array_get_idx(p, ii); 486 | 487 | if ((r = json_read(NULL, p2, 488 | "{msg:%s rid:%s _id:%s u:{username:%s} attachments?%o reactions?%o tmid?%s t?%s}", 489 | &msg, &rid, &_id, 490 | &username, &attachments, &reactions, &tmid, &mt)) >= 4 491 | && msg && rid && _id && username) { 492 | 493 | t_rc_room *room = sess__rc_room_by_rid(s, rid); 494 | 495 | sess__rc_process_message(s, _id, username, msg, roomName, mt, 496 | t, room, reactions, attachments); 497 | 498 | } 499 | 500 | IFFREE(msg); 501 | IFFREE(rid); 502 | IFFREE(username); 503 | IFFREE(mt); 504 | IFFREE(tmid); 505 | 506 | } 507 | } else { 508 | if ((r = json_read(NULL, p, 509 | "{msg:%s rid:%s _id:%s u:{username:%s} attachments?%o reactions?%o tmid?%s t?%s}", 510 | &msg, &rid, &_id, 511 | &username, &attachments, &reactions, &tmid, &mt)) >= 4 512 | && msg && rid && _id && username) { 513 | 514 | t_rc_room *room = sess__rc_room_by_rid(s, rid); 515 | 516 | sess__rc_process_message(s, _id, username, msg, roomName, mt, 517 | t, room, reactions, attachments); 518 | 519 | } 520 | 521 | IFFREE(msg); 522 | IFFREE(rid); 523 | IFFREE(username); 524 | IFFREE(mt); 525 | IFFREE(tmid); 526 | 527 | } 528 | 529 | 530 | IFFREE(t); 531 | IFFREE(roomName); 532 | 533 | } 534 | } 535 | } 536 | 537 | } 538 | 539 | finished: 540 | json_object_put(jobj); 541 | IFFREE(msg); 542 | IFFREE(_id); 543 | IFFREE(collection); 544 | IFFREE(selfuser); 545 | IFFREE(userid); 546 | IFFREE(username); 547 | IFFREE(roomName); 548 | 549 | return (0); 550 | } 551 | 552 | static int 553 | callback_minimal_broker_2(struct lws *wsi, enum lws_callback_reasons reason, 554 | void *user, void *in, size_t len) 555 | { 556 | int m; 557 | 558 | t_sess *s = lws_get_opaque_user_data(wsi); 559 | 560 | if (!s) 561 | return lws_callback_http_dummy(wsi, reason, user, in, len); 562 | 563 | if (s->rc.finished) 564 | return (-1); 565 | 566 | switch (reason) { 567 | 568 | case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: 569 | logg(ERR, "CLIENT_CONNECTION_ERROR: %s\n", 570 | in ? (char *)in : "(null)"); 571 | s->rc.client_wsi = NULL; 572 | s->rc.finished = 1; 573 | sess__add_irc_out(s, 574 | buff__sprintf("RocketChat connection error\r\n")); 575 | 576 | s->state |= STATE_SHUTTING_DOWN; 577 | break; 578 | 579 | case LWS_CALLBACK_CLIENT_ESTABLISHED: 580 | logg(DBG1, "%s: established\n", __func__); 581 | s->rc.established = 1; 582 | break; 583 | 584 | case LWS_CALLBACK_CLIENT_WRITEABLE: 585 | logg(DBG4, "LWS_CALLBACK_CLIENT_WRITEABLE\n"); 586 | t_buff *b = s->rc.out_buff; 587 | if (!b) { 588 | s->rc_poll->events = POLLIN; 589 | goto skip; 590 | } 591 | 592 | logg(DBG4, "RC-TX[%d]: %.*s\n", s->rc_poll->fd, b->left, 593 | (const char *)b->buff + LWS_PRE); 594 | m = lws_write(wsi, ((unsigned char *)b->buff) + LWS_PRE, 595 | b->left, LWS_WRITE_TEXT); 596 | if (m < (int)b->left) { 597 | logg(ERR, "ERROR %d writing to ws socket\n", m); 598 | return -1; 599 | } 600 | 601 | if (s->rc.out_buff_tail == &b->next) { 602 | s->rc.out_buff_tail = &s->rc.out_buff; 603 | s->rc_poll->events = POLLIN; 604 | } else { 605 | lws_callback_on_writable(wsi); 606 | } 607 | s->rc.out_buff = b->next; 608 | buff__free(b); 609 | skip: 610 | break; 611 | 612 | case LWS_CALLBACK_CLIENT_CLOSED: 613 | logg(DBG1, "%s: LWS_CALLBACK_CLIENT_CLOSED\n", __func__); 614 | s->rc.client_wsi = NULL; 615 | s->rc.established = 0; 616 | s->rc.finished = 1; 617 | 618 | sess__add_irc_out(s, 619 | buff__sprintf("RocketChat closing socket\r\n")); 620 | 621 | s->state |= STATE_SHUTTING_DOWN; 622 | break; 623 | 624 | case LWS_CALLBACK_EVENT_WAIT_CANCELLED: 625 | if (s->rc.client_wsi && s->rc.established) 626 | lws_callback_on_writable(s->rc.client_wsi); 627 | break; 628 | 629 | case LWS_CALLBACK_USER: 630 | logg(DBG4, "%s: LWS_CALLBACK_USER\n", __func__); 631 | break; 632 | 633 | case LWS_CALLBACK_CLIENT_RECEIVE: 634 | logg(DBG4, "RC-RX[%d]: %s\n", s->rc_poll->fd, (const char *)in); 635 | 636 | sess__rc_work(s, in); 637 | break; 638 | 639 | default: 640 | break; 641 | } 642 | 643 | return lws_callback_http_dummy(wsi, reason, user, in, len); 644 | } 645 | 646 | static const struct lws_protocols protocols[] = { 647 | { 648 | "lws-minimal-broker", 649 | callback_minimal_broker_2, 650 | 0, 651 | 65535*2, 652 | 0, 653 | NULL, 654 | }, 655 | {NULL, NULL, 0, 0} 656 | }; 657 | 658 | static void sigint_handler(int sig) 659 | { 660 | interrupted = 1; 661 | } 662 | 663 | struct lws_pollfd irc_mainport_pollfd; 664 | int irc_mainport = 6666; 665 | int rc_port = 443; 666 | 667 | int irc_mainport_bind() 668 | { 669 | int sockopt; 670 | struct addrinfo *n, *no, hostinf; 671 | char sport[128]; 672 | int r, sock; 673 | 674 | snprintf(sport, sizeof(sport), "%d", irc_mainport); 675 | 676 | memset(&hostinf, 0, sizeof(hostinf)); 677 | 678 | hostinf.ai_family = AF_UNSPEC; 679 | hostinf.ai_socktype = SOCK_STREAM; 680 | hostinf.ai_flags = AI_PASSIVE; 681 | 682 | r = getaddrinfo(NULL, sport, &hostinf, &n); 683 | 684 | if (r || !n) { 685 | logg(ERR, "Cannot getaddrinfo socket %d\n", r); 686 | return (-2); 687 | } 688 | 689 | no = n; 690 | 691 | for (; n; n = n->ai_next) { 692 | sock = socket(n->ai_family, n->ai_socktype, n->ai_protocol); 693 | 694 | if (sock == -1) { 695 | logg(ERR, "Cannot create socket.. %d (%s)", errno, 696 | strerror(errno)); 697 | freeaddrinfo(no); 698 | return (-2); 699 | } 700 | 701 | sockopt = 1; 702 | 703 | if (setsockopt 704 | (sock, SOL_SOCKET, SO_REUSEADDR, (void *)&sockopt, 705 | sizeof(sockopt)) != 0) { 706 | logg(ERR, "error setting socket options (%s).", 707 | strerror(errno)); 708 | return (-1); 709 | } 710 | 711 | if (bind(sock, n->ai_addr, n->ai_addrlen) == 0) 712 | break; 713 | 714 | close(sock); 715 | 716 | sock = -1; 717 | } 718 | 719 | if (sock == -1) { 720 | logg(ERR, "Cannot create socket.\n"); 721 | return (-3); 722 | } 723 | 724 | listen(sock, 5); 725 | 726 | return (sock); 727 | } 728 | 729 | t_buff *buff__new() 730 | { 731 | t_buff *b; 732 | 733 | b = malloc(sizeof(*b)); 734 | memset(b, 0, sizeof(*b)); 735 | 736 | return (b); 737 | } 738 | 739 | void buff__free(t_buff * b) 740 | { 741 | free(b->buff); 742 | free(b); 743 | } 744 | 745 | t_buff *buff__sprintf(const char *fmt, ...) 746 | { 747 | va_list args; 748 | t_buff *b; 749 | 750 | b = buff__new(); 751 | va_start(args, fmt); 752 | 753 | b->left = vasprintf(&b->buff, fmt, args); 754 | b->start = b->buff; 755 | 756 | return (b); 757 | } 758 | 759 | t_sess *sess_new() 760 | { 761 | t_sess *s; 762 | 763 | s = malloc(sizeof(*s)); 764 | memset(s, 0, sizeof(*s)); 765 | s->irc_buff_head = 0; 766 | s->irc_out_buff_tail = &s->irc_out_buff; 767 | s->rc.out_buff_tail = &s->rc.out_buff; 768 | 769 | s->next = session_list; 770 | session_list = s; 771 | 772 | return (s); 773 | } 774 | 775 | void sess__add_irc_out(t_sess * s, t_buff * b) 776 | { 777 | *s->irc_out_buff_tail = b; 778 | s->irc_out_buff_tail = &b->next; 779 | 780 | s->poll->events |= POLLOUT; 781 | } 782 | 783 | void gen_id(t_rc_id id) 784 | { 785 | int r = rand(); 786 | snprintf(id, sizeof(t_rc_id), "%014x", r); 787 | } 788 | 789 | int sess__rc_command_call(t_sess * s, void *data, json_object * json, 790 | int (*fce)(t_sess * s, void *data, json_object * j)) 791 | { 792 | t_rc_id id; 793 | t_rc_command *cmd; 794 | 795 | gen_id(id); 796 | 797 | json_object_object_add(json, "id", json_object_new_string(id)); 798 | 799 | cmd = NEW(t_rc_command); 800 | 801 | sess__add_rc_out(s, -1, json_object_to_json_string(json)); 802 | 803 | cmd->data = data; 804 | cmd->fce = fce; 805 | memcpy(cmd->id, id, sizeof(id)); 806 | 807 | cmd->next = s->rc.commands; 808 | s->rc.commands = cmd; 809 | 810 | return 0; 811 | } 812 | 813 | t_rc_room *sess__rc_room_add(t_sess * s, char t, const char *name, 814 | const char *rid, const char *fname, 815 | const char *topic) 816 | { 817 | 818 | t_rc_room *room = sess__rc_room_by_rid(s, rid); 819 | if (room) { 820 | IFFREE(room->name); 821 | IFFREE(room->fname); 822 | IFFREE(room->topic); 823 | IFFREE(room->rid); 824 | } else { 825 | room = NEW(t_rc_room); 826 | room->next = s->rc.rooms; 827 | s->rc.rooms = room; 828 | } 829 | if (fname) 830 | room->fname = strdup(fname); 831 | if (name) 832 | room->name = strdup(name); 833 | if (topic) 834 | room->topic = strdup(topic); 835 | room->t = t; 836 | room->rid = strdup(rid); 837 | 838 | return (room); 839 | } 840 | 841 | t_rc_room *sess__rc_room_by_rid(t_sess * s, const char *rid) 842 | { 843 | t_rc_room *r; 844 | 845 | for (r = s->rc.rooms; r && (strcmp(r->rid, rid)); r = r->next) ; 846 | 847 | return (r); 848 | } 849 | 850 | t_rc_room *sess__rc_room_by_name(t_sess * s, char t, const char *name) 851 | { 852 | t_rc_room *r; 853 | 854 | for (r = s->rc.rooms; r && (r->t != t || strcmp(r->name, name)); 855 | r = r->next) ; 856 | 857 | return (r); 858 | } 859 | 860 | int sess__cb_rc_createdirect(t_sess * s, void *data, json_object * j) 861 | { 862 | char *t = NULL, *rid = NULL, *msg = NULL; 863 | json_object *usernames; 864 | 865 | if (json_read 866 | (NULL, j, "{result:{t:%s rid:%s usernames:%o}}", &t, &rid, 867 | &usernames) == 3) { 868 | const char *name = NULL; 869 | 870 | for (int i = 0; i < json_object_array_length(usernames); i++) { 871 | json_object *un = json_object_array_get_idx(usernames, i); 872 | name = json_object_get_string(un); 873 | if (name && strcmp(name, s->irc.nick)) { 874 | break; 875 | } 876 | } 877 | if (!name) 878 | goto end; 879 | 880 | sess__rc_room_add(s, t[0], name, rid, NULL, NULL); 881 | } else { 882 | 883 | if (json_read(NULL, j, "{error:{message:%s}}", &msg) == 1) { 884 | sess__add_irc_out(s, 885 | buff__sprintf("ERROR: %s\r\n", msg)); 886 | } 887 | 888 | } 889 | 890 | end: 891 | IFFREE(t); 892 | IFFREE(rid); 893 | IFFREE(msg); 894 | 895 | sess__rc_queue_process(s); 896 | return 0; 897 | } 898 | 899 | void sess__add_sent_message(t_sess * s, const char *id) 900 | { 901 | int l = strlen(id); 902 | t_rc_sndmsg *m = malloc(sizeof(t_rc_sndmsg) + l + 1); 903 | 904 | m->next = s->rc.sent; 905 | s->rc.sent = m; 906 | 907 | strcpy(m->id, id); 908 | } 909 | 910 | int sess__find_sent_message(t_sess * s, const char *id) 911 | { 912 | for (t_rc_sndmsg * m = s->rc.sent; m; m = m->next) 913 | if (!strcmp(m->id, id)) 914 | return 1; 915 | 916 | return (0); 917 | } 918 | 919 | int sess__cb_rc_sendmessage(t_sess * s, void *data, json_object * j) 920 | { 921 | char *_id = NULL; 922 | if ((json_read(NULL, j, "{result:{_id:%s}}", &_id)) != 1) { 923 | return 0; 924 | } 925 | 926 | sess__add_sent_message(s, _id); 927 | 928 | IFFREE(_id); 929 | return 0; 930 | } 931 | 932 | int sess__cb_rc_getsubscriptions(t_sess * s, void *data, json_object * j) 933 | { 934 | logg(ERR, "CALLBACK - SUBS \n"); 935 | json_object *res; 936 | 937 | if (json_read(NULL, j, "{result:%o}", &res) != 1) { 938 | logg(ERR, "Malformed reply\n"); 939 | goto error; 940 | } 941 | 942 | int sublen = json_object_array_length(res); 943 | 944 | for (int i = 0; i < sublen; i++) { 945 | json_object *o = json_object_array_get_idx(res, i); 946 | 947 | char *fname = NULL, *rid = NULL, *name = 948 | NULL, *t = NULL; 949 | 950 | if (json_read(NULL, o, "{fname:%s rid:%s name:%s t:%s}", 951 | &fname, &rid, &name, &t) != 4) { 952 | 953 | logg(ERR, "Invalid sub record\n"); 954 | continue; 955 | } 956 | 957 | sess__rc_room_add(s, t[0], name, rid, fname, NULL); 958 | 959 | IFFREE(fname); 960 | IFFREE(t); 961 | IFFREE(rid); 962 | IFFREE(name); 963 | 964 | } 965 | 966 | for (t_rc_room * r = s->rc.rooms; r; r = r->next) { 967 | if (r->t == 'c' || r->t == 'p') 968 | sess__rc_join_room(s, r->t, r->name); 969 | } 970 | 971 | return 0; 972 | error: 973 | return -1; 974 | } 975 | 976 | int sess__cb_rc_login(t_sess * s, void *data, json_object * j) 977 | { 978 | logg(ERR, "CALLBACK - LOGIN\n"); 979 | 980 | char *reason = NULL; 981 | int error; 982 | 983 | if (json_read(NULL, j, "{error:{error:%d reason:%s}}", &error, &reason) 984 | == 2 && error) { 985 | sess__add_irc_out(s, 986 | buff__sprintf("RocketChat Error %d: %s\r\n", error, 987 | reason)); 988 | 989 | s->state |= STATE_SHUTTING_DOWN; 990 | IFFREE(reason); 991 | } 992 | return 0; 993 | } 994 | 995 | int sess__rc_command_call_(t_sess * s, void *data, json_object * json, 996 | int (*fce)(t_sess * s, void *data, json_object * j)) 997 | { 998 | int r = sess__rc_command_call(s, data, json, fce); 999 | json_object_put(json); 1000 | return (r); 1001 | } 1002 | 1003 | int sess__rc_json_send_(t_sess * s, json_object * json) 1004 | { 1005 | const char *c = json_object_to_json_string(json); 1006 | int r = sess__add_rc_out(s, -1, c); 1007 | json_object_put(json); 1008 | return (r); 1009 | } 1010 | 1011 | int sess__rc_start(t_sess * s, struct lws_context *ctx) 1012 | { 1013 | if (s->rc_poll) { 1014 | logg(ERR, "RC session already started"); 1015 | return 2; 1016 | } 1017 | s->rc.context = ctx; 1018 | s->rc.i.context = ctx; 1019 | s->rc.i.port = rc_port; 1020 | s->rc.i.address = server_name; 1021 | s->rc.i.path = "/websocket"; 1022 | s->rc.i.host = s->rc.i.address; 1023 | s->rc.i.origin = s->rc.i.address; 1024 | s->rc.i.opaque_user_data = s; 1025 | s->rc.i.ssl_connection = LCCSCF_USE_SSL; 1026 | 1027 | s->rc.i.protocol = "lws-minimal-broker"; 1028 | s->rc.i.pwsi = &s->rc.client_wsi; 1029 | 1030 | struct lws *ws = lws_client_connect_via_info(&s->rc.i); 1031 | 1032 | if (!ws) { 1033 | logg(ERR, "Error in lws_client_connect_via_info\n"); 1034 | return 1; 1035 | } 1036 | 1037 | int fd = lws_get_socket_fd(ws); 1038 | 1039 | logg(ERR, "FD=%d\n", fd); 1040 | 1041 | s->rc_poll = &pollfds[poll_addfd(fd, s, POLLIN | POLLOUT)]; 1042 | 1043 | sess__rc_json_send_(s, 1044 | json_create(NULL, 1045 | "{msg:%s version:%s support:[%s]}", 1046 | "connect", "1", "1")); 1047 | 1048 | sess__rc_command_call_(s, NULL, 1049 | json_create(NULL, 1050 | "{msg:%s method:%s params:[{resume:%s}]}", 1051 | "method", "login", s->rc.token), 1052 | sess__cb_rc_login); 1053 | 1054 | sess__rc_command_call_(s, NULL, 1055 | json_create(NULL, "{msg:%s method:%s params:[]}", 1056 | "method", "subscriptions/get"), 1057 | sess__cb_rc_getsubscriptions); 1058 | 1059 | sess__rc_command_call_(s, NULL, 1060 | json_create(NULL, 1061 | "{msg:%s name:%s params:[%s %b]}", 1062 | "sub", "stream-room-messages", 1063 | "__my_messages__", 0), NULL); 1064 | 1065 | s->rc.context = lws_get_context(ws); 1066 | s->rc.protocol = lws_get_protocol(ws); 1067 | s->rc.vhost = lws_get_vhost(ws); 1068 | 1069 | return 0; 1070 | } 1071 | 1072 | int sess__irc_nick_set(t_sess * s, const char *newnick) 1073 | { 1074 | sess__add_irc_out(s, 1075 | buff__sprintf(":%s NICK %s\r\n", s->irc.nick, 1076 | newnick)); 1077 | IFFREE(s->irc.nick); 1078 | s->irc.nick = strdup(newnick); 1079 | 1080 | return 0; 1081 | } 1082 | 1083 | int sess__irc_send_message(t_sess * s, char t, const char *srcname, 1084 | const char *name, char *pfx, char *msg) 1085 | { 1086 | int r = 0; 1087 | char pmsg[2]; 1088 | char *next, *start; 1089 | 1090 | if (t == 'c') { 1091 | pmsg [0] = '#'; 1092 | pmsg [1] = '\0'; 1093 | } else 1094 | pmsg [0] = '\0'; 1095 | 1096 | 1097 | start = msg; 1098 | while (1) { 1099 | next = strchr(start, '\n'); 1100 | if (next) *next = '\0'; 1101 | 1102 | if (! pfx) 1103 | sess__add_irc_out(s, 1104 | buff__sprintf(":%s PRIVMSG %s%s :%s\r\n", 1105 | srcname, pmsg, name, start)); 1106 | else 1107 | sess__add_irc_out(s, 1108 | buff__sprintf(":%s PRIVMSG %s%s :\x02%s\x0f %s\r\n", 1109 | srcname, pmsg, name, pfx, start)); 1110 | 1111 | if (! next || !(*(next+1))) break; 1112 | else start = next + 1; 1113 | } 1114 | 1115 | return r; 1116 | } 1117 | 1118 | int sess__cb_rc_getusers(t_sess * s, void *data, json_object * j) 1119 | { 1120 | t_rc_room *r = (t_rc_room *) data; 1121 | char buff[512], *b = buff; 1122 | 1123 | buff[0] = 0; 1124 | 1125 | json_object *records; 1126 | 1127 | if ((json_read(NULL, j, "{result:{records:%o}}", &records)) != 1) 1128 | return 0; 1129 | 1130 | int cnt_args = json_object_array_length(records); 1131 | for (int i = 0; i < cnt_args; i++) { 1132 | json_object *n, *p = json_object_array_get_idx(records, i); 1133 | 1134 | if (json_object_object_get_ex(p, "username", &n)) { 1135 | const char *st = json_object_get_string(n); 1136 | b = stpncpy(b, st, buff + sizeof(buff) - b); 1137 | b = stpncpy(b, " ", buff + sizeof(buff) - b); 1138 | } else n = NULL; 1139 | } 1140 | *b = 0; 1141 | sess__add_irc_out(s, buff__sprintf(":%s 353 %s = #%s :%s\r\n", 1142 | selfident, s->irc.nick, r->name, 1143 | buff)); 1144 | sess__add_irc_out(s, 1145 | buff__sprintf(":%s 366 %s #%s :End of NAMES list\r\n", 1146 | selfident, s->irc.nick, r->name)); 1147 | 1148 | return 0; 1149 | } 1150 | 1151 | int sess__cb_rc_joinroom(t_sess * s, void *data, json_object * j) 1152 | { 1153 | t_rc_room *r = (t_rc_room *) data; 1154 | 1155 | sess__add_irc_out(s, buff__sprintf(":%s JOIN #%s\r\n", 1156 | s->irc.nick, r->name)); 1157 | sess__add_irc_out(s, buff__sprintf(":%s 332 %s #%s :%s\r\n", 1158 | selfident, s->irc.nick, r->name, 1159 | r->topic ? r->topic : "")); 1160 | 1161 | sess__rc_command_call_(s, r, 1162 | json_create(NULL, 1163 | "{msg:%s method:%s params:[%s %b {limit:%d skip:%d} %s]}", 1164 | "method", "getUsersOfRoom", r->rid, 1165 | 0, 100, 0, ""), 1166 | sess__cb_rc_getusers); 1167 | 1168 | return 0; 1169 | } 1170 | 1171 | int sess__cb_rc_getroom(t_sess * s, void *data, json_object * j) 1172 | { 1173 | int r; 1174 | 1175 | char *_id = NULL, *t = NULL, *name = NULL, *fname = NULL, *topic = NULL; 1176 | 1177 | if ((r = 1178 | json_read(NULL, j, 1179 | "{result:{_id:%s name:%s fname:%s t:%s topic?%s}}", &_id, 1180 | &name, &fname, &t, &topic)) < 4) 1181 | goto err; 1182 | 1183 | t_rc_room *room = sess__rc_room_add(s, *t, name, _id, fname, topic); 1184 | 1185 | sess__rc_command_call_(s, room, 1186 | json_create(NULL, 1187 | "{msg:%s method:%s params:[%s %o]}", 1188 | "method", "joinRoom", _id, NULL), 1189 | sess__cb_rc_joinroom); 1190 | err: 1191 | IFFREE(_id); 1192 | IFFREE(t); 1193 | IFFREE(name); 1194 | IFFREE(fname); 1195 | IFFREE(topic); 1196 | return 0; 1197 | } 1198 | 1199 | int sess__rc_join_room(t_sess * s, char t, const char *name) 1200 | { 1201 | char tt[2]; 1202 | tt[0] = t; 1203 | tt[1] = '\0'; 1204 | 1205 | if (t == 'd') 1206 | sess__rc_command_call_(s, NULL, 1207 | json_create(NULL, 1208 | "{msg:%s method:%s params:[%s]}", 1209 | "method", 1210 | "createDirectMessage", name), 1211 | sess__cb_rc_createdirect); 1212 | else 1213 | sess__rc_command_call_(s, NULL, 1214 | json_create(NULL, 1215 | "{msg:%s method:%s params:[%s %s]}", 1216 | "method", 1217 | "getRoomByTypeAndName", tt, 1218 | (*name == 1219 | '#') ? (name + 1) : name), 1220 | sess__cb_rc_getroom); 1221 | return 0; 1222 | } 1223 | 1224 | int sess__rc_queue_message(t_sess * s, char t, const char *name, 1225 | const char *msgs) 1226 | { 1227 | t_rc_message *msg, **m; 1228 | 1229 | for (m = &s->rc.messages; *m; m = &(*m)->next) ; 1230 | 1231 | msg = NEW(t_rc_message); 1232 | msg->t = t; 1233 | msg->dstname = strdup(name); 1234 | msg->msg = strdup(msgs); 1235 | 1236 | *m = msg; 1237 | 1238 | return 0; 1239 | } 1240 | 1241 | static int rc_message_cmpid(const t_rc_message *a, const t_rc_message *b) 1242 | { 1243 | return strcmp(a->id, b->id); 1244 | } 1245 | 1246 | t_rc_message *sess__rc_add_message(t_sess *s, const char *id, const char *msg, 1247 | const char *sender, const char *reactions) 1248 | { 1249 | t_rc_message *m; 1250 | 1251 | m = NEW(t_rc_message); 1252 | 1253 | m->id = strdup(id); 1254 | m->msg = STRDUP(msg); 1255 | m->sender = STRDUP(sender); 1256 | m->reactions = STRDUP(reactions); 1257 | 1258 | m->next = s->rc.in_messages; 1259 | s->rc.in_messages = m; 1260 | tsearch((void*)m, (void**)&s->rc.in_messages_tree, 1261 | (int(*)(const void *, const void*))rc_message_cmpid); 1262 | 1263 | return m; 1264 | } 1265 | 1266 | t_rc_message *sess__rc_find_message(t_sess *s, const char *id) 1267 | { 1268 | 1269 | t_rc_message **m = NULL; 1270 | t_rc_message fm; 1271 | fm.id = id; 1272 | 1273 | m = (t_rc_message**)tfind((void*)&fm, (void**)&s->rc.in_messages_tree, 1274 | (int(*)(const void *, const void*))rc_message_cmpid); 1275 | 1276 | if (m) return (*m); 1277 | else return (NULL); 1278 | 1279 | } 1280 | 1281 | void rc_message__free(t_rc_message * m) 1282 | { 1283 | IFFREE(m->dstname); 1284 | IFFREE(m->reactions); 1285 | IFFREE(m->msg); 1286 | IFFREE(m); 1287 | } 1288 | 1289 | int sess__rc_queue_process(t_sess * s) 1290 | { 1291 | t_rc_message *msg, **m; 1292 | 1293 | for (m = &s->rc.messages; (msg = *m); ) { 1294 | if (!sess__rc_send_message(s, msg->t, msg->dstname, msg->msg)) { 1295 | *m = msg->next; 1296 | 1297 | rc_message__free(msg); 1298 | } else { 1299 | m = &msg->next; 1300 | } 1301 | } 1302 | return 0; 1303 | } 1304 | 1305 | int sess__rc_send_message(t_sess * s, char t, const char *name, const char *msg) 1306 | { 1307 | t_rc_room *room; 1308 | 1309 | room = sess__rc_room_by_name(s, t, name); 1310 | if (!room && t == 'c') 1311 | room = sess__rc_room_by_name(s, 'p', name); 1312 | if (!room) { 1313 | logg(ERR, "Room %c:%s NOT FOUND, not supported atm\n", t, name); 1314 | return (EROOMNOTFOUND); 1315 | } 1316 | 1317 | sess__rc_command_call_(s, NULL, 1318 | json_create(NULL, 1319 | "{msg:%s method:%s params:[{rid:%s msg:%s}]}", 1320 | "method", "sendMessage", room->rid, 1321 | msg), sess__cb_rc_sendmessage); 1322 | 1323 | return 0; 1324 | } 1325 | 1326 | int sess__rc_set_away(t_sess * s, const char *msg) 1327 | { 1328 | 1329 | if (!msg) { 1330 | logg(DBG3, "Setting back\n"); 1331 | sess__rc_set_back(s); 1332 | } 1333 | else { 1334 | logg(DBG3, "Setting away\n"); 1335 | sess__rc_command_call_(s, NULL, 1336 | json_create(NULL, 1337 | "{msg:%s method:%s params:[%s]}", 1338 | "method", 1339 | "setUserStatus", "away"), 1340 | sess__cb_rc_set_away); 1341 | } 1342 | return 0; 1343 | } 1344 | 1345 | int sess__rc_set_back(t_sess * s) 1346 | { 1347 | sess__rc_command_call_(s, NULL, 1348 | json_create(NULL, 1349 | "{msg:%s method:%s params:[%s]}", 1350 | "method", 1351 | "setUserStatus", "online"), 1352 | sess__cb_rc_set_back); 1353 | return 0; 1354 | } 1355 | 1356 | int sess__cb_rc_set_away(t_sess * s, void *data, json_object * j) 1357 | { 1358 | char buff[512]; 1359 | 1360 | sess__add_irc_out(s, buff__sprintf(":%s 306 :You have been marked as being away\r\n", 1361 | selfident, buff)); 1362 | return 0; 1363 | } 1364 | 1365 | int sess__cb_rc_set_back(t_sess * s, void *data, json_object * j) 1366 | { 1367 | char buff[512]; 1368 | 1369 | sess__add_irc_out(s, buff__sprintf(":%s 305 :You are no longer marked as being away\r\n", 1370 | selfident, buff)); 1371 | return 0; 1372 | } 1373 | 1374 | int sess__free(t_sess * s) 1375 | { 1376 | t_sess **d; 1377 | for (d = &session_list; *d && *d != s; d = &(*d)->next); 1378 | 1379 | *d = s->next; 1380 | 1381 | tdestroy((void*)s->rc.in_messages_tree, (void(*)(void*))rc_message__free); 1382 | 1383 | /* TODO: add pretty much all freeing !!! */ 1384 | free(s); 1385 | 1386 | return 0; 1387 | } 1388 | 1389 | int sess__close(t_sess * s) 1390 | { 1391 | logg(DBG1, "Closing session FDs %d,%d\n", s->poll->fd, 1392 | s->rc_poll?s->rc_poll->fd:-1); 1393 | 1394 | shutdown(s->poll->fd, SHUT_RDWR); 1395 | close(s->poll->fd); 1396 | s->rc.finished = 1; 1397 | if (s->rc_poll) { 1398 | shutdown(s->rc_poll->fd, SHUT_RDWR); 1399 | close(s->rc_poll->fd); 1400 | lws_service_fd(s->rc.context, s->rc_poll); 1401 | } 1402 | for (int i = 0; i <= maxfds; i++) { 1403 | if (&pollfds[i] == s->poll) { 1404 | poll_removefd(i); 1405 | break; 1406 | } 1407 | } 1408 | for (int i = 0; i <= maxfds; i++) { 1409 | if (&pollfds[i] == s->rc_poll) { 1410 | poll_removefd(i); 1411 | break; 1412 | } 1413 | } 1414 | 1415 | return 0; 1416 | } 1417 | 1418 | int irc__process(t_sess * s, struct lws_context *ctx) 1419 | { 1420 | int st = 0; 1421 | char *c, *end, *b; 1422 | char *command; 1423 | 1424 | for (c = s->irc_buff; c < s->irc_buff + s->irc_buff_head && st != 2; 1425 | c++) { 1426 | switch (*c) { 1427 | case '\r': 1428 | st = 1; 1429 | break; 1430 | case '\n': 1431 | // if (st == 1) 1432 | st = 2; 1433 | break; 1434 | default: 1435 | st = 0; 1436 | } 1437 | if (st == 2) 1438 | break; 1439 | } 1440 | 1441 | if (st != 2) 1442 | return 1; 1443 | 1444 | if (c > s->irc_buff && (*(c-1) == '\r')) *(c - 1) = '\0'; 1445 | else 1446 | *(c ) = '\0'; 1447 | end = c; 1448 | 1449 | logg(DBG2, "IRC received: %s\n", s->irc_buff); 1450 | 1451 | b = s->irc_buff; 1452 | c = strchr(b, ' '); 1453 | if (!c) { 1454 | logg(DBG3, "IRC command without parameter\n"); 1455 | goto skip_parsing; 1456 | } 1457 | *c = '\0'; 1458 | 1459 | if (*b == ':') { 1460 | 1461 | c = strchr(c + 1, ' '); 1462 | if (!c) { 1463 | logg(ERR, "Can\'t find #2 space\n"); 1464 | goto error_parsing; 1465 | } 1466 | b = c + 1; 1467 | *c = '\0'; 1468 | } else { 1469 | } 1470 | c += 1; 1471 | skip_parsing: 1472 | command = b; 1473 | 1474 | logg(ERR, "command \'%s\'\n", command); 1475 | if (c && !strcmp(command, "NICK")) { 1476 | if (!s->irc.nick || !strcmp(s->irc.nick, c)) { 1477 | sess__add_irc_out(s, 1478 | buff__sprintf 1479 | (":%s 001 %s :Welcome to the Internet Relay Network %s\r\n", 1480 | selfident, c, c)); 1481 | sess__add_irc_out(s, 1482 | buff__sprintf 1483 | 1484 | (":myserver 002 alnovak Your host is localhost, running version 1\r\n" 1485 | ":myserver 003 alnovak This server was created Tue Oct 22, 16:00:54 UTC\r\n" 1486 | ":myserver 004 alnovak localhost 1 oirw abeIiklmnopqstv\r\n" 1487 | ":%s 375 %s :%s message of the day\r\n" 1488 | ":%s 372 %s :RocketChat->IRC gateway!\r\n" 1489 | ":%s 376 %s :End of message of the day.\r\n", 1490 | selfident, c, selfident, 1491 | selfident, c, 1492 | selfident, c)); 1493 | 1494 | IFFREE(s->irc.nick); 1495 | s->irc.nick = strdup(c); 1496 | if (s->rc.token && !s->rc_poll) 1497 | sess__rc_start(s, ctx); 1498 | } 1499 | 1500 | } else if (!strcmp(command, "USER")) { 1501 | /* TODO */ 1502 | } else if (c && !strcmp(command, "PING")) { 1503 | sess__add_irc_out(s, buff__sprintf(":%s PONG %s :%s\r\n", selfident, selfident, c)); 1504 | } else if (c && (!strcmp(command, "PASS") || !strcmp(command, "IDENTIFY"))) { 1505 | IFFREE(s->rc.token); 1506 | if (*c == ':') c++; 1507 | s->rc.token = strdup(c); 1508 | if (s->irc.nick) 1509 | sess__rc_start(s, ctx); 1510 | } else if (c && !strcmp(command, "PRIVMSG")) { 1511 | char *msg = strchr(c, ' '); 1512 | char t; 1513 | logg(DBG3, "PRIVMSG->msg = %p,c = %s\n", msg, c); 1514 | if (!msg) 1515 | goto error_parsing; 1516 | *(msg++) = '\0'; 1517 | if (*msg == ':') 1518 | msg++; 1519 | 1520 | t = (*c == '#') ? 'c' : 'd'; 1521 | if (t == 'c') 1522 | c++; 1523 | 1524 | if (sess__rc_send_message(s, t, c, msg) == EROOMNOTFOUND) { 1525 | sess__rc_join_room(s, t, c); 1526 | sess__rc_queue_message(s, t, c, msg); 1527 | } 1528 | } else if (!strcmp(command, "WHO")) { 1529 | /* TODO */ 1530 | } else if (c && !strcmp(command, "JOIN")) { 1531 | sess__rc_join_room(s, 'c', c); 1532 | } else if (!strcmp(command, "AWAY")) { 1533 | sess__rc_set_away(s, c); 1534 | } else if (!strcmp(command, "QUERY")) { 1535 | sess__rc_join_room(s, 'd', c); 1536 | } else if (!strcmp(command, "QUIT")) { 1537 | shutdown(s->poll->fd, SHUT_RDWR); 1538 | if (s->rc_poll) 1539 | shutdown(s->rc_poll->fd, SHUT_RDWR); 1540 | } else { 1541 | logg(ERR, "Unrecognized command %s %s\n", command, c ? c : ""); 1542 | } 1543 | 1544 | error_parsing: 1545 | memcpy(s->irc_buff, end + 1, 1546 | s->irc_buff + s->irc_buff_head - (end + 1)); 1547 | s->irc_buff_head -= (end - s->irc_buff + 1); 1548 | 1549 | return 0; 1550 | } 1551 | static const char *doc_usage = 1552 | "Usage: %s [options] \n" 1553 | "Options:\n" 1554 | "\t-h\tshows this help\n" 1555 | "\t-d\tincreases logging verbosity, can be used repeatedly\n" 1556 | "\t-l\tIRC listen port (%d)\n" 1557 | "\t-p\tRC server port (%d)\n\n"; 1558 | 1559 | int main(int argc, char **argv) 1560 | { 1561 | struct lws_context_creation_info info; 1562 | struct lws_context *context; 1563 | int showhelp = 0; 1564 | int logmask = 1, c; 1565 | 1566 | int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; 1567 | 1568 | while ((c = getopt(argc, argv, "hdl:p:")) != -1) { 1569 | switch (c) { 1570 | case 'h': 1571 | showhelp = 1; 1572 | break; 1573 | case 'd': 1574 | logmask++; 1575 | break; 1576 | case 'l': 1577 | irc_mainport = atoi(optarg); 1578 | break; 1579 | case 'p': 1580 | rc_port = atoi(optarg); 1581 | break; 1582 | case '?': 1583 | logg(ERR, "Unknown argument '%c'\n", 1584 | optopt); 1585 | showhelp = 2; 1586 | break; 1587 | default: 1588 | logg(ERR, "getopt error (%d,%s)\n", 1589 | optopt, 1590 | optarg?optarg:""); 1591 | } 1592 | } 1593 | 1594 | if (argc < 2 || argc != optind + 1 || showhelp) { 1595 | fprintf(stderr, doc_usage, argv[0], irc_mainport, rc_port); 1596 | return 1; 1597 | } else 1598 | server_name = (char*)argv[optind]; 1599 | 1600 | logg_setmask((1<= 0 && !interrupted) { 1629 | int ret; 1630 | 1631 | ret = poll(pollfds, maxfds, 10000); 1632 | 1633 | if (ret > 0 && pollfds[0].revents == POLLIN) { 1634 | /* new connection on the IRC port */ 1635 | struct sockaddr_in cl_addr; 1636 | socklen_t cl_len = sizeof(cl_addr); 1637 | 1638 | int newfd = 1639 | accept(pollfds[0].fd, 1640 | (struct sockaddr *)&cl_addr, 1641 | (socklen_t *) & cl_len); 1642 | 1643 | logg(DBG1, "spawning new fd %d\n", newfd); 1644 | 1645 | pollfds[maxfds].fd = newfd; 1646 | pollfds[maxfds].events = POLLIN | POLLNVAL; 1647 | pollfds[maxfds].revents = 0; 1648 | 1649 | t_sess *sess = sess_new(); 1650 | sess->poll = &pollfds[maxfds]; 1651 | sess->irc_fd = newfd; 1652 | 1653 | sessions[maxfds] = sess; 1654 | 1655 | maxfds++; 1656 | 1657 | ret--; 1658 | } 1659 | 1660 | 1661 | for (int i = 0; i < maxfds && ret > 0; i++) { 1662 | t_sess *s = sessions[i]; 1663 | 1664 | if (pollfds[i].revents == 0) 1665 | continue; 1666 | ret--; 1667 | 1668 | if (s && pollfds[i].revents & (POLLNVAL | POLLERR)) { 1669 | sess__close(s); 1670 | sess__free(s); 1671 | continue; 1672 | } 1673 | 1674 | if (s && s->irc_fd == pollfds[i].fd) { 1675 | logg(DBG4, "gotcha [%d] %d -> %d\n", pollfds[i].fd, pollfds[i].events, pollfds[i].revents); 1676 | if (pollfds[i].revents & POLLIN) { 1677 | /* IRC RX */ 1678 | int r = 1679 | recv(pollfds[i].fd, 1680 | s->irc_buff + s->irc_buff_head, 1681 | sizeof(s->irc_buff) - 1682 | (unsigned long long)s-> 1683 | irc_buff_head, 0); 1684 | 1685 | logg(DBG4, "IRC-RX[%d]: %.*s\n", pollfds[i].fd, r, 1686 | s->irc_buff + s->irc_buff_head); 1687 | 1688 | if (r == 0 || pollfds[i].revents & POLLHUP) { 1689 | logg(DBG1,"Going to close %d+%d\n", pollfds[i].fd, pollfds[s->rc.fd_idx].fd); 1690 | shutdown(pollfds[i].fd, 1691 | SHUT_RDWR); 1692 | close(pollfds[i].fd); 1693 | s->rc.finished = 1; 1694 | if (s->rc_poll) 1695 | lws_service_fd(context, 1696 | s-> 1697 | rc_poll); 1698 | sess__close(s); 1699 | sess__free(s); 1700 | continue; 1701 | 1702 | } else if (r == -1) { 1703 | perror("what's the issue?"); 1704 | exit(1); 1705 | } 1706 | 1707 | s->irc_buff_head += r; 1708 | 1709 | while (!irc__process(s, context)) ; 1710 | 1711 | } else if (pollfds[i].revents & POLLOUT) { 1712 | if (!s->irc_out_buff) { 1713 | pollfds[i].events &= ~POLLOUT; 1714 | 1715 | if (s->state & STATE_SHUTTING_DOWN) { 1716 | if (s->poll) shutdown(s->poll->fd, SHUT_RDWR); 1717 | if (s->rc_poll) shutdown(s->rc_poll->fd, SHUT_RDWR); 1718 | } 1719 | } else { 1720 | int r = 1721 | send(pollfds[i].fd, 1722 | s->irc_out_buff->start, 1723 | s->irc_out_buff->left, 1724 | 0); 1725 | 1726 | if (r == -1) { 1727 | perror("Can't send\n"); 1728 | exit(1); 1729 | 1730 | } 1731 | logg(DBG4, "IRC-TX[%d]: %.*s\n", 1732 | pollfds[i].fd, 1733 | r, 1734 | s->irc_out_buff->start); 1735 | 1736 | s->irc_out_buff->start += r; 1737 | s->irc_out_buff->left -= r; 1738 | 1739 | if (!s->irc_out_buff->left) { 1740 | t_buff *b = 1741 | s->irc_out_buff-> 1742 | next; 1743 | buff__free(s-> 1744 | irc_out_buff); 1745 | s->irc_out_buff = b; 1746 | if (b == NULL) { 1747 | s->irc_out_buff_tail = &s->irc_out_buff; 1748 | } 1749 | } 1750 | 1751 | } 1752 | 1753 | } else { 1754 | logg(DBG1,"TODO[%d]: -> %x\n", pollfds[i].fd, pollfds[i].revents); 1755 | } 1756 | } else { 1757 | logg(DBG4, "gotcha-2 [%d] %d -> %d\n", pollfds[i].fd, pollfds[i].events, pollfds[i].revents); 1758 | int ret = lws_service_fd(context, &pollfds[i]); 1759 | if (ret) { 1760 | logg(ERR,"lws_service_fd(%d)... %d:\n", pollfds[i].fd, ret); 1761 | sessions[i]->rc.finished = 1; 1762 | sessions[i]->state |= STATE_SHUTTING_DOWN; 1763 | sess__add_irc_out(sessions[i], 1764 | buff__sprintf("RocketChat connection error (%d)\r\n", ret)); 1765 | 1766 | } 1767 | } 1768 | pollfds[i].revents = 0; 1769 | 1770 | } 1771 | 1772 | time_t now = time(NULL); 1773 | 1774 | for (t_sess *s = session_list; s; s = s->next) { 1775 | if (s->rc.last_ping && (now - s->rc.last_ping) > rc_timeout && !(s->state & STATE_SHUTTING_DOWN)) { 1776 | sess__add_irc_out(s, 1777 | buff__sprintf("RocketChat server didn't ping us in %d secs, closing\r\n", 1778 | now - s->rc.last_ping)); 1779 | s->state |= STATE_SHUTTING_DOWN; 1780 | } 1781 | } 1782 | } 1783 | 1784 | lws_context_destroy(context); 1785 | logg(DBG1, "Completed\n"); 1786 | 1787 | return 0; 1788 | } 1789 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "util.h" 5 | 6 | static int log_mask = ERR | DBG1 | DBG2 | DBG3 | DBG4; 7 | FILE *logout = NULL; 8 | 9 | void logg(short lvl, const char *fmt, ...) 10 | { 11 | time_t t; 12 | va_list args; 13 | struct tm *tm; 14 | 15 | if (!(lvl & log_mask)) 16 | return; 17 | 18 | if (!logout) 19 | logout = stdout; 20 | 21 | va_start(args, fmt); 22 | 23 | time(&t); 24 | tm = localtime(&t); 25 | 26 | fprintf(logout, "%02d-%02d-%02d %02d:%02d:%02d ", 27 | tm->tm_year % 100, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, 28 | tm->tm_min, tm->tm_sec); 29 | vfprintf(logout, fmt, args); 30 | va_end(args); 31 | fflush(logout); 32 | } 33 | 34 | void logg_setmask(int mask) 35 | { 36 | log_mask = mask; 37 | } 38 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #ifndef __util_h__ 2 | #define __util_h__ 3 | 4 | #define ERR (1<<0) 5 | #define DBG1 (1<<1) 6 | #define DBG2 (1<<2) 7 | #define DBG3 (1<<3) 8 | #define DBG4 (1<<4) 9 | 10 | void logg(short lvl, const char *fmt, ...); 11 | void logg_setmask(int mask); 12 | #endif 13 | --------------------------------------------------------------------------------