1
0
forked from bip/bip

Compare commits

..

3 Commits

Author SHA1 Message Date
Loïc Gomez
65fdde9e21 Also reload SSL context on bip reload, allowing for SSL cert updates 2024-02-07 18:08:26 +09:00
Loïc Gomez
ee9478e7ce Allow checking ssl files are readable (check_ssl_files) 2024-02-07 18:08:25 +09:00
Loïc Gomez
24365e72e8 Move SSL context code to get/set_ssl_context methods 2024-02-07 18:07:36 +09:00
14 changed files with 170 additions and 183 deletions

View File

@ -3,7 +3,7 @@ if COND_WANT_TESTS
endif endif
SUBDIRS = src . $(MAYBE_TESTS) SUBDIRS = src . $(MAYBE_TESTS)
dist_man_MANS = bip.1 bip.conf.5 bipmkpw.1 bipgenconfig.1 dist_man_MANS = bip.1 bip.conf.5 bipmkpw.1
examplesdir = $(prefix)/share/doc/bip/examples/ examplesdir = $(prefix)/share/doc/bip/examples/
dist_examples_DATA = samples/bip.conf samples/bip.vim dist_examples_DATA = samples/bip.conf samples/bip.vim

View File

@ -146,7 +146,7 @@ Defines the delay between each logfiles sync to the disk. Must be a non null
positive integer. positive integer.
.TP .TP
\fBreconn_timer\fP (default: \fB30\fP) \fBreconn_timer\fP (default: \fB120\fP)
Defines the initial delay (in seconds) before a reconnection attempt. Defines the initial delay (in seconds) before a reconnection attempt.
The delay increases with the number of attempts: The delay increases with the number of attempts:
delay = reconn_timer * number of attempts delay = reconn_timer * number of attempts
@ -227,7 +227,7 @@ This option should of course not be enabled if \fBbacklog_lines\fP is 0 !
If you still want to do so, don't forget to \fB/BIP BLRESET\fP sometimes. If you still want to do so, don't forget to \fB/BIP BLRESET\fP sometimes.
.TP .TP
\fBbacklog_lines\fP (default: \fB0\fP) \fBbacklog_lines\fP (default: \fB10\fP)
If set to 0, BIP will replay all the logs since last client disconnect. Else, If set to 0, BIP will replay all the logs since last client disconnect. Else,
it'll replay exactly \fBbacklog_lines\fP lines on each channel and privates. it'll replay exactly \fBbacklog_lines\fP lines on each channel and privates.
Be aware that BIP will replay \fBbacklog_lines\fP lines of all privates, even Be aware that BIP will replay \fBbacklog_lines\fP lines of all privates, even

View File

@ -86,7 +86,7 @@
# Sets the initial delay (in seconds) before a reconnection attempt. # Sets the initial delay (in seconds) before a reconnection attempt.
# The delay increases with the number of attempts: # The delay increases with the number of attempts:
# delay = reconn_timer * number of attempts # delay = reconn_timer * number of attempts
#reconn_timer = 30; #reconn_timer = 120;
# Network definition, a name and server info # Network definition, a name and server info
#network { #network {

View File

