Compare commits

...

11 Commits

Author SHA1 Message Date
Loïc Gomez 40ac98214a
Add missing defines for SOFT_FAIL/HARD_FAIL in util.h
This is for these previous 2 commits:
  * 428c1b6 Allow checking ssl files are readable (check_ssl_files)
  * a03b123 Also reload SSL context on bip reload, allowing for SSL cert updates
2024-03-20 23:27:33 +09:00
Loïc Gomez a03b12319a
Also reload SSL context on bip reload, allowing for SSL cert updates
- on BIP reload, check if SSL files are readable, and try to load new
  SSL context.
- on success only, update SSL context for new client connections

This allows for SSL certificate/key updates on /BIP reload or SIGHUP.

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:42:01 +09:00
Loïc Gomez 428c1b6173
Allow checking ssl files are readable (check_ssl_files)
- adds a new check_path_exists() method to path_util
- move code checking if SSL-related files exist with assertions (thus
  causing a fatal error) to a new check_ssl_files() method, allowing for
  soft or hard fail modes

This will allow for non-fatal checks of SSL files existence on reload.

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:33:31 +09:00
Loïc Gomez f2443aaf23
Move SSL context code to get/set_ssl_context methods
- Move code setting up SSL context from accept_new() to new
  get/set_ssl_context() methods
- set_ssl_context() will allow setting context only if not already set,
  or force re-setting context even if already present
- set_ssl_context() will return 1 if SSL context has been set
- if getting the new SSL context fails, set_ssl_context() will not break
  current SSL context

This is preparatory work for reloading SSL certificates/key on BIP
reload.

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:29:05 +09:00
Loïc Gomez 400b0d66de
Fix deprecation notices for OpenSSL 3 and failure to build on GCC12
Tested with libssl 3.0.10-1ubuntu2.1 (Ubuntu 23.10) and 1.1.1f-1ubuntu2.20
(Ubuntu 20.04).

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:27:20 +09:00
Loïc Gomez e643cd4944
Fix endless loop on /BIP LIST connections with long on_connect_send
- when the line to send to IRC was over a specific limit, the code was
  not moving to the next item in the on_connect_send list after sending
  the text to the client
- this change also adds a hard limit of 10 on_connect_send to display
  for each connection

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:19:28 +09:00
Loïc Gomez 8f3b313776
Fix file descriptor leak on erroring client disconnects
- Client connections in error also need to be closed/freed
- This fixes a file descriptor leak that would result in a bip crash

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:18:09 +09:00
Loïc Gomez d49f135370
Move strict gcc compilation flags to configure.ac to avoid breaking incompatible environments
- Move gcc hardening/warning/advanced warnings flags to configure.ac to avoid
  breaking incompatible environments
- Use -Warith-conversion only with gcc 10 and later
- Keep -Wundef -Wpedantic enabled globally

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:16:25 +09:00
Loïc Gomez 5f054bdded
Fix bipmkpw compilation issues per external variables
- Add missing extern stanza to global variables

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:10:37 +09:00
Loïc Gomez 76c9ee11de
Handle CAP requests from clients, reply with no capabilities
Some clients require the server to handle CAP requests (IRCv3).
This adds basic support for CAP requests during the authentication
phase, sending no capabilities on CAP LS or CAP LIST, and CAP NAK
with all requested capabilities listed on CAP REQ.

This will be a base for adding capabilities later.

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 22:07:43 +09:00
Loïc Gomez b810ba0f77
Add method to stringify a line struct, skipping first N elements
- adds an internal method _irc_line_to_string with current irc_line_to_string
  code, adding capability to skip the first N elements
- call this internal method from irc_line_to_string with N=0
- call this internal method from new irc_line_to_string_skip method

Signed-off-by: Loïc Gomez <bip@animanova.fr>
2024-03-20 21:53:45 +09:00
13 changed files with 307 additions and 84 deletions

View File

