1
0
mirror of https://github.com/irssi/irssi.git synced 2024-06-02 06:11:11 +00:00

Compare commits

...

18 Commits

Author SHA1 Message Date
Gunter Labes
8c5b624ab6
Merge b19d6a31b2 into 7b56ffdc2e 2024-04-01 22:36:10 +02:00
ailin-nemui
7b56ffdc2e
Merge pull request #1518 from ailin-nemui/dylib
fix deprecation in new glib module_open
2024-04-01 20:35:05 +00:00
ailin-nemui
bb4ed2b14f
Merge pull request #1521 from ailin-nemui/nodejs
github actions nodejs churn
2024-04-01 20:33:21 +00:00
Ailin Nemui
111e9160a9 github actions nodejs churn 2024-04-01 22:28:17 +02:00
ailin-nemui
d30653f7f9
Merge pull request #1520 from irssi/readme-perl-ver
Update minimum required Perl version in readme
2024-04-01 20:11:15 +00:00
ailin-nemui
c48354307e
Update minimum required Perl version in readme 2024-04-01 22:10:35 +02:00
Ailin Nemui
e13df83dc8 new code for g_module_open which might work with apple dylibs given new enough glib 2024-04-01 17:38:30 +02:00
ailin-nemui
b18832bf3b
Merge pull request #1500 from patrick-irc/scram
Added support for SCRAM-SHA-1, SCRAM-SHA-256 and SCRAM-SHA-512
2024-04-01 15:17:16 +00:00
Ailin Nemui
2f2fa029f9 up abi 2024-04-01 16:37:35 +02:00
Ailin Nemui
08bb648850 proper sasl mechanism variable initialisation 2024-04-01 16:33:57 +02:00
Ailin Nemui
98b391f62e minor cleanup 2024-04-01 16:33:36 +02:00
Patrick Okraku
f2b97631e1 Added support for SCRAM-SHA-1, SCRAM-SHA-256 and SCRAM-SHA-512 2024-04-01 15:12:01 +02:00
ailin-nemui
91dac0e5a1
Merge pull request #1512 from ailin-nemui/shquote
missing shell quotes
2024-03-31 16:53:57 +00:00
ailin-nemui
aebc0667a6
Merge pull request #1515 from maflcko/patch-1
Update server.c: Add missing include
2024-03-31 16:53:44 +00:00
maflcko
cf6615a70e
Update server.c: Add missing include 2024-03-28 19:33:24 +01:00
Ailin Nemui
4dd57cf24e missing shell quotes 2024-02-23 17:04:41 +01:00
Gunter Labes
b19d6a31b2
Update ignore examples 2023-08-21 13:06:04 +02:00
Gunter Labes
0ac0f5dbe3
Correct message level documentation 2023-08-21 12:01:26 +02:00
20 changed files with 506 additions and 35 deletions

View File

@ -37,7 +37,7 @@ jobs:
echo base abi : $base_abi echo base abi : $base_abi
./base$prefix/bin/irssi --version ./base$prefix/bin/irssi --version
echo base_abi=$base_abi >> $GITHUB_OUTPUT echo base_abi=$base_abi >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: base.inst name: base.inst
path: base path: base
@ -71,7 +71,7 @@ jobs:
echo merge abi : $merge_abi echo merge abi : $merge_abi
./merge$prefix/bin/irssi --version ./merge$prefix/bin/irssi --version
echo merge_abi=$merge_abi >> $GITHUB_OUTPUT echo merge_abi=$merge_abi >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: merge.inst name: merge.inst
path: merge path: merge
@ -89,12 +89,12 @@ jobs:
run: | run: |
sudo apt install abigail-tools sudo apt install abigail-tools
- name: fetch base build - name: fetch base build
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: base.inst name: base.inst
path: base path: base
- name: fetch merge build - name: fetch merge build
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
with: with:
name: merge.inst name: merge.inst
path: merge path: merge
@ -103,7 +103,7 @@ jobs:
abipkgdiff -l base merge >abipkgdiff.out && diff_ret=0 || diff_ret=$? abipkgdiff -l base merge >abipkgdiff.out && diff_ret=0 || diff_ret=$?
echo "diff_ret=$diff_ret" >> $GITHUB_ENV echo "diff_ret=$diff_ret" >> $GITHUB_ENV
cat abipkgdiff.out cat abipkgdiff.out
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
path: abipkgdiff.out path: abipkgdiff.out
- run: | - run: |

View File