@ -119,7 +119,7 @@ my %optdesc = (
'optional' => 1, 'optional' => 1,
'depends' => 'log', 'depval' => 'true', 'depends' => 'log', 'depval' => 'true',
'desc' => 'Do you want to activate backlog {play back logs} system ?' }, 'desc' => 'Do you want to activate backlog {play back logs} system ?' },
'backlog_lines' => { 'type' => 'i', 'adv' => 0, 'default' => '0', 'backlog_lines' => { 'type' => 'i', 'adv' => 0, 'default' => '10',
'optional' => 1, 'optional' => 1,
'depends' => 'backlog', 'depval' => 'true', 'depends' => 'backlog', 'depval' => 'true',
'desc' => 'How much line do you want bip to play back upon client connect' . 'desc' => 'How much line do you want bip to play back upon client connect' .

View File

@ -67,7 +67,6 @@ void conf_die(bip_t *bip, char *fmt, ...);
static char *get_tuple_pvalue(list_t *tuple_l, int lex); static char *get_tuple_pvalue(list_t *tuple_l, int lex);
void bip_notify(struct link_client *ic, char *fmt, ...); void bip_notify(struct link_client *ic, char *fmt, ...);
void adm_list_connections(struct link_client *ic, struct bipuser *bu); void adm_list_connections(struct link_client *ic, struct bipuser *bu);
struct link *find_link(const char *link_name, struct bipuser *bu);
void free_conf(list_t *l); void free_conf(list_t *l);
@ -374,45 +373,6 @@ static int add_network(bip_t *bip, list_t *data)
return 1; return 1;
} }
void adm_bip_jump(struct link_client *ic, const char *conn_name,
int reset_timer)
{
struct link *lnk;
if (conn_name) {
lnk = find_link(conn_name, LINK(ic)->user);
if (!lnk) {
bip_notify(ic, "-- Cannot find connection named %s",
conn_name);
return;
}
goto do_the_jump;
} else {
lnk = LINK(ic);
}
do_the_jump:
if (!lnk->l_server) {
if (reset_timer) {
lnk->recon_timer = 0;
bip_notify(ic,
"-- %s is not connected, "
"timer has been reset, please wait",
conn_name);
return;
}
int timer = (lnk->recon_timer ? lnk->recon_timer : 0);
bip_notify(ic,
"-- %s is not connected, "
"please wait for reconnect (%ds)",
conn_name, timer);
return;
}
WRITE_LINE1(CONN(lnk->l_server), NULL, "QUIT", "jumpin' jumpin'");
connection_close(CONN(lnk->l_server));
bip_notify(ic, "-- Jumping to next server on %s", conn_name);
}
void adm_bip_delconn(bip_t *bip, struct link_client *ic, const char *conn_name) void adm_bip_delconn(bip_t *bip, struct link_client *ic, const char *conn_name)
{ {
struct bipuser *user = LINK(ic)->user; struct bipuser *user = LINK(ic)->user;
@ -704,31 +664,6 @@ static int add_connection(bip_t *bip, struct bipuser *user, list_t *data)
return 1; return 1;
} }
struct link *find_link(const char *link_name, struct bipuser *bu)
{
list_iterator_t it;
// Immediately return NULL when bip user (bu) is NULL
if (!bu) {
return NULL;
}
for (list_it_init(&_bip->link_list, &it); list_it_item(&it);
list_it_next(&it)) {
struct link *lnk = list_it_item(&it);
if (!lnk)
continue;
// Only lookup in optionally specified user links
if (bu != lnk->user)
continue;
if (strcmp(link_name, lnk->name) == 0)
return lnk;
}
return NULL;
}
static char *get_tuple_pvalue(list_t *tuple_l, int lex) static char *get_tuple_pvalue(list_t *tuple_l, int lex)
{ {
struct tuple *t; struct tuple *t;
@ -2057,8 +1992,8 @@ void adm_bip_help(struct link_client *ic, int admin, const char *subhelp)
bip_notify(ic, "/BIP LIST networks|connections"); bip_notify(ic, "/BIP LIST networks|connections");
} }
bip_notify(ic, bip_notify(ic,
"/BIP JUMP [-f] [conn_name] # jump to next " "/BIP JUMP # jump to next server (in same "
"server (defaults to current network)"); "network)");
bip_notify(ic, bip_notify(ic,
"/BIP BLRESET [channel|query]# reset backlog " "/BIP BLRESET [channel|query]# reset backlog "
"(this connection only). Add -q flag and the " "(this connection only). Add -q flag and the "
@ -2111,14 +2046,8 @@ void adm_bip_help(struct link_client *ic, int admin, const char *subhelp)
" Removing a connection will cause " " Removing a connection will cause "
"its disconnection."); "its disconnection.");
} else if (strcasecmp(subhelp, "JUMP") == 0) { } else if (strcasecmp(subhelp, "JUMP") == 0) {
bip_notify(ic, "/BIP JUMP [-f] [conn_name]:"); bip_notify(ic, "/BIP JUMP :");
bip_notify(ic, " Jump to next server in current network."); bip_notify(ic, " Jump to next server in current network.");
bip_notify(ic,
" If [conn_name] is set, jump to next server in "
"[conn_name] network instead.");
bip_notify(ic,
" If [-f] flag is used, also resets reconnect "
"timer.");
} else if (strcasecmp(subhelp, "BLRESET") == 0) { } else if (strcasecmp(subhelp, "BLRESET") == 0) {
bip_notify(ic, "/BIP BLRESET :"); bip_notify(ic, "/BIP BLRESET :");
bip_notify(ic, " Reset backlog on this network."); bip_notify(ic, " Reset backlog on this network.");
@ -2298,16 +2227,12 @@ int adm_bip(bip_t *bip, struct link_client *ic, struct line *line, int privmsg)
bip_notify(ic, "-- Invalid INFO request"); bip_notify(ic, "-- Invalid INFO request");
} }
} else if (irc_line_elem_case_equals(line, privmsg + 1, "JUMP")) { } else if (irc_line_elem_case_equals(line, privmsg + 1, "JUMP")) {
if (irc_line_count(line) == privmsg + 2) { if (LINK(ic)->l_server) {
adm_bip_jump(ic, NULL, 0); WRITE_LINE1(CONN(LINK(ic)->l_server), NULL, "QUIT",
} else if (irc_line_count(line) == privmsg + 3) { "jumpin' jumpin'");
adm_bip_jump(ic, irc_line_elem(line, privmsg + 2), 0); connection_close(CONN(LINK(ic)->l_server));
} else if (irc_line_count(line) == privmsg + 4
&& irc_line_elem_equals(line, privmsg + 2, "-f")) {
adm_bip_jump(ic, irc_line_elem(line, privmsg + 3), 1);
} else {
bip_notify(ic, "-- Invalid usage (/BIP HELP JUMP)");
} }
bip_notify(ic, "-- Jumping to next server");
} else if (irc_line_elem_case_equals(line, privmsg + 1, "BLRESET")) { } else if (irc_line_elem_case_equals(line, privmsg + 1, "BLRESET")) {
if (irc_line_includes(line, privmsg + 2)) { if (irc_line_includes(line, privmsg + 2)) {
if (irc_line_elem_equals(line, privmsg + 2, "-q")) { if (irc_line_elem_equals(line, privmsg + 2, "-q")) {

View File

@ -68,6 +68,7 @@ void rlimit_cpu_reached(int i);
void rlimit_bigfile_reached(int i); void rlimit_bigfile_reached(int i);
void conf_die(bip_t *bip, char *fmt, ...); void conf_die(bip_t *bip, char *fmt, ...);
int fireup(bip_t *bip, FILE *conf); int fireup(bip_t *bip, FILE *conf);
int check_ssl_files(int failmode);
int do_pid_stuff(void); int do_pid_stuff(void);
static void usage(char *name) static void usage(char *name)
@ -143,6 +144,44 @@ static pid_t daemonize(void)
return getpid(); 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) int main(int argc, char **argv)
{ {
FILE *conf = NULL; FILE *conf = NULL;
@ -264,31 +303,8 @@ int main(int argc, char **argv)
#ifdef HAVE_LIBSSL #ifdef HAVE_LIBSSL
if (conf_css) { if (conf_css) {
int e; check_ssl_files(HARD_FAIL);
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);
}
}
#endif #endif
check_dir(conf_log_root, 1); check_dir(conf_log_root, 1);
@ -324,6 +340,22 @@ int main(int argc, char **argv)
/* re-open to allow logfile rotate */ /* re-open to allow logfile rotate */
log_file_setup(); 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; return 1;
} }

View File

@ -34,6 +34,7 @@ extern char *conf_client_ciphers;
extern char *conf_client_dh_file; extern char *conf_client_dh_file;
static int SSLize(connection_t *cn, int *nc); static int SSLize(connection_t *cn, int *nc);
static SSL_CTX *SSL_init_context(char *ciphers); static SSL_CTX *SSL_init_context(char *ciphers);
SSL_CTX *get_ssl_context(void);
/* SSH like trust management */ /* SSH like trust management */
int link_add_untrusted(void *ls, X509 *cert); int link_add_untrusted(void *ls, X509 *cert);
#endif #endif
@ -1206,6 +1207,77 @@ static int ctx_set_dh(SSL_CTX *ctx)
return 1; 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 #endif
connection_t *accept_new(connection_t *cn) connection_t *accept_new(connection_t *cn)
@ -1241,52 +1313,11 @@ connection_t *accept_new(connection_t *cn)
conn->client = 1; conn->client = 1;
#ifdef HAVE_LIBSSL #ifdef HAVE_LIBSSL
if (cn->ssl) { if (cn->ssl) {
if (!sslctx)
set_ssl_context(SSLCTX_NO_REPLACE);
if (!sslctx) { if (!sslctx) {
mylog(LOG_DEBUG, connection_free(conn);
"No SSL context available for " return NULL;
"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");
} }
conn->ssl_h = SSL_new(sslctx); conn->ssl_h = SSL_new(sslctx);

View File

@ -60,6 +60,9 @@
#define SSL_CHECK_NONE (0) #define SSL_CHECK_NONE (0)
#define SSL_CHECK_BASIC (1) #define SSL_CHECK_BASIC (1)
#define SSL_CHECK_CA (2) #define SSL_CHECK_CA (2)
#define SSLCTX_NO_REPLACE 0
#define SSLCTX_FORCE_UPDATE 1
#endif #endif
struct connecting_data; struct connecting_data;
@ -113,4 +116,9 @@ uint16_t connection_localport(connection_t *cn);
uint16_t connection_remoteport(connection_t *cn); uint16_t connection_remoteport(connection_t *cn);
char *connection_localip(connection_t *cn); char *connection_localip(connection_t *cn);
char *connection_remoteip(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 #endif

View File

@ -19,7 +19,7 @@
#define DEFAULT_BACKLOG 1 #define DEFAULT_BACKLOG 1
#define DEFAULT_ALWAYS_BACKLOG 0 #define DEFAULT_ALWAYS_BACKLOG 0
#define DEFAULT_BL_MSG_ONLY 0 #define DEFAULT_BL_MSG_ONLY 0
#define DEFAULT_BACKLOG_LINES 0 #define DEFAULT_BACKLOG_LINES 10
#define DEFAULT_BACKLOG_TIMESTAMP BLTSTime #define DEFAULT_BACKLOG_TIMESTAMP BLTSTime
#define DEFAULT_BLRESET_ON_TALK 0 #define DEFAULT_BLRESET_ON_TALK 0
#define DEFAULT_BLRESET_CONNECTION 0 #define DEFAULT_BLRESET_CONNECTION 0
@ -29,6 +29,6 @@
#define DEFAULT_LOG_LEVEL LOG_INFO #define DEFAULT_LOG_LEVEL LOG_INFO
#define DEFAULT_LOG_FORMAT "%u/%n/%Y-%m/%c.%d.log" #define DEFAULT_LOG_FORMAT "%u/%n/%Y-%m/%c.%d.log"
#define DEFAULT_BIP_USE_NOTICE 0 #define DEFAULT_BIP_USE_NOTICE 0
#define DEFAULT_RECONN_TIMER 30 #define DEFAULT_RECONN_TIMER 120
#endif /* DEFAULTS_H */ #endif /* DEFAULTS_H */

View File

@ -33,7 +33,6 @@
extern int sighup; extern int sighup;
extern bip_t *_bip; extern bip_t *_bip;
static int irc_generic_error(struct link_server *server, struct line *line);
static int irc_join(struct link_server *server, struct line *line); static int irc_join(struct link_server *server, struct line *line);
static int irc_part(struct link_server *server, struct line *line); static int irc_part(struct link_server *server, struct line *line);
static int irc_mode(struct link_server *server, struct line *line); static int irc_mode(struct link_server *server, struct line *line);
@ -626,10 +625,6 @@ int irc_dispatch_server(bip_t *bip, struct link_server *server,
ret = irc_quit(server, line); ret = irc_quit(server, line);
} else if (irc_line_elem_equals(line, 0, "NICK")) { } else if (irc_line_elem_equals(line, 0, "NICK")) {
ret = irc_nick(server, line); ret = irc_nick(server, line);
} else if (irc_line_is_error(line)) {
// IRC errors catchall (for unhandled ones)
// logs error to bip.log
ret = irc_generic_error(server, line);
} }
if (ret == OK_COPY) { if (ret == OK_COPY) {
@ -1453,17 +1448,6 @@ static int origin_is_me(struct line *l, struct link_server *server)
return 0; return 0;
} }
static int irc_generic_error(struct link_server *server, struct line *line)
{
if (irc_line_count(line) == 4)
mylog(LOG_INFO, "[%s] IRC error: %s", LINK(server)->name,
irc_line_elem(line, 3));
else
mylog(LOG_INFO, "[%s] IRC error: %s", LINK(server)->name,
irc_line_to_string(line));
return OK_COPY;
}
static int irc_join(struct link_server *server, struct line *line) static int irc_join(struct link_server *server, struct line *line)
{ {
char *s_nick; char *s_nick;

View File

@ -143,12 +143,6 @@ void irc_line_drop(struct line *line, int elem)
bip_cfree(array_drop(&line->words, elem)); bip_cfree(array_drop(&line->words, elem));
} }
int irc_line_is_error(struct line *line)
{
const char *irc_code = irc_line_elem(line, 0);
return (irc_code[0] == '4');
}
int irc_line_elem_equals(struct line *line, int elem, const char *cmp) int irc_line_elem_equals(struct line *line, int elem, const char *cmp)
{ {
return !strcmp(irc_line_elem(line, elem), cmp); return !strcmp(irc_line_elem(line, elem), cmp);

View File

@ -98,7 +98,6 @@ int irc_line_includes(struct line *line, int elem);
const char *irc_line_elem(struct line *line, int elem); const char *irc_line_elem(struct line *line, int elem);
int irc_line_count(struct line *line); int irc_line_count(struct line *line);
char *irc_line_pop(struct line *l); char *irc_line_pop(struct line *l);
int irc_line_is_error(struct line *line);
int irc_line_elem_equals(struct line *line, int elem, const char *cmp); int irc_line_elem_equals(struct line *line, int elem, const char *cmp);
int irc_line_elem_case_equals(struct line *line, int elem, const char *cmp); int irc_line_elem_case_equals(struct line *line, int elem, const char *cmp);
void irc_line_drop(struct line *line, int elem); void irc_line_drop(struct line *line, int elem);

View File

@ -35,3 +35,15 @@ void assert_path_exists(char *path)
if (stat(path, &st_buf) != 0) if (stat(path, &st_buf) != 0)
fatal("Path %s doesn't exist (%s)", path, strerror(errno)); 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); char *default_path(const char *biphome, const char *filename, const char *desc);
/* exit program if path doesn't exist */ /* exit program if path doesn't exist */
void assert_path_exists(char *path); void assert_path_exists(char *path);
/* return 1 if path exists, 0 otherwise */
int check_path_exists(char *path);
#endif #endif