Compare commits

..

3 Commits

Author SHA1 Message Date
Patrick Griffis
915901975b fish: Misc test cleanups 2021-07-15 20:53:24 -05:00
Patrick Griffis
867d831e9a actions: Add MSYS2 builder 2021-07-15 20:24:45 -05:00
Patrick Griffis
a96b5cc8f6 actions: Attempt at building gtk 2021-07-15 20:21:35 -05:00
22 changed files with 1749 additions and 574 deletions

40
.github/workflows/msys-build.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: MSYS2 Build
on: [push, pull_request]
jobs:
build:
runs-on: windows-latest
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v2
- uses: msys2/setup-msys2@v2
with:
install: >-
mingw-w64-x86_64-gcc
mingw-w64-x86_64-pkg-config
mingw-w64-x86_64-python3-cffi
mingw-w64-x86_64-meson
mingw-w64-x86_64-gtk2
mingw-w64-x86_64-luajit
mingw-w64-x86_64-desktop-file-utils
- name: Configure
run: >-
meson build
-Dtext-frontend=true
-Ddbus=disabled
-Dwith-upd=false
-Dwith-perl=false
- name: Build
run: ninja -C build
- name: Test
run: ninja -C build test
- name: Install
run: ninja -C build install

View File

@@ -2,8 +2,41 @@ name: Windows Build
on: [push, pull_request]
jobs:
build-gtk:
runs-on: windows-2019
strategy:
matrix:
platform: [x64, win32]
arch: [x64, x86]
exclude:
- platform: x64
arch: x86
- platform: win32
arch: x64
fail-fast: false
steps:
- uses: actions/checkout@v2
with:
repository: wingtk/gvsbuild
ref: 9b10978a8c5aa539f4280feeaa69bc5cc8bf9fbf
- uses: actions/cache@v2
with:
path: C:\gtk-build
key: 9b10978a8c5aa539f4280feeaa69bc5cc8bf9fbf
- name: Build
run: |
C:/hostedtoolcache/windows/Python/3.6.8/${{ matrix.arch }}/python.exe .\build.py build --python-dir="C:/hostedtoolcache/windows/Python/3.6.8/${{ matrix.arch }}" -p ${{ matrix.arch }} --vs-ver=16 gtk lgi openssl
- uses: actions/upload-artifact@v2
with:
name: Build Files ${{ matrix.arch }}
path: C:\gtk-build\gtk\${{ matrix.platform }}\release
build:
runs-on: windows-2019
needs: build-gtk
strategy:
matrix:
platform: [x64, win32]

View File

