#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static HttpServer *server = NULL; static pthread_t xmpp_thr; static XMPPComponent *jabber = NULL; static volatile uint64_t start; uint64_t ParseeUptime(void) { return UtilTsMillis() - start; } static const Argument arguments[] = { #define Arg(c, req, vdesc, desc) \ { \ .end = false, \ .argument = c, .value_req = req, \ .value_descr = vdesc, \ .description = desc \ }, #define EndOfArgs { .end = true } Arg('H', true, "number(=8)", "Sets the number of HTTP threads") Arg('J', true, "number(=8)", "Sets the number of XMPP threads") Arg('C', true, "file(='parsee.json')", "Sets the JSON config to use") Arg('g', false, NULL, "Generates a parsee.yaml AS file before exiting") Arg('v', false, NULL, "Forces Parsee to print in a more verbose fashion " "(-vvv prints stanzas to stderr)") Arg('h', false, NULL, "Generates an help screen(this one!)") EndOfArgs #undef EndOfArgs #undef Argument }; void ParseeCheckMatrix(void *datp) { static volatile uint64_t streak = 0; ParseeData *data = datp; if (data->config->accept_pings && !ASPing(data->config)) { Log(LOG_ERR, "Cannot reach '%s' properly...", data->config->homeserver_host); if (++streak == 10) { DbRef *ref = DbLockIntent(data->db, DB_HINT_READONLY, 1, "chats"); HashMap *json = DbJson(ref); HashMap *mucs = GrabObject(json, 1, "mucs"); char *muc; void *ignored; /* Notify any potential MUCs about this */ while (HashMapIterate(mucs, &muc, &ignored)) { char *id = StrRandom(32); char *sender = StrConcat(3, "parsee@", data->jabber->host, "/parsee"); StanzaBuilder *b = CreateStanzaBuilder(sender, muc, id); SetStanzaType(b, "groupchat"); SetStanzaBody(b, "This bridge hasn't been able to reach the Matrix host, and " "as such, some messages may not have been sent over." ); WriteoutStanza(b, data->jabber, 0); DestroyStanzaBuilder(b); Free(sender); Free(id); } (void) ignored; DbUnlock(data->db, ref); } return; } streak = 0; } int Main(Array *args, HashMap *env) { HttpServerConfig conf; const ParseeConfig *parsee_conf; Stream *yaml; Cron *cron = NULL; char *configuration = "parsee.json"; int xmpp = 8; int http = 8; int verbose = 0; start = UtilTsMillis(); memset(&conf, 0, sizeof(conf)); Log(LOG_INFO, "%s - v%s[%s] (Cytoplasm %s)", NAME, VERSION, CODE, CytoplasmGetVersionStr() ); ParseePrintASCII(); Log(LOG_INFO, "======================="); Log(LOG_INFO, "(C)opyright 2024-2025 LDA and other contributors"); Log(LOG_INFO, "(This program is free software, see LICENSE.)"); #ifdef PLATFORM_IPHONE Log(LOG_WARNING, "Wait. Are you running this on an iPhone?"); Log(LOG_WARNING, "You *ought* to have spoofed this, haven't you?"); Log(LOG_WARNING, "Simply jealous of you for doing this."); #endif LogConfigIndent(LogConfigGlobal()); { ArgParseState state; char *opts = ParseeGenerateGetopt(arguments); int flag; ArgParseStateInit(&state); while ((flag = ArgParse(&state, args, opts)) != -1) { switch (flag) { case 'h': ParseeGenerateHelp(arguments); Free(opts); goto end; case 'H': http = strtol(state.optArg, NULL, 10); break; case 'J': xmpp = strtol(state.optArg, NULL, 10); break; case 'g': /* Write out the config file to a YAML document */ Log(LOG_INFO, "Generating YAML..."); yaml = StreamOpen("parsee.yaml", "w"); ParseeConfigLoad(configuration); ParseeConfigInit(); ParseeExportConfigYAML(yaml); StreamClose(yaml); Free(opts); goto end; case 'v': switch (++verbose) { case PARSEE_VERBOSE_LOG: LogConfigLevelSet(LogConfigGlobal(), LOG_DEBUG); break; case PARSEE_VERBOSE_TIMINGS: Log(LOG_DEBUG, "Logging bench information."); break; case PARSEE_VERBOSE_STANZA: Log(LOG_DEBUG, "Enabling stanza printing."); break; case PARSEE_VERBOSE_COMICAL: Log(LOG_DEBUG, "What?"); Log(LOG_DEBUG, "No, but like, what do you except?"); Log(LOG_DEBUG, "Like do you want to log _every_ instruction?"); Log(LOG_DEBUG, "Like just every single thing %s does?", NAME); Log(LOG_DEBUG, " ( why??? )"); Log(LOG_DEBUG, "....................................."); Log(LOG_DEBUG, "Argh."); Log(LOG_DEBUG, "Alright. I'll do my best."); Log(LOG_DEBUG, "Get what you paid for."); break; } break; case 'C': if (!UtilLastModified(state.optArg)) { Log(LOG_ERR, "Invalid config: %s", state.optArg); Log(LOG_ERR, "Ignoring."); break; } configuration = state.optArg; break; case '?': Log(LOG_ERR, "INVALID ARGUMENT GIVEN"); Free(opts); goto end; } } Free(opts); } if (verbose >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Loading configuration..."); } ParseeConfigLoad(configuration); ParseeConfigInit(); parsee_conf = ParseeConfigGet(); if (!parsee_conf) { goto end; } ParseeSetThreads(xmpp, http); Log(LOG_NOTICE, "Connecting to XMPP..."); jabber = XMPPInitialiseCompStream( parsee_conf->component_addr, parsee_conf->component_host, parsee_conf->component_port ); Log(LOG_NOTICE, "Connecting to XMPP... %p", jabber); if (!XMPPAuthenticateCompStream( jabber, parsee_conf->shared_comp_secret )) { Log(LOG_ERR, "Could not connect to XMPP..."); if (verbose >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Destroying component..."); } XMPPEndCompStream(jabber); goto end; } Log(LOG_NOTICE, "Creating volatile tables..."); if (verbose >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Initialising JID table"); } ParseeInitialiseJIDTable(); if (verbose >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Initialising OID table"); } ParseeInitialiseOIDTable(); if (verbose >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Initialising head table"); } ParseeInitialiseHeadTable(); if (verbose >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Initialising nick table"); } ParseeInitialiseNickTable(); if (verbose >= PARSEE_VERBOSE_COMICAL) { Log(LOG_DEBUG, "Initialising affiliation table"); } ParseeInitialiseAffiliationTable(); conf.port = parsee_conf->port; conf.threads = parsee_conf->http_threads; conf.maxConnections = conf.threads << 2; conf.handlerArgs = ParseeInitData(jabber); conf.handler = ParseeRequest; if (!conf.handlerArgs) { goto end; } Log(LOG_DEBUG, "Verbosity level: %d", verbose); ((ParseeData *) conf.handlerArgs)->verbosity = verbose; Log(LOG_NOTICE, "Setting up local Matrix user..."); if (ASRegisterUser(parsee_conf, parsee_conf->sender_localpart)) { char *parsee = ParseeMXID(conf.handlerArgs); ASSetAvatar(parsee_conf, parsee, "mxc://tedomum.net/" "7e228734ec8e792960bb5633e43f0cb845f709f61825130490034651136" ); ASSetName(parsee_conf, parsee, "Parsee bridge"); Free(parsee); } Log(LOG_NOTICE, "Starting up local cronjobs..."); cron = CronCreate(10 SECONDS); CronEvery(cron, 5 MINUTES, ParseeCleanup, conf.handlerArgs); CronEvery(cron, 10 SECONDS, ParseeCheckMatrix, conf.handlerArgs); ParseeCleanup(conf.handlerArgs); CronStart(cron); Log(LOG_NOTICE, "Creating XMPP listener thread..."); if (pthread_create(&xmpp_thr, NULL, ParseeXMPPThread, conf.handlerArgs)) { Log(LOG_ERR, "Couldn't start XMPP listener thread."); goto end; } server = HttpServerCreate(&conf); ((ParseeData *) conf.handlerArgs)->server = server; if (!ParseeInitialiseSignals(conf.handlerArgs, xmpp_thr)) { goto end; } HttpServerStart(server); Log(LOG_NOTICE, "Listening to MUCs..."); LogConfigIndent(LogConfigGlobal()); ParseeSendPresence(conf.handlerArgs); LogConfigUnindent(LogConfigGlobal()); LogConfigUnindent(LogConfigGlobal()); Log(LOG_INFO, "======================="); HttpServerJoin(server); end: Log(LOG_INFO, "Exiting..."); HttpServerFree(server); ParseeConfigFree(); CronStop(cron); CronFree(cron); ParseeFreeData(conf.handlerArgs); ParseeDestroyAffiliationTable(); ParseeDestroyNickTable(); ParseeDestroyOIDTable(); ParseeDestroyHeadTable(); ParseeDestroyJIDTable(); (void) env; return 0; }