@ -94,6 +94,74 @@ if test "$ap_cv_cc_pie" = "yes"; then
enable_pie=yes
fi
AC_CACHE_CHECK([whether $CC accepts hardening flags], [ap_cv_cc_hardening], [
save_CFLAGS=$CFLAGS
save_LDFLAGS=$LDFLAGS
CFLAGS="$CFLAGS -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code"
AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])],
[ap_cv_cc_hardening=yes],
[ap_cv_cc_hardening=no],
[ap_cv_cc_hardening=yes]
)
CFLAGS=$save_CFLAGS
])
if test "$ap_cv_cc_hardening" = "yes"; then
CFLAGS="$CFLAGS -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fstack-clash-protection -Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code"
enable_cc_hardening=yes
fi
AC_CACHE_CHECK([whether $CC accepts some warning flags], [ap_cv_cc_warnings], [
save_CFLAGS=$CFLAGS
save_LDFLAGS=$LDFLAGS
CFLAGS="$CFLAGS -Wformat-overflow=2 -Wformat-truncation=2 -Wtrampolines -Warray-bounds=2 -Wimplicit-fallthrough=3 -Wtraditional-conversion -Wshift-overflow=2 -Wstringop-overflow=4 -Wlogical-op -Wduplicated-cond -Wduplicated-branches -Wformat-signedness -Wstack-usage=1000000 -Wcast-align=strict"
AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])],
[ap_cv_cc_warnings=yes],
[ap_cv_cc_warnings=no],
[ap_cv_cc_warnings=yes]
)
CFLAGS=$save_CFLAGS
])
if test "$ap_cv_cc_warnings" = "yes"; then
CFLAGS="$CFLAGS -Wformat-overflow=2 -Wformat-truncation=2 -Wtrampolines -Warray-bounds=2 -Wimplicit-fallthrough=3 -Wtraditional-conversion -Wshift-overflow=2 -Wstringop-overflow=4 -Wlogical-op -Wduplicated-cond -Wduplicated-branches -Wformat-signedness -Wstack-usage=1000000 -Wcast-align=strict"
enable_cc_warnings=yes
fi
AC_CACHE_CHECK([whether $CC accepts some supplementary warning flags], [ap_cv_cc_warnings2], [
save_CFLAGS=$CFLAGS
save_LDFLAGS=$LDFLAGS
CFLAGS="$CFLAGS -Wformat=2 -Wformat-security -Wnull-dereference -Wstack-protector -Walloca -Wvla -Wcast-qual -Wconversion -Wshadow -Wstrict-overflow=4 -Wstrict-prototypes -Wswitch-default -Wswitch-enum"
AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])],
[ap_cv_cc_warnings2=yes],
[ap_cv_cc_warnings2=no],
[ap_cv_cc_warnings2=yes]
)
CFLAGS=$save_CFLAGS
])
if test "$ap_cv_cc_warnings2" = "yes"; then
CFLAGS="$CFLAGS -Wformat=2 -Wformat-security -Wnull-dereference -Wstack-protector -Walloca -Wvla -Wcast-qual -Wconversion -Wshadow -Wstrict-overflow=4 -Wstrict-prototypes -Wswitch-default -Wswitch-enum"
enable_cc_warnings2=yes
fi
AC_CACHE_CHECK([whether $CC accepts -Warith-conversion flag], [ap_cv_cc_warith], [
save_CFLAGS=$CFLAGS
save_LDFLAGS=$LDFLAGS
CFLAGS="$CFLAGS -Warith-conversion"
AC_RUN_IFELSE([AC_LANG_SOURCE([[static int foo[30000]; int main () { return 0; }]])],
[ap_cv_cc_warith=yes],
[ap_cv_cc_warith=no],
[ap_cv_cc_warith=yes]
)
CFLAGS=$save_CFLAGS
])
if test "$ap_cv_cc_warith" = "yes"; then
CFLAGS="$CFLAGS -Warith-conversion"
enable_warith_conversion=yes
fi
PKG_CHECK_MODULES([CHECK], [check >= 0.9.6], [enable_tests=yes], [enable_tests=no])
AM_CONDITIONAL([COND_WANT_TESTS], [test "$enable_tests" = yes])

View File