@@ -13,7 +13,7 @@ gnome = import('gnome')
cc = meson.get_compiler('c')
libgio_dep = dependency('gio-2.0', version: '>= 2.44.0')
libgio_dep = dependency('gio-2.0', version: '>= 2.34.0')
libgmodule_dep = dependency('gmodule-2.0')
libcanberra_dep = dependency('libcanberra', version: '>= 0.22',
@@ -46,8 +46,8 @@ config_h.set('G_DISABLE_SINGLE_INCLUDES', true)
config_h.set('GTK_DISABLE_DEPRECATED', true)
config_h.set('GTK_DISABLE_SINGLE_INCLUDES', true)
config_h.set('GDK_PIXBUF_DISABLE_SINGLE_INCLUDES', true)
config_h.set('GLIB_VERSION_MAX_ALLOWED', 'GLIB_VERSION_2_44')
config_h.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_44')
config_h.set('GLIB_VERSION_MAX_ALLOWED', 'GLIB_VERSION_2_34')
config_h.set('GLIB_VERSION_MIN_REQUIRED', 'GLIB_VERSION_2_34')
# Detected features
config_h.set('HAVE_MEMRCHR', cc.has_function('memrchr'))

View File

@@ -1,17 +1,15 @@
subdir('old_version')
fishlim_test_sources = [
'tests.c',
'fake/keystore.c',
'mock-keystore.c',
'../fish.c',
'../utils.c',
]
fishlim_tests = executable('fishlim_tests', fishlim_test_sources,
dependencies: [libgio_dep, libssl_dep],
link_with : fishlim_old_lib
dependencies: [libgio_dep, libssl_dep, hexchat_plugin_dep],
include_directories: include_directories('..'),
)
test('Fishlim Tests', fishlim_tests,
timeout: 90
protocol: 'tap',
)

View File

@@ -1,5 +1,4 @@
/*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -22,26 +21,31 @@
*/
#include "../../fish.h"
#include "fish.h"
/**
* Extracts a key from the key store file.
*/
char *keystore_get_key(const char *nick, enum fish_mode *mode) {
char *
keystore_get_key(const char *nick, enum fish_mode *mode)
{
return NULL;
}
/**
* Sets a key in the key store file.
*/
gboolean keystore_store_key(const char *nick, const char *key, enum fish_mode mode) {
gboolean
keystore_store_key(const char *nick, const char *key, enum fish_mode mode)
{
return TRUE;
}
/**
* Deletes a nick from the key store.
*/
gboolean keystore_delete_nick(const char *nick) {
gboolean
keystore_delete_nick(const char *nick)
{
return TRUE;
}

View File

@@ -1,155 +0,0 @@
/*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifdef __APPLE__
#define __AVAILABILITYMACROS__
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
#include <stdlib.h>
#include <string.h>
#include <openssl/blowfish.h>
#include "fish.h"
#define IB 64
static const char fish_base64[64] = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const signed char fish_unbase64[256] = {
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB,IB,IB,
/* ! " # $ % & ' ( ) * + , - . / */
IB,IB,IB,IB,IB,IB,IB,IB, IB,IB,IB,IB,IB,IB, 0, 1,
/* 0 1 2 3 4 5 6 7 8 9 : ; < = > ? */
2, 3, 4, 5, 6, 7, 8, 9, 10,11,IB,IB,IB,IB,IB,IB,
/* @ A B C D E F G H I J K L M N O */
IB,38,39,40,41,42,43,44, 45,46,47,48,49,50,51,52,
/* P Q R S T U V W X Y Z [ \ ] ^ _*/
53,54,55,56,57,58,59,60, 61,62,63,IB,IB,IB,IB,IB,
/* ` a b c d e f g h i j k l m n o */
IB,12,13,14,15,16,17,18, 19,20,21,22,23,24,25,26,
/* p q r s t u v w x y z { | } ~ <del> */
27,28,29,30,31,32,33,34, 35,36,37,IB,IB,IB,IB,IB,
};
#define GET_BYTES(dest, source) do { \
*((dest)++) = ((source) >> 24) & 0xFF; \
*((dest)++) = ((source) >> 16) & 0xFF; \
*((dest)++) = ((source) >> 8) & 0xFF; \
*((dest)++) = (source) & 0xFF; \
} while (0);
char *__old_fish_encrypt(const char *key, size_t keylen, const char *message) {
BF_KEY bfkey;
size_t messagelen;
size_t i;
int j;
char *encrypted;
char *end;
unsigned char bit;
unsigned char word;
unsigned char d;
BF_set_key(&bfkey, keylen, (const unsigned char*)key);
messagelen = strlen(message);
if (messagelen == 0) return NULL;
encrypted = g_malloc(((messagelen - 1) / 8) * 12 + 12 + 1); /* each 8-byte block becomes 12 bytes */
end = encrypted;
while (*message) {
/* Read 8 bytes (a Blowfish block) */
BF_LONG binary[2] = { 0, 0 };
unsigned char c;
for (i = 0; i < 8; i++) {
c = message[i];
binary[i >> 2] |= c << 8*(3 - (i&3));
if (c == '\0') break;
}
message += 8;
/* Encrypt block */
BF_encrypt(binary, &bfkey);
/* Emit FiSH-BASE64 */
bit = 0;
word = 1;
for (j = 0; j < 12; j++) {
d = fish_base64[(binary[word] >> bit) & 63];
*(end++) = d;
bit += 6;
if (j == 5) {
bit = 0;
word = 0;
}
}
/* Stop if a null terminator was found */
if (c == '\0') break;
}
*end = '\0';
return encrypted;
}
char *__old_fish_decrypt(const char *key, size_t keylen, const char *data) {
BF_KEY bfkey;
size_t i;
char *decrypted;
char *end;
unsigned char bit;
unsigned char word;
unsigned char d;
BF_set_key(&bfkey, keylen, (const unsigned char*)key);
decrypted = g_malloc(strlen(data) + 1);
end = decrypted;
while (*data) {
/* Convert from FiSH-BASE64 */
BF_LONG binary[2] = { 0, 0 };
bit = 0;
word = 1;
for (i = 0; i < 12; i++) {
d = fish_unbase64[(const unsigned char)*(data++)];
if (d == IB) goto decrypt_end;
binary[word] |= (unsigned long)d << bit;
bit += 6;
if (i == 5) {
bit = 0;
word = 0;
}
}
/* Decrypt block */
BF_decrypt(binary, &bfkey);
/* Copy to buffer */
GET_BYTES(end, binary[0]);
GET_BYTES(end, binary[1]);
}
decrypt_end:
*end = '\0';
return decrypted;
}

View File

@@ -1,37 +0,0 @@
/*
Copyright (c) 2010 Samuel Lidén Borell <samuel@kodafritt.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef FISH_OLD_H
#define FISH_OLD_H
#include <stddef.h>
#include <glib.h>
char *__old_fish_encrypt(const char *key, size_t keylen, const char *message);
char *__old_fish_decrypt(const char *key, size_t keylen, const char *data);
#endif

View File

@@ -1,4 +0,0 @@
fishlim_old_lib = shared_library('fishlim_old_lib',
['fish.c'],
dependencies: [libgio_dep, libssl_dep],
)

View File

@@ -1,5 +1,4 @@
/*
Copyright (c) 2020 <bakasura@protonmail.ch>
Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -22,22 +21,19 @@
*/
#ifndef PLUGIN_HEXCHAT_FISHLIM_TEST_H
#define PLUGIN_HEXCHAT_FISHLIM_TEST_H
// Libs
#include <glib.h>
// Project Libs
#include "../fish.h"
#include "../utils.h"
#include "old_version/fish.h"
#include "fish.h"
#include "utils.h"
/**
* Auxiliary function: Generate a random string
* @param out Preallocated string to fill
* @param len Size of bytes to fill
*/
void random_string(char *out, size_t len) {
static void
random_string(char *out, size_t len)
{
GRand *rand = NULL;
int i = 0;
@@ -51,13 +47,14 @@ void random_string(char *out, size_t len) {
g_rand_free(rand);
}
/**
* Check encrypt and decrypt in ECB mode and compare with old implementation
* Check encrypt and decrypt in ECB mode
*/
void __ecb(void) {
char *bo64 = NULL, *b64 = NULL;
char *deo = NULL, *de = NULL;
static void
test_ecb(void)
{
char *b64 = NULL;
char *de = NULL;
int key_len, message_len = 0;
char key[57];
char message[1000];
@@ -71,36 +68,20 @@ void __ecb(void) {
random_string(message, message_len);
/* Encrypt */
bo64 = __old_fish_encrypt(key, key_len, message);
g_assert_nonnull(bo64);
b64 = fish_encrypt(key, key_len, message, message_len, FISH_ECB_MODE);
g_assert_nonnull(b64);
g_assert_cmpuint(g_strcmp0(b64, bo64), == , 0);
/* Decrypt */
/* Linear */
deo = __old_fish_decrypt(key, key_len, bo64);
de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE);
g_assert_nonnull(deo);
g_assert_nonnull(de);
g_assert_cmpuint(g_strcmp0(de, message), == , 0);
g_assert_cmpuint(g_strcmp0(deo, message), == , 0);
g_assert_cmpuint(g_strcmp0(de, deo), == , 0);
g_free(deo);
g_assert_cmpstr (de, ==, message);
g_free(de);
/* Mixed */
deo = __old_fish_decrypt(key, key_len, b64);
de = fish_decrypt_str(key, key_len, bo64, FISH_ECB_MODE);
g_assert_nonnull(deo);
g_assert_nonnull(de);
g_assert_cmpuint(g_strcmp0(de, message), == , 0);
g_assert_cmpuint(g_strcmp0(deo, message), == , 0);
g_assert_cmpuint(g_strcmp0(de, deo), == , 0);
g_free(deo);
de = fish_decrypt_str(key, key_len, b64, FISH_ECB_MODE);
g_assert_cmpstr (de, ==, message);
g_free(de);
/* Free */
g_free(bo64);
g_free(b64);
}
}
@@ -109,7 +90,9 @@ void __ecb(void) {
/**
* Check encrypt and decrypt in CBC mode
*/
void __cbc(void) {
static void
test_cbc(void)
{
char *b64 = NULL;
char *de = NULL;
int key_len, message_len = 0;
@@ -131,11 +114,9 @@ void __cbc(void) {
/* Decrypt */
/* Linear */
de = fish_decrypt_str(key, key_len, b64, FISH_CBC_MODE);
g_assert_nonnull(de);
g_assert_cmpuint(g_strcmp0(de, message), == , 0);
g_assert_cmpstr (de, ==, message);
g_free(de);
/* Free */
g_free(b64);
}
}
@@ -144,13 +125,14 @@ void __cbc(void) {
/**
* Check the calculation of final length from an encoded string in Base64
*/
void __base64_len(void) {
static void
test_base64_len (void)
{
char *b64 = NULL;
int i, message_len = 0;
char message[1000];
for (i = 0; i < 10; ++i) {
for (message_len = 1; message_len < 1000; ++message_len) {
random_string(message, message_len);
b64 = g_base64_encode((const unsigned char *) message, message_len);
@@ -164,7 +146,9 @@ void __base64_len(void) {
/**
* Check the calculation of final length from an encoded string in BlowcryptBase64
*/
void __base64_fish_len(void) {
static void
test_base64_fish_len (void)
{
char *b64 = NULL;
int i, message_len = 0;
char message[1000];
@@ -181,11 +165,12 @@ void __base64_fish_len(void) {
}
}
/**
* Check the calculation of final length from an encrypted string in ECB mode
*/
void __base64_ecb_len(void) {
static void
test_base64_ecb_len(void)
{
char *b64 = NULL;
int key_len, message_len = 0;
char key[57];
@@ -209,7 +194,9 @@ void __base64_ecb_len(void) {
/**
* Check the calculation of final length from an encrypted string in CBC mode
*/
void __base64_cbc_len(void) {
static void
test_base64_cbc_len(void)
{
char *b64 = NULL;
int key_len, message_len = 0;
char key[57];
@@ -233,7 +220,9 @@ void __base64_cbc_len(void) {
/**
* Check the calculation of length limit for a plaintext in each encryption mode
*/
void __max_text_command_len(void) {
static void
test_max_text_command_len(void)
{
int max_encoded_len, plaintext_len;
enum fish_mode mode;
@@ -248,7 +237,9 @@ void __max_text_command_len(void) {
/**
* Check the calculation of length limit for a plaintext in each encryption mode
*/
void __foreach_utf8_data_chunks(void) {
static void
test_foreach_utf8_data_chunks(void)
{
GRand *rand = NULL;
GString *chunks = NULL;
int tests, max_chunks_len, chunks_len;
@@ -277,21 +268,19 @@ void __foreach_utf8_data_chunks(void) {
}
}
int main(int argc, char *argv[]) {
int
main(int argc, char *argv[]) {
g_test_init(&argc, &argv, NULL);
g_test_add_func("/fishlim/__ecb", __ecb);
g_test_add_func("/fishlim/__cbc", __ecb);
g_test_add_func("/fishlim/__base64_len", __base64_len);
g_test_add_func("/fishlim/__base64_fish_len", __base64_fish_len);
g_test_add_func("/fishlim/__base64_ecb_len", __base64_ecb_len);
g_test_add_func("/fishlim/__base64_cbc_len", __base64_cbc_len);
g_test_add_func("/fishlim/__max_text_command_len", __max_text_command_len);
g_test_add_func("/fishlim/__foreach_utf8_data_chunks", __foreach_utf8_data_chunks);
g_test_add_func("/fishlim/ecb", test_ecb);
g_test_add_func("/fishlim/cbc", test_cbc);
g_test_add_func("/fishlim/base64_len", test_base64_len);
g_test_add_func("/fishlim/base64_fish_len", test_base64_fish_len);
g_test_add_func("/fishlim/base64_ecb_len", test_base64_ecb_len);
g_test_add_func("/fishlim/base64_cbc_len", test_base64_cbc_len);
g_test_add_func("/fishlim/max_text_command_len", test_max_text_command_len);
g_test_add_func("/fishlim/foreach_utf8_data_chunks", test_foreach_utf8_data_chunks);
return g_test_run();
}
#endif //PLUGIN_HEXCHAT_FISHLIM_TEST_H

View File

@@ -487,19 +487,6 @@ dcc_notify_kill (struct server *serv)
}
}
static int
tcp_send_real (int sok, GIConv write_converter, char *buf, int len)
{
int ret;
gsize buf_encoded_len;
gchar *buf_encoded = text_convert_invalid (buf, len, write_converter, arbitrary_encoding_fallback_string, &buf_encoded_len);
ret = send (sok, buf_encoded, buf_encoded_len, 0);
g_free (buf_encoded);
return ret;
}
struct DCC *
dcc_write_chat (char *nick, char *text)
{
@@ -512,7 +499,7 @@ dcc_write_chat (char *nick, char *text)
if (dcc && dcc->dccstat == STAT_ACTIVE)
{
len = strlen (text);
tcp_send_real (dcc->sok, dcc->serv->write_converter, text, len);
tcp_send_real (NULL, dcc->sok, dcc->serv->write_converter, text, len);
send (dcc->sok, "\n", 1, 0);
dcc->size += len;
fe_dcc_update (dcc);
@@ -1669,8 +1656,7 @@ dcc_listen_init (struct DCC *dcc, session *sess)
memset (&SAddr, 0, sizeof (struct sockaddr_in));
len = sizeof (SAddr);
/* TODO: Get rid of raw socket usage */
getsockname (g_socket_get_fd (dcc->serv->socket), (struct sockaddr *) &SAddr, &len);
getsockname (dcc->serv->sok, (struct sockaddr *) &SAddr, &len);
SAddr.sin_family = AF_INET;

View File

@@ -266,7 +266,7 @@ lag_check (void)
EMIT_SIGNAL (XP_TE_PINGTIMEOUT, serv->server_session, tbuf, NULL,
NULL, NULL, 0);
if (prefs.hex_net_auto_reconnect)
serv->auto_reconnect (serv, FALSE, NULL);
serv->auto_reconnect (serv, FALSE, -1);
}
else
{

View File

@@ -39,6 +39,10 @@
#include "history.h"
#include "tree.h"
#ifdef USE_OPENSSL
#include <openssl/ssl.h> /* SSL_() */
#endif
#ifdef __EMX__ /* for o/s 2 */
#define OFLAGS O_BINARY
#define g_ascii_strcasecmp stricmp
@@ -430,10 +434,10 @@ typedef struct server
{
/* server control operations (in server*.c) */
void (*connect)(struct server *, char *hostname, int port, int no_login);
void (*disconnect)(struct session *, int sendquit, GError *err);
void (*disconnect)(struct session *, int sendquit, int err);
int (*cleanup)(struct server *);
void (*flush_queue)(struct server *);
void (*auto_reconnect)(struct server *, int send_quit, GError *err);
void (*auto_reconnect)(struct server *, int send_quit, int err);
/* irc protocol functions (in proto*.c) */
void (*p_inline)(struct server *, char *buf, int len);
void (*p_invite)(struct server *, char *channel, char *nick);
@@ -470,18 +474,29 @@ typedef struct server
int (*p_cmp)(const char *s1, const char *s2);
int port;
int sok; /* is equal to sok4 or sok6 (the one we are using) */
int sok4; /* tcp4 socket */
int sok6; /* tcp6 socket */
int proxy_type;
int proxy_sok; /* Additional information for MS Proxy beast */
int proxy_sok4;
int proxy_sok6;
int id; /* unique ID number (for plugin API) */
/* dcc_ip moved from hexchatprefs to make it per-server */
guint32 dcc_ip;
GSocketClient *socket_client;
GSocketConnection *socket_conn;
GSocket *socket; /* Owned by socket_conn */
GCancellable *connection_cancellable;
GTlsCertificate *client_cert;
GSource *socket_read_source;
#ifdef USE_OPENSSL
SSL_CTX *ctx;
SSL *ssl;
int ssl_do_connect_tag;
#else
void *ssl;
#endif
int childread;
int childwrite;
int childpid;
int iotag;
int recondelay_tag; /* reconnect delay timeout */
int joindelay_tag; /* waiting before we send JOIN */
char hostname[128]; /* real ip number */

View File

@@ -74,6 +74,7 @@ textevents = custom_target('textevents',
# HAVE_GTK_MAC
if libssl_dep.found()
common_sources += 'ssl.c'
common_deps += libssl_dep
endif

View File

@@ -903,8 +903,8 @@ cmd_debug (struct session *sess, char *tbuf, char *word[], char *word_eol[])
while (list)
{
v = (struct server *) list->data;
sprintf (tbuf, "%p %s\n",
v, v->servername);
sprintf (tbuf, "%p %-5d %s\n",
v, v->sok, v->servername);
PrintText (sess, tbuf);
list = list->next;
}
@@ -1414,7 +1414,7 @@ cmd_devoice (struct session *sess, char *tbuf, char *word[], char *word_eol[])
static int
cmd_discon (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{
sess->server->disconnect (sess, TRUE, NULL);
sess->server->disconnect (sess, TRUE, -1);
return TRUE;
}
@@ -1956,7 +1956,7 @@ cmd_quit (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{
if (*word_eol[2])
sess->quitreason = word_eol[2];
sess->server->disconnect (sess, TRUE, NULL);
sess->server->disconnect (sess, TRUE, -1);
sess->quitreason = NULL;
return 2;
}
@@ -3216,7 +3216,7 @@ cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[])
{
serv = list->data;
if (serv->connected)
serv->auto_reconnect (serv, TRUE, NULL);
serv->auto_reconnect (serv, TRUE, -1);
list = list->next;
}
}
@@ -3249,11 +3249,11 @@ cmd_reconnect (struct session *sess, char *tbuf, char *word[], char *word_eol[])
if (*word[3+offset])
serv->port = atoi (word[3+offset]);
safe_strcpy (serv->hostname, word[2+offset], sizeof (serv->hostname));
serv->auto_reconnect (serv, TRUE, NULL);
serv->auto_reconnect (serv, TRUE, -1);
}
else
{
serv->auto_reconnect (serv, TRUE, NULL);
serv->auto_reconnect (serv, TRUE, -1);
}
prefs.hex_net_reconnect_delay = tmp;
@@ -3294,7 +3294,6 @@ cmd_send (struct session *sess, char *tbuf, char *word[], char *word_eol[])
if (!word[2][0])
return FALSE;
#if 0
addr = dcc_get_my_address (sess);
if (addr == 0)
{
@@ -3314,7 +3313,6 @@ cmd_send (struct session *sess, char *tbuf, char *word[], char *word_eol[])
g_snprintf (tbuf, 512, "DCC SEND %s", word_eol[2]);
handle_command (sess, tbuf, FALSE);
#endif
return TRUE;
}

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,7 @@ extern GSList *serv_list;
/* eventually need to keep the tcp_* functions isolated to server.c */
int tcp_send_len (server *serv, char *buf, int len);
void tcp_sendf (server *serv, const char *fmt, ...) G_GNUC_PRINTF (2, 3);
int tcp_send_real (void *ssl, int sok, GIConv write_converter, char *buf, int len);
server *server_new (void);
int is_server (server *serv);
@@ -38,4 +39,6 @@ void server_free (server *serv);
void server_away_save_message (server *serv, char *nick, char *msg);
struct away_msg *server_away_find_message (server *serv, char *nick);
void base64_encode (char *to, char *from, unsigned int len);
#endif

567
src/common/ssl.c Normal file
View File

@@ -0,0 +1,567 @@
/*
* ssl.c v0.0.3
* Copyright (C) 2000 -- DaP <profeta@freemail.c3.hu>
*
* 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 St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifdef __APPLE__
#define __AVAILABILITYMACROS__
#define DEPRECATED_IN_MAC_OS_X_VERSION_10_7_AND_LATER
#endif
#include "inet.h" /* make it first to avoid macro redefinitions */
#include <openssl/ssl.h> /* SSL_() */
#include <openssl/err.h> /* ERR_() */
#include <openssl/x509v3.h>
#ifdef WIN32
#include <openssl/rand.h> /* RAND_seed() */
#endif
#include "config.h"
#include <time.h> /* asctime() */
#include <string.h> /* strncpy() */
#include "ssl.h" /* struct cert_info */
#include <glib.h>
#include <glib/gprintf.h>
#include <gio/gio.h>
#include "util.h"
/* If openssl was built without ec */
#ifndef SSL_OP_SINGLE_ECDH_USE
#define SSL_OP_SINGLE_ECDH_USE 0
#endif
#ifndef SSL_OP_NO_COMPRESSION
#define SSL_OP_NO_COMPRESSION 0
#endif
/* globals */
static struct chiper_info chiper_info; /* static buffer for _SSL_get_cipher_info() */
static char err_buf[256]; /* generic error buffer */
/* +++++ Internal functions +++++ */
static void
__SSL_fill_err_buf (char *funcname)
{
int err;
char buf[256];
err = ERR_get_error ();
ERR_error_string (err, buf);
g_snprintf (err_buf, sizeof (err_buf), "%s: %s (%d)\n", funcname, buf, err);
}
static void
__SSL_critical_error (char *funcname)
{
__SSL_fill_err_buf (funcname);
fprintf (stderr, "%s\n", err_buf);
exit (1);
}
/* +++++ SSL functions +++++ */
SSL_CTX *
_SSL_context_init (void (*info_cb_func))
{
SSL_CTX *ctx;
SSLeay_add_ssl_algorithms ();
SSL_load_error_strings ();
ctx = SSL_CTX_new (SSLv23_client_method ());
SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH);
SSL_CTX_set_timeout (ctx, 300);
SSL_CTX_set_options (ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3
|SSL_OP_NO_COMPRESSION
|SSL_OP_SINGLE_DH_USE|SSL_OP_SINGLE_ECDH_USE
|SSL_OP_NO_TICKET
|SSL_OP_CIPHER_SERVER_PREFERENCE);
#if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined (OPENSSL_NO_COMP) /* workaround for OpenSSL 0.9.8 */
sk_SSL_COMP_zero(SSL_COMP_get_compression_methods());
#endif
/* used in SSL_connect(), SSL_accept() */
SSL_CTX_set_info_callback (ctx, info_cb_func);
return(ctx);
}
static void
ASN1_TIME_snprintf (char *buf, int buf_len, ASN1_TIME * tm)
{
char *expires = NULL;
BIO *inMem = BIO_new (BIO_s_mem ());
ASN1_TIME_print (inMem, tm);
BIO_get_mem_data (inMem, &expires);
buf[0] = 0;
if (expires != NULL)
{
/* expires is not \0 terminated */
safe_strcpy (buf, expires, MIN(24, buf_len));
}
BIO_free (inMem);
}
static void
broke_oneline (char *oneline, char *parray[])
{
char *pt, *ppt;
int i;
i = 0;
ppt = pt = oneline + 1;
while ((pt = strchr (pt, '/')))
{
*pt = 0;
parray[i++] = ppt;
ppt = ++pt;
}
parray[i++] = ppt;
parray[i] = NULL;
}
/*
FIXME: Master-Key, Extensions, CA bits
(openssl x509 -text -in servcert.pem)
*/
int
_SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl)
{
X509 *peer_cert;
X509_PUBKEY *key;
X509_ALGOR *algor = NULL;
EVP_PKEY *peer_pkey;
char notBefore[64];
char notAfter[64];
int alg;
int sign_alg;
if (!(peer_cert = SSL_get_peer_certificate (ssl)))
return (1); /* FATAL? */
X509_NAME_oneline (X509_get_subject_name (peer_cert), cert_info->subject,
sizeof (cert_info->subject));
X509_NAME_oneline (X509_get_issuer_name (peer_cert), cert_info->issuer,
sizeof (cert_info->issuer));
broke_oneline (cert_info->subject, cert_info->subject_word);
broke_oneline (cert_info->issuer, cert_info->issuer_word);
key = X509_get_X509_PUBKEY(peer_cert);
if (!X509_PUBKEY_get0_param(NULL, NULL, 0, &algor, key))
return 1;
alg = OBJ_obj2nid (algor->algorithm);
#ifndef HAVE_X509_GET_SIGNATURE_NID
sign_alg = OBJ_obj2nid (peer_cert->sig_alg->algorithm);
#else
sign_alg = X509_get_signature_nid (peer_cert);
#endif
ASN1_TIME_snprintf (notBefore, sizeof (notBefore),
X509_get_notBefore (peer_cert));
ASN1_TIME_snprintf (notAfter, sizeof (notAfter),
X509_get_notAfter (peer_cert));
peer_pkey = X509_get_pubkey (peer_cert);
safe_strcpy (cert_info->algorithm,
(alg == NID_undef) ? "Unknown" : OBJ_nid2ln (alg),
sizeof (cert_info->algorithm));
cert_info->algorithm_bits = EVP_PKEY_bits (peer_pkey);
safe_strcpy (cert_info->sign_algorithm,
(sign_alg == NID_undef) ? "Unknown" : OBJ_nid2ln (sign_alg),
sizeof (cert_info->sign_algorithm));
/* EVP_PKEY_bits(ca_pkey)); */
cert_info->sign_algorithm_bits = 0;
safe_strcpy (cert_info->notbefore, notBefore, sizeof (cert_info->notbefore));
safe_strcpy (cert_info->notafter, notAfter, sizeof (cert_info->notafter));
EVP_PKEY_free (peer_pkey);
/* SSL_SESSION_print_fp(stdout, SSL_get_session(ssl)); */
/*
if (ssl->session->sess_cert->peer_rsa_tmp) {
tmp_pkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(tmp_pkey, ssl->session->sess_cert->peer_rsa_tmp);
cert_info->rsa_tmp_bits = EVP_PKEY_bits (tmp_pkey);
EVP_PKEY_free(tmp_pkey);
} else
fprintf(stderr, "REMOTE SIDE DOESN'T PROVIDES ->peer_rsa_tmp\n");
*/
cert_info->rsa_tmp_bits = 0;
X509_free (peer_cert);
return (0);
}
struct chiper_info *
_SSL_get_cipher_info (SSL * ssl)
{
const SSL_CIPHER *c;
c = SSL_get_current_cipher (ssl);
safe_strcpy (chiper_info.version, SSL_CIPHER_get_version (c),
sizeof (chiper_info.version));
safe_strcpy (chiper_info.chiper, SSL_CIPHER_get_name (c),
sizeof (chiper_info.chiper));
SSL_CIPHER_get_bits (c, &chiper_info.chiper_bits);
return (&chiper_info);
}
int
_SSL_send (SSL * ssl, char *buf, int len)
{
int num;
num = SSL_write (ssl, buf, len);
switch (SSL_get_error (ssl, num))
{
case SSL_ERROR_SSL: /* setup errno! */
/* ??? */
__SSL_fill_err_buf ("SSL_write");
fprintf (stderr, "%s\n", err_buf);
break;
case SSL_ERROR_SYSCALL:
/* ??? */
perror ("SSL_write/write");
break;
case SSL_ERROR_ZERO_RETURN:
/* fprintf(stderr, "SSL closed on write\n"); */
break;
}
return (num);
}
int
_SSL_recv (SSL * ssl, char *buf, int len)
{
int num;
num = SSL_read (ssl, buf, len);
switch (SSL_get_error (ssl, num))
{
case SSL_ERROR_SSL:
/* ??? */
__SSL_fill_err_buf ("SSL_read");
fprintf (stderr, "%s\n", err_buf);
break;
case SSL_ERROR_SYSCALL:
/* ??? */
if (!would_block ())
perror ("SSL_read/read");
break;
case SSL_ERROR_ZERO_RETURN:
/* fprintf(stdeerr, "SSL closed on read\n"); */
break;
}
return (num);
}
SSL *
_SSL_socket (SSL_CTX *ctx, int sd)
{
SSL *ssl;
const SSL_METHOD *method;
if (!(ssl = SSL_new (ctx)))
/* FATAL */
__SSL_critical_error ("SSL_new");
SSL_set_fd (ssl, sd);
#ifndef HAVE_SSL_CTX_GET_SSL_METHOD
method = ctx->method;
#else
method = SSL_CTX_get_ssl_method (ctx);
#endif
if (method == SSLv23_client_method())
SSL_set_connect_state (ssl);
else
SSL_set_accept_state(ssl);
return (ssl);
}
char *
_SSL_set_verify (SSL_CTX *ctx, void *verify_callback, char *cacert)
{
if (!SSL_CTX_set_default_verify_paths (ctx))
{
__SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths");
return (err_buf);
}
/*
if (cacert)
{
if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL))
{
__SSL_fill_err_buf ("SSL_CTX_load_verify_locations");
return (err_buf);
}
}
*/
SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback);
return (NULL);
}
void
_SSL_close (SSL * ssl)
{
SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
SSL_free (ssl);
#ifdef HAVE_ERR_REMOVE_THREAD_STATE
#if OPENSSL_VERSION_NUMBER >= 0x10000000L && OPENSSL_VERSION_NUMBER < 0x10100000L
/* OpenSSL handles this itself in 1.1+ and this is a no-op */
ERR_remove_thread_state (NULL);
#endif
#else
ERR_remove_state (0);
#endif
}
/* Hostname validation code based on OpenBSD's libtls. */
static int
_SSL_match_hostname (const char *cert_hostname, const char *hostname)
{
const char *cert_domain, *domain, *next_dot;
if (g_ascii_strcasecmp (cert_hostname, hostname) == 0)
return 0;
/* Wildcard match? */
if (cert_hostname[0] == '*')
{
/*
* Valid wildcards:
* - "*.domain.tld"
* - "*.sub.domain.tld"
* - etc.
* Reject "*.tld".
* No attempt to prevent the use of eg. "*.co.uk".
*/
cert_domain = &cert_hostname[1];
/* Disallow "*" */
if (cert_domain[0] == '\0')
return -1;
/* Disallow "*foo" */
if (cert_domain[0] != '.')
return -1;
/* Disallow "*.." */
if (cert_domain[1] == '.')
return -1;
next_dot = strchr (&cert_domain[1], '.');
/* Disallow "*.bar" */
if (next_dot == NULL)
return -1;
/* Disallow "*.bar.." */
if (next_dot[1] == '.')
return -1;
domain = strchr (hostname, '.');
/* No wildcard match against a hostname with no domain part. */
if (domain == NULL || strlen(domain) == 1)
return -1;
if (g_ascii_strcasecmp (cert_domain, domain) == 0)
return 0;
}
return -1;
}
static int
_SSL_check_subject_altname (X509 *cert, const char *host)
{
STACK_OF(GENERAL_NAME) *altname_stack = NULL;
GInetAddress *addr;
GSocketFamily family;
int type = GEN_DNS;
int count, i;
int rv = -1;
altname_stack = X509_get_ext_d2i (cert, NID_subject_alt_name, NULL, NULL);
if (altname_stack == NULL)
return -1;
addr = g_inet_address_new_from_string (host);
if (addr != NULL)
{
family = g_inet_address_get_family (addr);
if (family == G_SOCKET_FAMILY_IPV4 || family == G_SOCKET_FAMILY_IPV6)
type = GEN_IPADD;
}
count = sk_GENERAL_NAME_num(altname_stack);
for (i = 0; i < count; i++)
{
GENERAL_NAME *altname;
altname = sk_GENERAL_NAME_value (altname_stack, i);
if (altname->type != type)
continue;
if (type == GEN_DNS)
{
const unsigned char *data;
int format;
format = ASN1_STRING_type (altname->d.dNSName);
if (format == V_ASN1_IA5STRING)
{
#ifdef HAVE_ASN1_STRING_GET0_DATA
data = ASN1_STRING_get0_data (altname->d.dNSName);
#else
data = ASN1_STRING_data (altname->d.dNSName);
#endif
if (ASN1_STRING_length (altname->d.dNSName) != (int)strlen(data))
{
g_warning("NUL byte in subjectAltName, probably a malicious certificate.\n");
rv = -2;
break;
}
if (_SSL_match_hostname (data, host) == 0)
{
rv = 0;
break;
}
}
else
g_warning ("unhandled subjectAltName dNSName encoding (%d)\n", format);
}
else if (type == GEN_IPADD)
{
const unsigned char *data;
const guint8 *addr_bytes;
int datalen, addr_len;
datalen = ASN1_STRING_length (altname->d.iPAddress);
#ifdef HAVE_ASN1_STRING_GET0_DATA
data = ASN1_STRING_get0_data (altname->d.iPAddress);
#else
data = ASN1_STRING_data (altname->d.iPAddress);
#endif
addr_bytes = g_inet_address_to_bytes (addr);
addr_len = (int)g_inet_address_get_native_size (addr);
if (datalen == addr_len && memcmp (data, addr_bytes, addr_len) == 0)
{
rv = 0;
break;
}
}
}
if (addr != NULL)
g_object_unref (addr);
sk_GENERAL_NAME_pop_free (altname_stack, GENERAL_NAME_free);
return rv;
}
static int
_SSL_check_common_name (X509 *cert, const char *host)
{
X509_NAME *name;
char *common_name = NULL;
int common_name_len;
int rv = -1;
GInetAddress *addr;
name = X509_get_subject_name (cert);
if (name == NULL)
return -1;
common_name_len = X509_NAME_get_text_by_NID (name, NID_commonName, NULL, 0);
if (common_name_len < 0)
return -1;
common_name = g_malloc0 (common_name_len + 1);
X509_NAME_get_text_by_NID (name, NID_commonName, common_name, common_name_len + 1);
/* NUL bytes in CN? */
if (common_name_len != (int)strlen(common_name))
{
g_warning ("NUL byte in Common Name field, probably a malicious certificate.\n");
rv = -2;
goto out;
}
if ((addr = g_inet_address_new_from_string (host)) != NULL)
{
/*
* We don't want to attempt wildcard matching against IP
* addresses, so perform a simple comparison here.
*/
if (g_strcmp0 (common_name, host) == 0)
rv = 0;
else
rv = -1;
g_object_unref (addr);
}
else if (_SSL_match_hostname (common_name, host) == 0)
rv = 0;
out:
g_free(common_name);
return rv;
}
int
_SSL_check_hostname (X509 *cert, const char *host)
{
int rv;
rv = _SSL_check_subject_altname (cert, host);
if (rv == 0 || rv == -2)
return rv;
return _SSL_check_common_name (cert, host);
}

85
src/common/ssl.h Normal file
View File

@@ -0,0 +1,85 @@
/* HexChat
* Copyright (C) 1998-2010 Peter Zelezny.
* Copyright (C) 2009-2013 Berke Viktor.
*
* 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 St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef HEXCHAT_SSL_H
#define HEXCHAT_SSL_H
struct cert_info {
char subject[256];
char *subject_word[12];
char issuer[256];
char *issuer_word[12];
char algorithm[32];
int algorithm_bits;
char sign_algorithm[32];
int sign_algorithm_bits;
char notbefore[32];
char notafter[32];
int rsa_tmp_bits;
};
struct chiper_info {
char version[16];
char chiper[48];
int chiper_bits;
};
SSL_CTX *_SSL_context_init (void (*info_cb_func));
#define _SSL_context_free(a) SSL_CTX_free(a);
SSL *_SSL_socket (SSL_CTX *ctx, int sd);
char *_SSL_set_verify (SSL_CTX *ctx, void *(verify_callback), char *cacert);
/*
int SSL_connect(SSL *);
int SSL_accept(SSL *);
int SSL_get_fd(SSL *);
*/
void _SSL_close (SSL * ssl);
int _SSL_check_hostname(X509 *cert, const char *host);
int _SSL_get_cert_info (struct cert_info *cert_info, SSL * ssl);
struct chiper_info *_SSL_get_cipher_info (SSL * ssl);
/*char *_SSL_add_keypair (SSL_CTX *ctx, char *privkey, char *cert);*/
/*void _SSL_add_random_keypair(SSL_CTX *ctx, int bits);*/
int _SSL_send (SSL * ssl, char *buf, int len);
int _SSL_recv (SSL * ssl, char *buf, int len);
/* misc */
/*void broke_oneline (char *oneline, char *parray[]);*/
/*char *_SSL_do_cipher_base64(char *buf, int buf_len, char *key, int operation);*/ /* must be freed */
/*void *_SSL_get_sess_obj(SSL *ssl, int type);*/ /* NOT must be freed */
#define _SSL_get_sess_pkey(a) _SSL_get_sess_obj(a, 0)
#define _SSL_get_sess_prkey(a) _SSL_get_sess_obj(a, 1)
#define _SSL_get_sess_x509(a) _SSL_get_sess_obj(a, 2)
/*char *_SSL_get_obj_base64(void *s, int type);*/ /* must be freed */
#define _SSL_get_pkey_base64(a) _SSL_get_obj_base64(a, 0)
#define _SSL_get_prkey_base64(a) _SSL_get_obj_base64(a, 1)
#define _SSL_get_x509_base64(a) _SSL_get_obj_base64(a, 2)
/*char *_SSL_get_ctx_obj_base64(SSL_CTX *ctx, int type);*/ /* must be freed */
#define _SSL_get_ctx_pkey_base64(a) _SSL_get_ctx_obj_base64(a, 0)
#define _SSL_get_ctx_prkey_base64(a) _SSL_get_ctx_obj_base64(a, 1)
#define _SSL_get_ctx_x509_base64(a) _SSL_get_ctx_obj_base64(a, 2)
/*int _SSL_verify_x509(X509 *x509);*/
#endif

View File

@@ -1323,6 +1323,7 @@ static char * const pevt_connect_help[] = {
};
static char * const pevt_sconnect_help[] = {
"PID"
};
static char * const pevt_generic_nick_help[] = {

View File

@@ -787,8 +787,8 @@ n2
Stop Connection
XP_TE_STOPCONNECT
pevt_sconnect_help
%C23*%O$tStopped previous connection attempt
0
%C23*%O$tStopped previous connection attempt (%C24$1%O)
1
Topic
XP_TE_TOPIC

View File

@@ -56,9 +56,6 @@
#ifdef USE_OPENSSL
#include <openssl/bn.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#ifndef WIN32
#include <netinet/in.h>
#endif
@@ -1553,52 +1550,3 @@ strftime_utf8 (char *dest, gsize destsize, const char *format, time_t time)
g_date_free (date);
return result;
}
static void
three_to_four (char *from, char *to)
{
static const char tab64[64]=
{
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
to[0] = tab64 [ (from[0] >> 2) & 63 ];
to[1] = tab64 [ ((from[0] << 4) | (from[1] >> 4)) & 63 ];
to[2] = tab64 [ ((from[1] << 2) | (from[2] >> 6)) & 63 ];
to[3] = tab64 [ from[2] & 63 ];
};
void
base64_encode (char *to, char *from, unsigned int len)
{
while (len >= 3)
{
three_to_four (from, to);
len -= 3;
from += 3;
to += 4;
}
if (len)
{
char three[3] = {0,0,0};
unsigned int i;
for (i = 0; i < len; i++)
{
three[i] = *from++;
}
three_to_four (three, to);
if (len == 1)
{
to[2] = to[3] = '=';
}
else if (len == 2)
{
to[3] = '=';
}
to += 4;
};
to[0] = 0;
}

View File

@@ -80,6 +80,4 @@ char *encode_sasl_pass_plain (char *user, char *pass);
char *challengeauth_response (const char *username, const char *password, const char *challenge);
size_t strftime_validated (char *dest, size_t destsize, const char *format, const struct tm *time);
gsize strftime_utf8 (char *dest, gsize destsize, const char *format, time_t time);
void base64_encode (char *to, char *from, unsigned int len);
#endif