@ -21,7 +21,7 @@ jobs:
- name: make dist - name: make dist
run: | run: |
./utils/make-dist.sh ./utils/make-dist.sh
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
path: irssi-*.tar.gz path: irssi-*.tar.gz
retention-days: 1 retention-days: 1
@ -51,7 +51,7 @@ jobs:
flags: meson-latest FAILURE-OK flags: meson-latest FAILURE-OK
steps: steps:
- name: fetch dist - name: fetch dist
uses: actions/download-artifact@v3 uses: actions/download-artifact@v4
- name: set PATH - name: set PATH
run: | run: |
echo "$HOME/.local/bin" >> $GITHUB_PATH echo "$HOME/.local/bin" >> $GITHUB_PATH

View File

@ -73,7 +73,7 @@ jobs:
- name: build irssi package - name: build irssi package
run: | run: |
sudo ./scripts/run-docker.sh ./build-package.sh -I irssi-an sudo ./scripts/run-docker.sh ./build-package.sh -I irssi-an
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v4
with: with:
name: irssi-termux-pkg name: irssi-termux-pkg
path: output/irssi-an*.deb path: output/irssi-an*.deb

View File

@ -41,7 +41,7 @@ ninja -C Build && sudo ninja -C Build install
- [glib-2.32](https://wiki.gnome.org/Projects/GLib) or greater - [glib-2.32](https://wiki.gnome.org/Projects/GLib) or greater
- [openssl](https://www.openssl.org/) - [openssl](https://www.openssl.org/)
- [perl-5.6](https://www.perl.org/) or greater (for perl support) - [perl-5.8](https://www.perl.org/) or greater (for perl support)
- terminfo or ncurses (for text frontend) - terminfo or ncurses (for text frontend)
#### See the [INSTALL](INSTALL) file for details #### See the [INSTALL](INSTALL) file for details

View File

@ -38,7 +38,7 @@
/IGNORE * JOINS /IGNORE * JOINS
/IGNORE * CTCPS /IGNORE * CTCPS
/IGNORE -except *!*@*.irssi.org CTCPS /IGNORE -except *!*@*.irssi.org CTCPS
/IGNORE #irssi ALL -PUBLIC -ACTIONS /IGNORE #irssi ALL -PUBLICS -ACTIONS
/IGNORE -replies *!*@*.irssi.org ALL /IGNORE -replies *!*@*.irssi.org ALL
/IGNORE -regexp -pattern (away|gone|back|playing|returned) * ACTIONS /IGNORE -regexp -pattern (away|gone|back|playing|returned) * ACTIONS
/IGNORE -regexp -pattern (away|gone|back|playing|returned) #channel ACTIONS /IGNORE -regexp -pattern (away|gone|back|playing|returned) #channel ACTIONS
@ -47,7 +47,7 @@
/IGNORE *away* NICKS /IGNORE *away* NICKS
/IGNORE #irssi NO_ACT JOINS PARTS QUITS /IGNORE #irssi NO_ACT JOINS PARTS QUITS
/IGNORE mike NO_ACT -MSGS /IGNORE mike NO_ACT -MSGS
/IGNORE mike HIDDEN PUBLIC JOINS PARTS QUITS /IGNORE mike HIDDEN PUBLICS JOINS PARTS QUITS
/IGNORE -time 5days christmas PUBLICS /IGNORE -time 5days christmas PUBLICS
/IGNORE -time 300 mike PUBLICS /IGNORE -time 300 mike PUBLICS

View File

@ -22,7 +22,7 @@
NICKS A nickname changes to another nickname. NICKS A nickname changes to another nickname.
NOTICES Private notices. NOTICES Private notices.
PARTS A nickname leaves a channel. PARTS A nickname leaves a channel.
PUBLIC Public messages in a channel. PUBLICS Public messages in a channel.
PUBNOTICES Public notices in a channel. PUBNOTICES Public notices in a channel.
QUITS A nickname disconnects from IRC. QUITS A nickname disconnects from IRC.
SNOTES Notices sent from a server. SNOTES Notices sent from a server.
@ -31,7 +31,7 @@
These are the special levels you can use: These are the special levels you can use:
HILIGHT The text is highlighted. HILIGHTS The text is highlighted.
NEVER Never ignores or logs the message. NEVER Never ignores or logs the message.
HIDDEN Hides the message when window HIDELEVEL includes HIDDEN. HIDDEN Hides the message when window HIDELEVEL includes HIDDEN.
NO_ACT Doesn't trigger any activity in the statusbar. NO_ACT Doesn't trigger any activity in the statusbar.

View File

@ -36,8 +36,8 @@
-cmdmax: Specifies the maximum number of commands to perform before -cmdmax: Specifies the maximum number of commands to perform before
starting the internal flood protection. starting the internal flood protection.
-sasl_mechanism Specifies the mechanism to use for the SASL authentication. -sasl_mechanism Specifies the mechanism to use for the SASL authentication.
At the moment irssi only supports the 'plain' and the Irssi supports: PLAIN, EXTERNAL, SCRAM-SHA-1, SCRAM-SHA-256
'external' mechanisms. and SCRAM-SHA-512
Use '' to disable the authentication. Use '' to disable the authentication.
-sasl_username Specifies the username to use during the SASL authentication. -sasl_username Specifies the username to use during the SASL authentication.
-sasl_password Specifies the password to use during the SASL authentication. -sasl_password Specifies the password to use during the SASL authentication.

View File

@ -6,7 +6,7 @@
#define IRSSI_GLOBAL_CONFIG "irssi.conf" /* config file name in /etc/ */ #define IRSSI_GLOBAL_CONFIG "irssi.conf" /* config file name in /etc/ */
#define IRSSI_HOME_CONFIG "config" /* config file name in ~/.irssi/ */ #define IRSSI_HOME_CONFIG "config" /* config file name in ~/.irssi/ */
#define IRSSI_ABI_VERSION 52 #define IRSSI_ABI_VERSION 53
#define DEFAULT_SERVER_ADD_PORT 6667 #define DEFAULT_SERVER_ADD_PORT 6667
#define DEFAULT_SERVER_ADD_TLS_PORT 6697 #define DEFAULT_SERVER_ADD_TLS_PORT 6697

View File

@ -102,10 +102,41 @@ static char *module_get_sub(const char *name, const char *root)
return g_strdup(name); return g_strdup(name);
} }
static GModule *module_open(const char *name, int *found) static GModule *module_open(const char *name)
{ {
struct stat statbuf;
GModule *module; GModule *module;
#if GLIB_CHECK_VERSION(2, 75, 0)
/* in this version of glib, g_module_open knows how to construct system-dependent module
file names, and g_module_build_path is deprecated. */
char *path;
if (g_path_is_absolute(name) || *name == '~' ||
(*name == '.' && name[1] == G_DIR_SEPARATOR))
path = g_strdup(name);
else {
/* first try from home dir */
path = g_strdup_printf("%s/modules/%s", get_irssi_dir(), name);
module = g_module_open(path, (GModuleFlags) 0);
g_free(path);
if (module != NULL) {
return module;
}
/* module not found from home dir, try global module dir */
path = g_strdup_printf("%s/%s", MODULEDIR, name);
}
module = g_module_open(path, (GModuleFlags) 0);
g_free(path);
return module;
#else /* GLib < 2.75.0 */
/* in this version of glib, we build the module path with g_module_build_path.
unfortunately, this is broken on Darwin when compiled with meson. */
struct stat statbuf;
char *path, *str; char *path, *str;
if (g_path_is_absolute(name) || *name == '~' || if (g_path_is_absolute(name) || *name == '~' ||
@ -120,7 +151,6 @@ static GModule *module_open(const char *name, int *found)
if (stat(path, &statbuf) == 0) { if (stat(path, &statbuf) == 0) {
module = g_module_open(path, (GModuleFlags) 0); module = g_module_open(path, (GModuleFlags) 0);
g_free(path); g_free(path);
*found = TRUE;
return module; return module;
} }
@ -129,10 +159,11 @@ static GModule *module_open(const char *name, int *found)
path = g_module_build_path(MODULEDIR, name); path = g_module_build_path(MODULEDIR, name);
} }
*found = stat(path, &statbuf) == 0;
module = g_module_open(path, (GModuleFlags) 0); module = g_module_open(path, (GModuleFlags) 0);
g_free(path); g_free(path);
return module; return module;
#endif
} }
static char *module_get_func(const char *rootmodule, const char *submodule, static char *module_get_func(const char *rootmodule, const char *submodule,
@ -151,8 +182,7 @@ static char *module_get_func(const char *rootmodule, const char *submodule,
signal_emit("module error", 4, GINT_TO_POINTER(error), text, \ signal_emit("module error", 4, GINT_TO_POINTER(error), text, \
rootmodule, submodule) rootmodule, submodule)
/* Returns 1 if ok, 0 if error in module and /* Returns 1 if ok, 0 if not */
-1 if module wasn't found */
static int module_load_name(const char *path, const char *rootmodule, static int module_load_name(const char *path, const char *rootmodule,
const char *submodule, int silent) const char *submodule, int silent)
{ {
@ -166,15 +196,15 @@ static int module_load_name(const char *path, const char *rootmodule,
gpointer value1, value2 = NULL; gpointer value1, value2 = NULL;
char *versionfunc, *initfunc, *deinitfunc; char *versionfunc, *initfunc, *deinitfunc;
int module_abi_version = 0; int module_abi_version = 0;
int found; int valid;
gmodule = module_open(path, &found); gmodule = module_open(path);
if (gmodule == NULL) { if (gmodule == NULL) {
if (!silent || found) { if (!silent) {
module_error(MODULE_ERROR_LOAD, g_module_error(), module_error(MODULE_ERROR_LOAD, g_module_error(),
rootmodule, submodule); rootmodule, submodule);
} }
return found ? 0 : -1; return 0;
} }
/* get the module's irssi abi version and bail out on mismatch */ /* get the module's irssi abi version and bail out on mismatch */
@ -201,12 +231,12 @@ static int module_load_name(const char *path, const char *rootmodule,
/* get the module's init() and deinit() functions */ /* get the module's init() and deinit() functions */
initfunc = module_get_func(rootmodule, submodule, "init"); initfunc = module_get_func(rootmodule, submodule, "init");
deinitfunc = module_get_func(rootmodule, submodule, "deinit"); deinitfunc = module_get_func(rootmodule, submodule, "deinit");
found = g_module_symbol(gmodule, initfunc, &value1) && valid = g_module_symbol(gmodule, initfunc, &value1) &&
g_module_symbol(gmodule, deinitfunc, &value2); g_module_symbol(gmodule, deinitfunc, &value2);
g_free(initfunc); g_free(initfunc);
g_free(deinitfunc); g_free(deinitfunc);
if (!found) { if (!valid) {
module_error(MODULE_ERROR_INVALID, NULL, module_error(MODULE_ERROR_INVALID, NULL,
rootmodule, submodule); rootmodule, submodule);
g_module_close(gmodule); g_module_close(gmodule);
@ -310,7 +340,7 @@ static int module_load_full(const char *path, const char *rootmodule,
/* check if the given module exists.. */ /* check if the given module exists.. */
try_prefixes = g_strcmp0(rootmodule, submodule) == 0; try_prefixes = g_strcmp0(rootmodule, submodule) == 0;
status = module_load_name(path, rootmodule, submodule, try_prefixes); status = module_load_name(path, rootmodule, submodule, try_prefixes);
if (status == -1 && try_prefixes) { if (status <= 0 && try_prefixes) {
/* nope, try loading the module_core, /* nope, try loading the module_core,
fe_module, etc. */ fe_module, etc. */
status = module_load_prefixes(path, rootmodule, status = module_load_prefixes(path, rootmodule,

View File

@ -30,6 +30,7 @@
#include <irssi/src/core/misc.h> #include <irssi/src/core/misc.h>
#include <irssi/src/core/servers-setup.h> #include <irssi/src/core/servers-setup.h>
#include <irssi/src/core/rawlog.h> #include <irssi/src/core/rawlog.h>
#include <irssi/src/core/network-openssl.h>
#include <irssi/src/core/net-sendbuffer.h> #include <irssi/src/core/net-sendbuffer.h>
#include <stddef.h> #include <stddef.h>

View File

@ -77,6 +77,10 @@ static void destroy_server_connect(SERVER_CONNECT_REC *conn)
g_free_not_null(ircconn->alternate_nick); g_free_not_null(ircconn->alternate_nick);
g_free_not_null(ircconn->sasl_username); g_free_not_null(ircconn->sasl_username);
g_free_not_null(ircconn->sasl_password); g_free_not_null(ircconn->sasl_password);
if (ircconn->scram_session != NULL) {
scram_session_free(ircconn->scram_session);
}
} }
void irc_core_init(void) void irc_core_init(void)

View File

@ -128,12 +128,31 @@ static void sig_server_setup_fill_chatnet(IRC_SERVER_CONNECT_REC *conn,
conn->sasl_password = g_strdup(ircnet->sasl_password); conn->sasl_password = g_strdup(ircnet->sasl_password);
} else } else
g_warning("The fields sasl_username and sasl_password are either missing or empty"); g_warning("The fields sasl_username and sasl_password are either missing or empty");
} } else if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "SCRAM-SHA-1") ||
else if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "external")) { !g_ascii_strcasecmp(ircnet->sasl_mechanism, "SCRAM-SHA-256") ||
!g_ascii_strcasecmp(ircnet->sasl_mechanism, "SCRAM-SHA-512")) {
/* The SCRAM-SHA-* methods need both the username and the password */
if (ircnet->sasl_username != NULL && *ircnet->sasl_username &&
ircnet->sasl_password != NULL && *ircnet->sasl_password) {
if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "SCRAM-SHA-1"))
conn->sasl_mechanism = SASL_MECHANISM_SCRAM_SHA_1;
if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "SCRAM-SHA-256"))
conn->sasl_mechanism = SASL_MECHANISM_SCRAM_SHA_256;
if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "SCRAM-SHA-512"))
conn->sasl_mechanism = SASL_MECHANISM_SCRAM_SHA_512;
conn->sasl_username = g_strdup(ircnet->sasl_username);
conn->sasl_password = g_strdup(ircnet->sasl_password);
} else
g_warning("The fields sasl_username and sasl_password are either "
"missing or empty");
} else if (!g_ascii_strcasecmp(ircnet->sasl_mechanism, "external")) {
conn->sasl_mechanism = SASL_MECHANISM_EXTERNAL; conn->sasl_mechanism = SASL_MECHANISM_EXTERNAL;
} else {
g_warning("Unsupported SASL mechanism \"%s\" selected",
ircnet->sasl_mechanism);
conn->sasl_mechanism = SASL_MECHANISM_MAX;
} }
else
g_warning("Unsupported SASL mechanism \"%s\" selected", ircnet->sasl_mechanism);
} }
} }