@ -36,9 +36,4 @@ bipmkpw_LDADD = libbip.a libbiplex.a $(OPENSSL_LIBS)
AM_YFLAGS= -d
BUILT_SOURCES = conf.c conf.h lex.c
AM_CFLAGS=-Wall -Wextra -Werror \
-O2 \
-D_FORTIFY_SOURCE=2 \
-fstack-protector-strong -fstack-clash-protection \
-Wl,-z,relro -Wl,-z,now -Wl,-z,noexecstack -Wl,-z,separate-code \
-Wpedantic -Wformat=2 -Wformat-overflow=2 -Wformat-truncation=2 -Wformat-security -Wnull-dereference -Wstack-protector -Wtrampolines -Walloca -Wvla -Warray-bounds=2 -Wimplicit-fallthrough=3 -Wtraditional-conversion -Wshift-overflow=2 -Wcast-qual -Wstringop-overflow=4 -Wconversion -Warith-conversion -Wlogical-op -Wduplicated-cond -Wduplicated-branches -Wformat-signedness -Wshadow -Wstrict-overflow=4 -Wundef -Wstrict-prototypes -Wswitch-default -Wswitch-enum -Wstack-usage=1000000 -Wcast-align=strict
AM_CFLAGS=-Wall -Wextra -Werror -Wundef -Wpedantic

View File

