#define VERSION "1.0" #include #include #include #include #include #include #include #include #include #include #include #include #define ENABLE_SSL #include "irc.h" #define bool char typedef struct { /* config */ int port; char *ip; char *nick; bool use_ssl; bool debug; /* Irc socket */ IRCC_client client; } CFG; static CFG cfg = {.nick = "gem2irc", .ip = "127.0.0.1", .port = 6667}; typedef struct { SSL_CTX *ctx; SSL *ssl; } GEM_CFG; static GEM_CFG gem_cfg; int irc_client(const CFG cfg, IRCC_client *client, char **channels, const int channels_size, char **error_msg) { if (IRCC_connect(client, cfg.ip, cfg.port) == IRCC_ERROR) { *error_msg = strerror(errno); return 1; } if (cfg.use_ssl) { if (IRCC_initssl(client) == IRCC_ERROR) { *error_msg = ERR_error_string(ERR_get_error(), NULL); return 1; } } int status = IRCC_register(client, cfg.nick); if (status == IRCC_ERROR || status == IRCC_DISCONNECTED) { *error_msg = "disconnected"; return 1; } for (int i = 0; i < channels_size; i++) { char *key = ""; const char *channel = channels[i]; char *p = strchr(channel, ':'); if (p != NULL) { p[0] = '\0'; key = p + 1; } IRCC_join(client, channel, key); } return 0; } void run_gemini(IRCC_client *client) { char *uri = client->irc_msg + 4; if (strlen(uri) <= 1) return; char *nick = strdup(client->irc_nick); if (nick == NULL) return; int ret = 1; /* Connect to the gemini server */ int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) goto RG_CLOSE; /* Get domainname from uri */ char domain[1024]; snprintf(domain, sizeof(domain), "%.*s", (int)strcspn(uri, ":/\0"), uri); struct hostent *he; if ((he = gethostbyname(domain)) == 0) goto RG_CLOSE_SOCK; /* Connect */ struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(1965); addr.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr *)he->h_addr)); if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) goto RG_CLOSE_SOCK; /* TLS */ if ((gem_cfg.ssl = SSL_new(gem_cfg.ctx)) == 0) goto RG_CLOSE_SOCK; if (!SSL_set_tlsext_host_name(gem_cfg.ssl, domain)) goto RG_CLOSE_TLS; if (!SSL_set_fd(gem_cfg.ssl, sock)) goto RG_CLOSE_TLS; if (SSL_connect(gem_cfg.ssl) < 1) goto RG_CLOSE_TLS; /* Send request */ ssize_t size = snprintf(domain, sizeof(domain), "gemini://%s\r\n", uri); if (SSL_write(gem_cfg.ssl, domain, size) < 1) goto RG_CLOSE_TLS; /* Send response to the irc user */ char buf[256]; char *p = buf; while ((size = SSL_read(gem_cfg.ssl, p, 1)) > 0) { if (*p == '\n') { *p = '\0'; p = buf; IRCC_send(client, nick, buf); } else p++; } ret = 0; RG_CLOSE_TLS: SSL_free(gem_cfg.ssl); gem_cfg.ssl = NULL; RG_CLOSE_SOCK: close(sock); RG_CLOSE: free(nick); if (ret) IRCC_send(client, nick, "Internal error. Maybe invaild uri?"); } void sig_handler(int sig) { IRCC_close(&cfg.client); /* Close ssl for all forked processes */ if (gem_cfg.ssl) { SSL_shutdown(gem_cfg.ssl); SSL_free(gem_cfg.ssl); } if (gem_cfg.ctx) SSL_CTX_free(gem_cfg.ctx); if (sig == -1) exit(1); fprintf(stderr, "gem2irc: received signal %d\n", sig); exit(0); } int main(int argc, char **argv) { signal(SIGINT, sig_handler); signal(SIGKILL, sig_handler); signal(SIGTERM, sig_handler); signal(SIGCHLD, SIG_IGN); int opt; while ((opt = getopt(argc, argv, "n:p:h:tVD")) != -1) { switch (opt) { case 'n': cfg.nick = optarg; break; case 'h': cfg.ip = optarg; break; case 'p': cfg.port = atoi(optarg); break; case 't': cfg.use_ssl = 1; break; case 'V': printf("License: WTFPL\nVersion: %s\n", VERSION); return 0; case 'D': cfg.debug = 1; break; default: printf("gem2irc - simple bridge between irc and gemini\ngem2irc -[n:p:h:tVD] [e.p CHANNEL1:PASSWORD CHANNEL2...]\n\t-n NICK default: %s\n\t-h HOST default: %s\n\t-p PORT default: %d\n\t-t Use ssl default: %s\n\t-D Print raw messages\n\t-V Version\n", cfg.nick, cfg.ip, cfg.port, (cfg.use_ssl) ? "true" : "false"); return 0; } } argv += optind; argc -= optind; /* TLS FOR GEMINI */ SSL_library_init(); gem_cfg.ctx = SSL_CTX_new(TLS_client_method()); if (!gem_cfg.ctx) { fprintf(stderr, "gem2irc: ssl: %s\n", ERR_error_string(ERR_get_error(), NULL)); return 1; } /* IRC */ char *error_msg = NULL; if (irc_client(cfg, &cfg.client, argv, argc, &error_msg)) { fprintf(stderr, "gem2irc: irc: %s\n", (error_msg) ? error_msg : "error"); sig_handler(-1); } while (1) { int status = IRCC_recv(&cfg.client); if (status == IRCC_DISCONNECTED) { fprintf(stderr, "gem2irc: irc: disconnected\n"); sig_handler(-1); } if (cfg.debug) printf("\033[32m%s\033[0m", cfg.client.irc_raw); status = IRCC_parse(&cfg.client); if (status == IRCC_PRIVMSG && cfg.client.irc_nick != NULL) if (!strncmp(cfg.client.irc_msg, "!go ", 4)) if (fork() == 0) { run_gemini(&cfg.client); exit(0); } } return 0; }