View File

@ -475,6 +475,7 @@ SERVER_REC *irc_server_init_connect(SERVER_CONNECT_REC *conn)
server->send_message = send_message; server->send_message = send_message;
server->query_find_func = (QUERY_REC * (*) (SERVER_REC *, const char *) ) irc_query_find; server->query_find_func = (QUERY_REC * (*) (SERVER_REC *, const char *) ) irc_query_find;
server->nick_comp_func = irc_nickcmp_rfc1459; server->nick_comp_func = irc_nickcmp_rfc1459;
server->sasl_success = FALSE;
server_connect_init((SERVER_REC *) server); server_connect_init((SERVER_REC *) server);
return (SERVER_REC *) server; return (SERVER_REC *) server;

View File

@ -4,6 +4,7 @@
#include <irssi/src/core/chat-protocols.h> #include <irssi/src/core/chat-protocols.h>
#include <irssi/src/core/servers.h> #include <irssi/src/core/servers.h>
#include <irssi/src/irc/core/modes.h> #include <irssi/src/irc/core/modes.h>
#include <irssi/src/irc/core/scram.h>
/* /*
* 63 is the maximum hostname length defined by the protocol. 10 is a common * 63 is the maximum hostname length defined by the protocol. 10 is a common
@ -54,6 +55,7 @@ struct _IRC_SERVER_CONNECT_REC {
int sasl_mechanism; int sasl_mechanism;
char *sasl_username; char *sasl_username;
char *sasl_password; char *sasl_password;
SCRAM_SESSION_REC *scram_session;
int max_cmds_at_once; int max_cmds_at_once;
int cmd_queue_speed; int cmd_queue_speed;

View File

@ -28,6 +28,7 @@ libirc_core_a = static_library('irc_core',
'modes.c', 'modes.c',
'netsplit.c', 'netsplit.c',
'sasl.c', 'sasl.c',
'scram.c',
'servers-idle.c', 'servers-idle.c',
'servers-redirect.c', 'servers-redirect.c',
), ),
@ -70,6 +71,7 @@ install_headers(
'module.h', 'module.h',
'netsplit.h', 'netsplit.h',
'sasl.h', 'sasl.h',
'scram.h',
'servers-idle.h', 'servers-idle.h',
'servers-redirect.h', 'servers-redirect.h',
), ),

View File

@ -80,6 +80,24 @@ static void sasl_start(IRC_SERVER_REC *server, const char *data, const char *fro
case SASL_MECHANISM_EXTERNAL: case SASL_MECHANISM_EXTERNAL:
irc_send_cmd_now(server, "AUTHENTICATE EXTERNAL"); irc_send_cmd_now(server, "AUTHENTICATE EXTERNAL");
break; break;
case SASL_MECHANISM_SCRAM_SHA_1:
irc_send_cmd_now(server, "AUTHENTICATE SCRAM-SHA-1");
break;
case SASL_MECHANISM_SCRAM_SHA_256:
irc_send_cmd_now(server, "AUTHENTICATE SCRAM-SHA-256");
break;
case SASL_MECHANISM_SCRAM_SHA_512:
irc_send_cmd_now(server, "AUTHENTICATE SCRAM-SHA-512");
break;
case SASL_MECHANISM_MAX:
signal_emit("server sasl failure", 2, server,
"Irssi: Unsupported SASL mechanism");
irc_cap_finish_negotiation(server);
return;
} }
server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server); server->sasl_timeout = g_timeout_add(SASL_TIMEOUT, (GSourceFunc) sasl_timeout, server);
} }
@ -223,6 +241,54 @@ void sasl_send_response(IRC_SERVER_REC *server, GString *response)
g_free(enc); g_free(enc);
} }
/*
* Sends AUTHENTICATE messages to log in via SCRAM.
*/
static void scram_authenticate(IRC_SERVER_REC *server, const char *data, const char *digest)
{
char *output;
int ret;
size_t output_len;
IRC_SERVER_CONNECT_REC *conn = server->connrec;
if (conn->scram_session == NULL) {
conn->scram_session =
scram_session_create(digest, conn->sasl_username, conn->sasl_password);
if (conn->scram_session == NULL) {
g_critical("Could not create SCRAM session with digest %s", digest);
irc_send_cmd_now(server, "AUTHENTICATE *");
return;
}
}
ret = scram_process(conn->scram_session, data, &output, &output_len);
if (ret == SCRAM_IN_PROGRESS) {
// Authentication is still in progress
GString *resp = g_string_new_len(output, output_len);
sasl_send_response(server, resp);
g_string_free(resp, TRUE);
g_free(output);
} else if (ret == SCRAM_SUCCESS) {
// Authentication succeeded
sasl_send_response(server, NULL);
scram_session_free(conn->scram_session);
conn->scram_session = NULL;
} else if (ret == SCRAM_ERROR) {
// Authentication failed
irc_send_cmd_now(server, "AUTHENTICATE *");
if (conn->scram_session->error != NULL) {
g_warning("SASL SCRAM authentication failed: %s",
conn->scram_session->error);
}
scram_session_free(conn->scram_session);
conn->scram_session = NULL;
}
}
/* /*
* Called when the incoming SASL request is completely received. * Called when the incoming SASL request is completely received.
*/ */
@ -258,6 +324,18 @@ static void sasl_step_complete(IRC_SERVER_REC *server, GString *data)
/* Empty response */ /* Empty response */
sasl_send_response(server, NULL); sasl_send_response(server, NULL);
break; break;
case SASL_MECHANISM_SCRAM_SHA_1:
scram_authenticate(server, data->str, "SHA1");
break;
case SASL_MECHANISM_SCRAM_SHA_256:
scram_authenticate(server, data->str, "SHA256");
break;
case SASL_MECHANISM_SCRAM_SHA_512:
scram_authenticate(server, data->str, "SHA512");
break;
} }
} }

