gem2irc/main.c

251 lines
5.1 KiB
C
Raw Permalink Normal View History

2024-08-12 12:14:11 +00:00
#define VERSION "1.0"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#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;
2024-08-14 14:10:45 +00:00
/* Send request */
2024-08-12 12:14:11 +00:00
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;
2024-08-14 14:10:45 +00:00
/* Send response to the irc user */
char buf[256];
char *p = buf;
2024-08-12 12:14:11 +00:00
2024-08-14 14:10:45 +00:00
while ((size = SSL_read(gem_cfg.ssl, p, 1)) > 0) {
if (*p == '\n') {
*p = '\0';
p = buf;
IRCC_send(client, nick, buf);
2024-08-12 12:14:11 +00:00
}
2024-08-14 14:10:45 +00:00
else
p++;
2024-08-12 12:14:11 +00:00
}
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:
2024-08-14 14:10:45 +00:00
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");
2024-08-12 12:14:11 +00:00
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;
}