@ -1396,9 +1396,10 @@ void adm_print_connection(struct link_client *ic, struct link *lnk,
bufpos = buf;
list_iterator_t itocs;
int i = 0;
for (list_it_init(&lnk->on_connect_send, &itocs);
list_it_item(&itocs);) {
bufpos = bip_strcatf_fit(&remaining, bufpos, "%s",
list_it_item(&itocs) && i < 10; i++) {
bufpos = bip_strcatf_fit(&remaining, bufpos, " on_connect_send: %s",
(char *)list_it_item(&itocs));
if (!bufpos) {
// if oversized, print and reset
@ -1406,6 +1407,7 @@ void adm_print_connection(struct link_client *ic, struct link *lnk,
bip_notify(ic, "%s", buf);
remaining = LINE_SIZE_LIM;
bufpos = buf;
list_it_next(&itocs);
continue;
} else {
// if ok, go to next item

View File

@ -68,6 +68,7 @@ void rlimit_cpu_reached(int i);
void rlimit_bigfile_reached(int i);
void conf_die(bip_t *bip, char *fmt, ...);
int fireup(bip_t *bip, FILE *conf);
int check_ssl_files(int failmode);
int do_pid_stuff(void);
static void usage(char *name)
@ -143,6 +144,44 @@ static pid_t daemonize(void)
return getpid();
}
int check_ssl_files(int failmode)
{
int e;
struct stat fs;
if (!conf_ssl_certfile) {
conf_ssl_certfile = default_path(
conf_biphome, "bip.pem", "SSL certificate");
}
if (failmode == HARD_FAIL)
assert_path_exists(conf_ssl_certfile);
else if (!check_path_exists(conf_ssl_certfile))
return 0;
e = stat(conf_ssl_certfile, &fs);
if (e)
mylog(LOG_WARN,
"Unable to check PEM file, stat(%s): %s",
conf_ssl_certfile, strerror(errno));
else if ((fs.st_mode & S_IROTH) | (fs.st_mode & S_IWOTH))
mylog(LOG_ERROR,
"PEM file %s should not be world "
"readable / writable. Please fix the modes.",
conf_ssl_certfile);
if (conf_client_dh_file) {
if (failmode == HARD_FAIL) {
assert_path_exists(conf_client_dh_file);
} else if (!check_path_exists(conf_client_dh_file)) {
return 0;
}
}
/* all is well */
return 1;
}
int main(int argc, char **argv)
{
FILE *conf = NULL;
@ -264,31 +303,8 @@ int main(int argc, char **argv)
#ifdef HAVE_LIBSSL
if (conf_css) {
int e;
struct stat fs;
if (!conf_ssl_certfile) {
conf_ssl_certfile = default_path(
conf_biphome, "bip.pem", "SSL certificate");
}
assert_path_exists(conf_ssl_certfile);
e = stat(conf_ssl_certfile, &fs);
if (e)
mylog(LOG_WARN,
"Unable to check PEM file, stat(%s): "
"%s",
conf_ssl_certfile, strerror(errno));
else if ((fs.st_mode & S_IROTH) | (fs.st_mode & S_IWOTH))
mylog(LOG_ERROR,
"PEM file %s should not be world "
"readable / writable. Please fix the modes.",
conf_ssl_certfile);
if (conf_client_dh_file) {
assert_path_exists(conf_client_dh_file);
}
}
check_ssl_files(HARD_FAIL);
}
#endif
check_dir(conf_log_root, 1);
@ -324,6 +340,24 @@ int main(int argc, char **argv)
/* re-open to allow logfile rotate */
log_file_setup();
#ifdef HAVE_LIBSSL
/*
* reload SSL context if server-side SSL is enabled and SSL files
* seem accessible.
*/
if (conf_css) {
if (check_ssl_files(SOFT_FAIL)) {
if (set_ssl_context(SSLCTX_FORCE_UPDATE) == 1)
mylog(LOG_DEBUG, "SSL context has been updated");
else
mylog(LOG_DEBUG, "SSL context has not been updated");
} else {
mylog(LOG_ERROR, "Unable to update SSL context, "
"file checks failed");
}
}
#endif
}
return 1;
}

View File

@ -23,9 +23,9 @@
#include "util.h"
#include "md5.h"
int conf_log_level;
FILE *conf_global_log_file;
int conf_log_system;
extern int conf_log_level;
extern FILE *conf_global_log_file;
extern int conf_log_system;
void bipmkpw_fatal(char *msg, char *err)
{

View File

@ -34,6 +34,7 @@ extern char *conf_client_ciphers;
extern char *conf_client_dh_file;
static int SSLize(connection_t *cn, int *nc);
static SSL_CTX *SSL_init_context(char *ciphers);
SSL_CTX *get_ssl_context(void);
/* SSH like trust management */
int link_add_untrusted(void *ls, X509 *cert);
#endif
@ -62,7 +63,7 @@ void connection_close(connection_t *cn)
{
mylog(LOG_DEBUG, "Connection close asked. FD:%d (status: %d)",
(long)cn->handle, cn->connected);
if (cn->connected != CONN_DISCONN && cn->connected != CONN_ERROR) {
if (cn->connected != CONN_DISCONN) {
cn->connected = CONN_DISCONN;
if (close(cn->handle) == -1)
mylog(LOG_WARN, "Error on socket close: %s",
@ -1171,6 +1172,7 @@ static connection_t *connection_init(int anti_flood, int ssl, time_t timeout,
static int ctx_set_dh(SSL_CTX *ctx)
{
/* Return ephemeral DH parameters. */
#if OPENSSL_VERSION_NUMBER < 0x30000000L /* 3.0.0 */
DH *dh = NULL;
FILE *f;
long ret;
@ -1203,9 +1205,104 @@ static int ctx_set_dh(SSL_CTX *ctx)
ERR_error_string(ERR_get_error(), NULL));
return 0;
}
#else
BIO *pbio = BIO_new_file(conf_client_dh_file, "r");
if (!pbio) {
mylog(LOG_ERROR, "Unable to open DH parameters, BIO_new_file(%s): %s",
conf_client_dh_file, ERR_error_string(ERR_get_error(), NULL));
return 0;
}
EVP_PKEY *param = PEM_read_bio_Parameters(pbio, NULL);
BIO_free(pbio);
if (!param) {
mylog(LOG_ERROR, "TLS DH Error: PEM_read_bio_Parameters(%s): %s",
conf_client_dh_file, ERR_error_string(ERR_get_error(), NULL));
return 0;
}
if (SSL_CTX_set0_tmp_dh_pkey(ctx, param) != 1) {
EVP_PKEY_free(param);
mylog(LOG_ERROR, "TLS DH Error: SSL_CTX_set0_tmp_dh_pkey(%s): %s",
conf_client_dh_file, ERR_error_string(ERR_get_error(), NULL));
return 0;
}
#endif
mylog(LOG_DEBUG, "TLS: succesfully set up DH params %s",
conf_client_dh_file);
return 1;
}
int set_ssl_context(int force)
{
SSL_CTX *newctx = NULL;
if (sslctx) {
if (force == SSLCTX_FORCE_UPDATE) {
mylog(LOG_DEBUG, "SSL context already set, forcing update");
} else {
mylog(LOG_DEBUG, "SSL context is already set, not updating");
return 0;
}
} else {
mylog(LOG_DEBUG, "Initializing SSL context for accepted connections");
}
newctx = get_ssl_context();
if (!newctx) {
mylog(LOG_ERROR, "Failed to get new SSL context");
if (sslctx)
mylog(LOG_WARN, "Keeping old SSL context");
return 0;
}
sslctx = newctx;
return 1;
}
SSL_CTX *get_ssl_context(void)
{
SSL_CTX *newctx = NULL;
if (!(newctx = SSL_init_context(conf_client_ciphers))) {
mylog(LOG_ERROR, "SSL context initialization failed");
return NULL;
}
if (!conf_client_dh_file) {
// try with a default path but don't fail if it doesn't exist
conf_client_dh_file = default_path(conf_biphome, "dh.pem",
"DH parameters");
struct stat st_buf;
if (stat(conf_client_dh_file, &st_buf) != 0) {
free(conf_client_dh_file);
conf_client_dh_file = NULL;
}
}
if (conf_client_dh_file) {
if (!ctx_set_dh(newctx)) {
SSL_CTX_free(newctx);
mylog(LOG_ERROR, "SSL Unable to load DH parameters");
return NULL;
}
}
mylog(LOG_DEBUG, "Loading SSL cert from %s", conf_ssl_certfile);
if (!SSL_CTX_use_certificate_chain_file(newctx, conf_ssl_certfile)) {
mylog(LOG_WARN, "SSL: Unable to load certificate file");
SSL_CTX_free(newctx);
return NULL;
}
mylog(LOG_DEBUG, "Loading SSL key from %s", conf_ssl_certfile);
if (!SSL_CTX_use_PrivateKey_file(newctx, conf_ssl_certfile,
SSL_FILETYPE_PEM)) {
mylog(LOG_WARN, "SSL: Unable to load key file");
SSL_CTX_free(newctx);
return NULL;
}
return newctx;
}
#endif
connection_t *accept_new(connection_t *cn)
@ -1241,52 +1338,11 @@ connection_t *accept_new(connection_t *cn)
conn->client = 1;
#ifdef HAVE_LIBSSL
if (cn->ssl) {
if (!sslctx)
set_ssl_context(SSLCTX_NO_REPLACE);
if (!sslctx) {
mylog(LOG_DEBUG,
"No SSL context available for "
"accepted connections. "
"Initializing...");
if (!(sslctx = SSL_init_context(conf_client_ciphers))) {
mylog(LOG_ERROR,
"SSL context initialization "
"failed");
connection_free(conn);
return NULL;
}
if (!conf_client_dh_file) {
// try with a default path but don't fail if it
// doesn't exist
conf_client_dh_file =
default_path(conf_biphome, "dh.pem",
"DH parameters");
struct stat st_buf;
if (stat(conf_client_dh_file, &st_buf) != 0) {
free(conf_client_dh_file);
conf_client_dh_file = NULL;
}
}
if (conf_client_dh_file) {
if (!ctx_set_dh(sslctx)) {
mylog(LOG_ERROR,
"SSL Unable to load DH "
"parameters");
connection_free(conn);
return NULL;
}
}
if (!SSL_CTX_use_certificate_chain_file(
sslctx, conf_ssl_certfile))
mylog(LOG_WARN,
"SSL: Unable to load "
"certificate file");
if (!SSL_CTX_use_PrivateKey_file(sslctx,
conf_ssl_certfile,
SSL_FILETYPE_PEM))
mylog(LOG_WARN, "SSL: Unable to load key file");
connection_free(conn);
return NULL;
}
conn->ssl_h = SSL_new(sslctx);

View File

@ -60,6 +60,9 @@
#define SSL_CHECK_NONE (0)
#define SSL_CHECK_BASIC (1)
#define SSL_CHECK_CA (2)
#define SSLCTX_NO_REPLACE 0
#define SSLCTX_FORCE_UPDATE 1
#endif
struct connecting_data;
@ -113,4 +116,9 @@ uint16_t connection_localport(connection_t *cn);
uint16_t connection_remoteport(connection_t *cn);
char *connection_localip(connection_t *cn);
char *connection_remoteip(connection_t *cn);
#ifdef HAVE_LIBSSL
/* Set SSL context, force update if already set and force is 1
* Return 1 if SSL context has been set */
int set_ssl_context(int force);
#endif
#endif

View File

@ -1030,6 +1030,31 @@ static int irc_cli_pass(bip_t *bip, struct link_client *ic, struct line *line)
return OK_FORGET;
}
static int irc_cli_cap(bip_t *bip, struct link_client *ic, struct line *line)
{
if (irc_line_count(line) < 2)
return ERR_PROTOCOL;
/* When we decide to enable actual capabilities, we'll have to handle CAP
* version, add a flag cap_started or similar, and handle CAP END.
*/
if (irc_line_elem_equals(line, 1, "LS")) {
WRITE_LINE2(CONN(ic), NULL, "CAP", "*", "LS");
} else if (irc_line_elem_equals(line, 1, "LIST")) {
WRITE_LINE3(CONN(ic), NULL, "CAP", "*", "LIST", "");
} else if (irc_line_elem_equals(line, 1, "REQ")) {
char *caps = irc_line_to_string_skip(line, 2);
if (caps) {
caps++;
WRITE_LINE3(CONN(ic), NULL, "CAP", "*", "NAK", caps);
}
}
if ((ic->state & IRCC_READY) == IRCC_READY)
return irc_cli_startup(bip, ic, line);
return OK_FORGET;
}
static int irc_cli_quit(struct link_client *ic, struct line *line)
{
(void)ic;
@ -1408,6 +1433,8 @@ static int irc_dispatch_logging_client(bip_t *bip, struct link_client *ic,
return irc_cli_user(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "PASS")) {
return irc_cli_pass(bip, ic, line);
} else if (irc_line_elem_equals(line, 0, "CAP")) {
return irc_cli_cap(bip, ic, line);
}
return OK_FORGET;
}

View File

@ -16,6 +16,8 @@
#include "line.h"
#include "util.h"
char *_irc_line_to_string(struct line *l, int skip_first);
// TODO resolve assuming signed overflow does not occur when changing X +- C1
// cmp C2 to X cmp C2 -+ C1
#pragma GCC diagnostic ignored "-Wstrict-overflow"
@ -76,6 +78,19 @@ void irc_line_append(struct line *l, const char *s)
}
char *irc_line_to_string(struct line *l)
{
return _irc_line_to_string(l, 0);
}
char *irc_line_to_string_skip(struct line *l, int skip_first)
{
if (skip_first >= irc_line_count(l)) {
return NULL;
}
return _irc_line_to_string(l, skip_first);
}
char *_irc_line_to_string(struct line *l, int skip_first)
{
size_t len = 0;
int i;
@ -83,7 +98,7 @@ char *irc_line_to_string(struct line *l)
if (l->origin)
len = strlen(l->origin) + 2;
for (i = 0; i < array_count(&l->words); i++)
for (i = skip_first; i < array_count(&l->words); i++)
len += strlen(array_get(&l->words, i)) + 1;
len += 1; /* remove one trailing space and add \r\n */
len++; /* last args ":" */
@ -95,7 +110,7 @@ char *irc_line_to_string(struct line *l)
strcat(ret, l->origin);
strcat(ret, " ");
}
for (i = 0; i < array_count(&l->words) - 1; i++) {
for (i = skip_first; i < array_count(&l->words) - 1; i++) {
strcat(ret, array_get(&l->words, i));
strcat(ret, " ");
}

View File

@ -90,6 +90,7 @@ void irc_line_write(struct line *l, connection_t *c);
void irc_line_append(struct line *l, const char *s);
struct line *irc_line_new_from_string(char *str);
char *irc_line_to_string(struct line *l);
char *irc_line_to_string_skip(struct line *l, int skip_first);
char *irc_line_to_string_to(struct line *line, char *nick);
void irc_line_free(struct line *l);
struct line *irc_line_dup(struct line *line);

View File

@ -35,3 +35,15 @@ void assert_path_exists(char *path)
if (stat(path, &st_buf) != 0)
fatal("Path %s doesn't exist (%s)", path, strerror(errno));
}
int check_path_exists(char *path)
{
struct stat st_buf;
if (stat(path, &st_buf) != 0) {
mylog(LOG_WARN, "Path %s doesn't exist (%s)", path, strerror(errno));
return 0;
} else {
return 1;
}
}

View File

@ -18,5 +18,7 @@
char *default_path(const char *biphome, const char *filename, const char *desc);
/* exit program if path doesn't exist */
void assert_path_exists(char *path);
/* return 1 if path exists, 0 otherwise */
int check_path_exists(char *path);
#endif

View File

@ -31,6 +31,9 @@
#define HASH_NOCASE 1
#define HASH_DEFAULT 0
#define SOFT_FAIL 0
#define HARD_FAIL 1
void mylog(int level, char *fmt, ...);
void _mylog(int level, char *fmt, va_list ap);
void fatal(char *fmt, ...);