View File

@ -25,6 +25,9 @@ enum {
SASL_MECHANISM_NONE = 0, SASL_MECHANISM_NONE = 0,
SASL_MECHANISM_PLAIN, SASL_MECHANISM_PLAIN,
SASL_MECHANISM_EXTERNAL, SASL_MECHANISM_EXTERNAL,
SASL_MECHANISM_SCRAM_SHA_1,
SASL_MECHANISM_SCRAM_SHA_256,
SASL_MECHANISM_SCRAM_SHA_512,
SASL_MECHANISM_MAX SASL_MECHANISM_MAX
}; };

304
src/irc/core/scram.c Normal file
View File

@ -0,0 +1,304 @@
/*
scram.c : irssi
Copyright (C) 2023 Patrick Okraku
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "module.h"
#include <irssi/src/irc/core/scram.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#define NONCE_LENGTH 18
#define CLIENT_KEY "Client Key"
#define SERVER_KEY "Server Key"
// EVP_MD_CTX_create() and EVP_MD_CTX_destroy() were renamed in OpenSSL 1.1.0
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
#define EVP_MD_CTX_new(ctx) EVP_MD_CTX_create(ctx)
#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx)
#endif
SCRAM_SESSION_REC *scram_session_create(const char *digest, const char *username,
const char *password)
{
SCRAM_SESSION_REC *session;
const EVP_MD *md;
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
OpenSSL_add_all_algorithms();
#endif
md = EVP_get_digestbyname(digest);
if (md == NULL) {
// Unknown message digest
return NULL;
}
session = g_new0(SCRAM_SESSION_REC, 1);
session->digest = md;
session->digest_size = EVP_MD_size(md);
session->username = g_strdup(username);
session->password = g_strdup(password);
return session;
}
void scram_session_free(SCRAM_SESSION_REC *session)
{
if (session == NULL) {
return;
}
g_free(session->username);
g_free(session->password);
g_free(session->client_nonce_b64);
g_free(session->client_first_message_bare);
g_free(session->salted_password);
g_free(session->auth_message);
g_free(session->error);
g_free(session);
}
static int create_nonce(void *buffer, size_t length)
{
return RAND_bytes(buffer, length);
}
static int create_SHA(SCRAM_SESSION_REC *session, const unsigned char *input, size_t input_len,
unsigned char *output, unsigned int *output_len)
{
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
if (!EVP_DigestInit_ex(md_ctx, session->digest, NULL)) {
session->error = g_strdup("Message digest initialization failed");
EVP_MD_CTX_free(md_ctx);
return SCRAM_ERROR;
}
if (!EVP_DigestUpdate(md_ctx, input, input_len)) {
session->error = g_strdup("Message digest update failed");
EVP_MD_CTX_free(md_ctx);
return SCRAM_ERROR;
}
if (!EVP_DigestFinal_ex(md_ctx, output, output_len)) {
session->error = g_strdup("Message digest finalization failed");
EVP_MD_CTX_free(md_ctx);
return SCRAM_ERROR;
}
EVP_MD_CTX_free(md_ctx);
return SCRAM_IN_PROGRESS;
}
static scram_status process_client_first(SCRAM_SESSION_REC *session, char **output,
size_t *output_len)
{
char nonce[NONCE_LENGTH];
if (!create_nonce(nonce, NONCE_LENGTH)) {
session->error = g_strdup("Could not create client nonce");
return SCRAM_ERROR;
}
session->client_nonce_b64 = g_base64_encode((guchar *) nonce, NONCE_LENGTH);
*output = g_strdup_printf("n,,n=%s,r=%s", session->username, session->client_nonce_b64);
*output_len = strlen(*output);
session->client_first_message_bare = g_strdup(*output + 3);
session->step++;
return SCRAM_IN_PROGRESS;
}
static scram_status process_server_first(SCRAM_SESSION_REC *session, const char *data,
char **output, size_t *output_len)
{
char **params, *client_final_message_without_proof, *salt, *server_nonce_b64,
*client_proof_b64;
unsigned char *client_key, stored_key[EVP_MAX_MD_SIZE], *client_signature, *client_proof;
unsigned int i, param_count, iteration_count, client_key_len, stored_key_len;
gsize salt_len = 0;
size_t client_nonce_len;
params = g_strsplit(data, ",", -1);
param_count = g_strv_length(params);
if (param_count < 3) {
/* Invalid server-first-message */
session->error = g_strdup_printf("%s", data);
g_strfreev(params);
return SCRAM_ERROR;
}
server_nonce_b64 = NULL;
salt = NULL;
iteration_count = 0;
for (i = 0; i < param_count; i++) {
if (!strncmp(params[i], "r=", 2)) {
g_free(server_nonce_b64);
server_nonce_b64 = g_strdup(params[i] + 2);
} else if (!strncmp(params[i], "s=", 2)) {
g_free(salt);
salt = g_strdup(params[i] + 2);
} else if (!strncmp(params[i], "i=", 2)) {
iteration_count = strtoul(params[i] + 2, NULL, 10);
}
}
g_strfreev(params);
if (server_nonce_b64 == NULL || *server_nonce_b64 == '\0' || salt == NULL ||
*salt == '\0' || iteration_count == 0) {
session->error = g_strdup_printf("Invalid server-first-message: %s", data);
g_free(server_nonce_b64);
g_free(salt);
return SCRAM_ERROR;
}
client_nonce_len = strlen(session->client_nonce_b64);
// The server can append his nonce to the client's nonce
if (strlen(server_nonce_b64) < client_nonce_len ||
strncmp(server_nonce_b64, session->client_nonce_b64, client_nonce_len)) {
session->error = g_strdup_printf("Invalid server nonce: %s", server_nonce_b64);
return SCRAM_ERROR;
}
g_base64_decode_inplace((gchar *) salt, &salt_len);
// SaltedPassword := Hi(Normalize(password), salt, i)
session->salted_password = g_malloc(session->digest_size);
PKCS5_PBKDF2_HMAC(session->password, strlen(session->password), (unsigned char *) salt,
salt_len, iteration_count, session->digest, session->digest_size,
session->salted_password);
// AuthMessage := client-first-message-bare + "," +
// server-first-message + "," +
// client-final-message-without-proof
client_final_message_without_proof = g_strdup_printf("c=biws,r=%s", server_nonce_b64);
session->auth_message = g_strdup_printf("%s,%s,%s", session->client_first_message_bare,
data, client_final_message_without_proof);
// ClientKey := HMAC(SaltedPassword, "Client Key")
client_key = g_malloc0(session->digest_size);
HMAC(session->digest, session->salted_password, session->digest_size,
(unsigned char *) CLIENT_KEY, strlen(CLIENT_KEY), client_key, &client_key_len);
// StoredKey := H(ClientKey)
if (!create_SHA(session, client_key, session->digest_size, stored_key, &stored_key_len)) {
g_free(client_final_message_without_proof);
g_free(server_nonce_b64);
g_free(salt);
g_free(client_key);
return SCRAM_ERROR;
}
// ClientSignature := HMAC(StoredKey, AuthMessage)
client_signature = g_malloc0(session->digest_size);
HMAC(session->digest, stored_key, stored_key_len, (unsigned char *) session->auth_message,
strlen((char *) session->auth_message), client_signature, NULL);
// ClientProof := ClientKey XOR ClientSignature
client_proof = g_malloc0(client_key_len);
for (i = 0; i < client_key_len; i++) {
client_proof[i] = client_key[i] ^ client_signature[i];
}
client_proof_b64 = g_base64_encode((guchar *) client_proof, client_key_len);
*output = g_strdup_printf("%s,p=%s", client_final_message_without_proof, client_proof_b64);
*output_len = strlen(*output);
g_free(server_nonce_b64);
g_free(salt);
g_free(client_final_message_without_proof);
g_free(client_key);
g_free(client_signature);
g_free(client_proof);
g_free(client_proof_b64);
session->step++;
return SCRAM_IN_PROGRESS;
}
static scram_status process_server_final(SCRAM_SESSION_REC *session, const char *data)
{
char *verifier;
unsigned char *server_key, *server_signature;
unsigned int server_key_len = 0, server_signature_len = 0;
gsize verifier_len = 0;
if (strlen(data) < 3 || (data[0] != 'v' && data[1] != '=')) {
return SCRAM_ERROR;
}
verifier = g_strdup(data + 2);
g_base64_decode_inplace(verifier, &verifier_len);
// ServerKey := HMAC(SaltedPassword, "Server Key")
server_key = g_malloc0(session->digest_size);
HMAC(session->digest, session->salted_password, session->digest_size,
(unsigned char *) SERVER_KEY, strlen(SERVER_KEY), server_key, &server_key_len);
// ServerSignature := HMAC(ServerKey, AuthMessage)
server_signature = g_malloc0(session->digest_size);
HMAC(session->digest, server_key, session->digest_size,
(unsigned char *) session->auth_message, strlen((char *) session->auth_message),
server_signature, &server_signature_len);
if (verifier_len == server_signature_len &&
memcmp(verifier, server_signature, verifier_len) == 0) {
g_free(verifier);
g_free(server_key);
g_free(server_signature);
return SCRAM_SUCCESS;
} else {
g_free(verifier);
g_free(server_key);
g_free(server_signature);
return SCRAM_ERROR;
}
}
scram_status scram_process(SCRAM_SESSION_REC *session, const char *input, char **output,
size_t *output_len)
{
scram_status status;
switch (session->step) {
case 0:
status = process_client_first(session, output, output_len);
break;
case 1:
status = process_server_first(session, input, output, output_len);
break;
case 2:
status = process_server_final(session, input);
break;
default:
*output = NULL;
*output_len = 0;
status = SCRAM_ERROR;
break;
}
return status;
}

