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 \n Version: %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 \n gem2irc -[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 ;
}