27
src/irc/core/scram.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef IRSSI_IRC_CORE_SCRAM_H
#define IRSSI_IRC_CORE_SCRAM_H
#include <openssl/evp.h>
typedef struct {
const EVP_MD *digest;
size_t digest_size;
char *username;
char *password;
char *client_nonce_b64;
char *client_first_message_bare;
unsigned char *salted_password;
char *auth_message;
char *error;
int step;
} SCRAM_SESSION_REC;
typedef enum { SCRAM_ERROR = 0, SCRAM_IN_PROGRESS, SCRAM_SUCCESS } scram_status;
SCRAM_SESSION_REC *scram_session_create(const char *digset, const char *username,
const char *password);
void scram_session_free(SCRAM_SESSION_REC *session);
scram_status scram_process(SCRAM_SESSION_REC *session, const char *input, char **output,
size_t *output_len);
#endif

View File

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
DATE=`grep '^v' $1/NEWS | head -1` DATE=`grep '^v' "$1"/NEWS | head -1`
VERSION_DATE=`echo "$DATE" | cut -f 2 -d ' ' | tr -d -` VERSION_DATE=`echo "$DATE" | cut -f 2 -d ' ' | tr -d -`
case $VERSION_DATE in case $VERSION_DATE in
*xx) *xx)