/*
headers.h
This file is part of GNU Anubis.
Copyright (C) 2001-2024 The Anubis Team.
GNU Anubis 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 3 of the License, or (at your
option) any later version.
GNU Anubis 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 GNU Anubis. If not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
# define __attribute__(x)
#endif
#ifndef ANUBIS_PRINTFLIKE
# define ANUBIS_PRINTFLIKE(fmt,narg) \
__attribute__ ((__format__ (__printf__, fmt, narg)))
#endif
#ifndef ANUBIS_NORETURN
# define ANUBIS_NORETURN __attribute__((__noreturn__))
#endif
# if defined(HAVE_LIBGPGME) && defined(HAVE_GPGME_H) && !defined(NOGPG)
# define HAVE_GPG
# endif /* HAVE_LIBGPGME and HAVE_GPGME_H and not NOGPG */
# if defined(HAVE_LIBPCRE)
# if defined(HAVE_PCRE_H) || defined(HAVE_PCRE_PCRE_H)
# define HAVE_PCRE
# endif /* HAVE_PCRE_H or HAVE_PCRE_PCRE_H */
# endif /* HAVE_LIBPCRE */
# if defined(HAVE_LIBPAM) && defined(HAVE_LIBPAM_MISC)
# if defined(HAVE_SECURITY_PAM_APPL_H) && defined(HAVE_SECURITY_PAM_MISC_H)
# define HAVE_PAM
# endif /* HAVE_SECURITY_PAM_APPL_H and HAVE_SECURITY_PAM_MISC_H */
# endif /* HAVE_LIBPAM and HAVE_LIBPAM_MISC */
# if defined(HAVE_LIBWRAP) && defined(HAVE_TCPD_H)
# define USE_LIBWRAP
# endif /* HAVE_LIBWRAP and HAVE_TCPD_H */
#endif /* HAVE_CONFIG_H */
#include
#ifdef STDC_HEADERS
# include
# include
#endif /* STDC_HEADERS */
#ifdef HAVE_STRING_H
# include
#endif /* HAVE_STRING_H */
#ifdef HAVE_UNISTD_H
# include
#endif /* HAVE_UNISTD_H */
#ifdef HAVE_MEMORY_H
# include
#endif /* HAVE_MEMORY_H */
#include
#include
#include
#include
#include
#include
#ifdef HAVE_SYS_TYPES_H
# include
#endif /* HAVE_SYS_TYPES_H */
#ifdef HAVE_SYS_STAT_H
# include
#endif /* HAVE_SYS_STAT_H */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT)
# include
#endif /* HAVE_GETRLIMIT and HAVE_SETRLIMIT */
#ifdef HAVE_ARPA_INET_H
# include
#endif /* HAVE_ARPA_INET_H */
#if defined(USE_GNUTLS)
# include
# include
# define HAVE_TLS
#endif /* USE_GNUTLS */
#if defined(HAVE_TLS) || defined(HAVE_SSL)
# define USE_SSL
#else
# undef USE_SSL
#endif /* HAVE_TLS or HAVE_SSL */
#ifdef HAVE_PAM
# include
# include
#endif /* HAVE_PAM */
#if defined(WITH_GSASL)
# include
#endif
#include "gettext.h"
#ifdef ENABLE_NLS
# define _(String) gettext(String)
# ifdef HAVE_LOCALE_H
# include
# endif /* HAVE_LOCALE_H */
#else
# define _(String) (String)
#endif /* ENABLE_NLS */
#define N_(String) String
#ifdef WITH_GUILE
# include
#endif /* WITH_GUILE */
#ifdef HAVE_SYSEXITS_H
# include
#else
# define EX_OK 0
# define EX_USAGE 64
# define EX_UNAVAILABLE 69
# define EX_SOFTWARE 70
# define EX_TEMPFAIL 75
# define EX_PROTOCOL 76
# define EX_CONFIG 78
#endif
#include
#include "list.h"
#include "smtprepl.h"
#include
#ifndef INADDR_NONE
# define INADDR_NONE (unsigned long)0xffffffff
#endif /* not INADDR_NONE */
#ifndef INADDR_ANY
# define INADDR_ANY (unsigned long)0x00000000
#endif /* not INADDR_ANY */
#ifndef INADDR_LOOPBACK
# define INADDR_LOOPBACK (unsigned long)0x7f000001
#endif /* not INADDR_LOOPBACK */
#ifndef MAXPATHLEN
# ifdef PATH_MAX
# define MAXPATHLEN PATH_MAX
# else
# define MAXPATHLEN 1024
# endif /* PATH_MAX */
#endif /* not MAXPATHLEN */
#define MAXCLIENTS 50
#define LINEBUFFER 512
#define DATABUFFER 4096
#define DEFAULT_GLOBAL_RCFILE "/etc/anubisrc"
#define DEFAULT_LOCAL_RCFILE ".anubisrc"
#define DEFAULT_SSL_PEM "anubis.pem"
#define DEFAULT_SIGFILE ".signature"
#define DEFAULT_PIDFILE "anubis.pid"
#define BEGIN_TRIGGER "@@"
#define LF "\n"
#define CRLF "\r\n"
/* REGEX action methods */
#define NIL -1
#define COMMAND 0
#define HEADER 1
#define BODY 2
/* Tunnel methods */
#define CLIENT 0
#define SERVER 1
/* configuration file access */
#define CF_CLIENT 0x0001
#define CF_SUPERVISOR 0x0002
#define CF_INIT 0x0004
#define CF_ALL CF_INIT|CF_SUPERVISOR|CF_CLIENT
/* output modes */
#define SILENT 0
#define NORMAL 1
#define VERBOSE 2
#define DEBUG 3
/* logging level */
#define NONE 0
#define FAILS 1
#define ALL 2
typedef enum anubis_mode
{
anubis_transparent,
anubis_authenticate,
anubis_mda,
anubis_proxy
}
ANUBIS_MODE;
/* bit values for topt */
#define T_DISABLE_SYSLOG 0x00000001
#define T_SOCKS 0x00000002
#define T_SOCKS_V4 0x00000004
#define T_SOCKS_AUTH 0x00000008
#define T_FOREGROUND_INIT 0x00000010
#define T_FOREGROUND 0x00000020
#define T_DAEMON 0x00000040
#define T_STDINOUT 0x00000080
#define T_SSL 0x00000100
#define T_SSL_FINISHED 0x00000200
#define T_SSL_ONEWAY 0x00000400
#define T_SSL_CKCLIENT 0x00000800
#define T_NAMES 0x00001000
#define T_LOCAL_MTA 0x00002000
/* Not used (ex T_ALLOW_LOCAL_MTA) 0x00004000 */
#define T_TRANSLATION_MAP 0x00008000
#define T_DROP_UNKNOWN_USER 0x00010000
#define T_USER_NOTPRIVIL 0x00020000
#define T_STARTTLS 0x00040000
#define T_ESMTP_AUTH 0x00080000
#define T_NORC 0x00100000
#define T_ALTRC 0x00200000
#define T_CHECK_CONFIG 0x00400000
#define T_RELAX_PERM_CHECK 0x00800000
#define T_ENTIRE_BODY 0x01000000
#define T_SMTP_ERROR_CODES 0x02000000
#define T_TRACEFILE_SYS 0x04000000
#define T_TRACEFILE_USR 0x08000000
#define T_XELO 0x10000000
#define T_LOCATION_COLUMN 0x20000000
#define T_ESMTP_AUTH_DELAYED 0x40000000
#define T_PASSFD 0x80000000
/* Regexp modifiers */
/* Basic types */
#define R_EXACT 0x00000001
#define R_POSIX 0x00000002
#define R_PERLRE 0x00000004
/* Other modifiers */
#define R_BASIC 0x00000010
#define R_SCASE 0x00000020
#define R_TYPEMASK 0x0000000f
#define re_set_type(m,t) ((m) = ((m) & ~R_TYPEMASK) | ((t) & R_TYPEMASK))
#define re_typeof(m) ((m) & R_TYPEMASK)
#define re_set_flag(m,f) ((m) |= (f))
#define re_clear_flag(m,f) ((m) &= ~(f))
/* A special header used by Anubis to implement rules. */
#define X_ANUBIS_RULE_HEADER "\nRULE\n"
#define safe_strcpy(s, ct) \
(s[sizeof(s) - 1] = '\0', strncpy((char *)s, (char *)ct, sizeof(s) - 1))
typedef struct rc_regex RC_REGEX;
typedef struct assoc ASSOC;
typedef struct message_struct *MESSAGE;
#define xfree(p) do\
if (p) { \
free(p); \
p = NULL; \
}\
while (0)
#define xfree_pptr(p) do\
if (p) { \
free_pptr(p); \
p = NULL; \
}\
while (0)
#define ASSERT_MTA_CONFIG() \
do \
{ \
if (!(topt & T_LOCAL_MTA) && !session.mta) \
anubis_error (EX_CONFIG, 0, \
_("MTA has not been specified. " \
"Set the `remote-mta' or `local-mta'.")); \
} \
while (0)
/* Message ID constants */
#define MSGIDLEN 14
#define MSGIDBOUND (MSGIDLEN + 1)
/* stream.c */
typedef struct net_stream *NET_STREAM;
typedef int (*stream_read_t) (void *, char *, size_t, size_t *);
typedef int (*stream_write_t) (void *, const char *, size_t, size_t *);
typedef int (*stream_close_t) (void *);
typedef int (*stream_destroy_t) (void *);
typedef const char *(*stream_strerror_t) (void *, int);
void stream_create (NET_STREAM * str);
int stream_set_io (NET_STREAM str,
void *data,
stream_read_t read, stream_write_t write,
stream_close_t close,
stream_destroy_t destroy, stream_strerror_t strerror);
int stream_set_read (struct net_stream *str, stream_read_t read);
int stream_set_write (struct net_stream *str, stream_write_t write);
int stream_set_strerror (struct net_stream *str, stream_strerror_t strerr);
int stream_close (NET_STREAM str);
const char *stream_strerror (NET_STREAM str, int errcode);
int stream_read (NET_STREAM str, char *buf, size_t size, size_t *nbytes);
int stream_write (NET_STREAM str, const char *buf, size_t size,
size_t *nbytes);
int stream_readline (NET_STREAM str, char *buf, size_t size, size_t *nbytes);
int stream_getline (NET_STREAM sd, char **vptr, size_t *maxlen, size_t *nread);
int stream_destroy (NET_STREAM *);
/* main.c */
void anubis (char *);
/* env.c */
void get_options (int, char *[]);
void get_homedir (char *, char *, int);
void anubis_getlogin (char **);
void anubis_changeowner (const char *);
int anubis_set_mode (char *modename);
int check_superuser (void);
int check_username (char *);
int check_filemode (char *);
int check_filename (char *, time_t *);
void write_pid_file (void);
/* errs.c */
#define EXIT_ABORT 256
void anubis_error (int, int, const char *, ...)
ANUBIS_PRINTFLIKE(3,4);
void anubis_warning (int error_code, const char *fmt, ...)
ANUBIS_PRINTFLIKE(2,3);
void socket_error (const char *);
void hostname_error (const char *);
/* log.c */
void mprintf (const char *, ...);
void info (int, const char *, ...);
void filelog (char *, char *);
/* net.c */
NET_STREAM make_remote_connection (char *, unsigned int);
int bind_and_listen (char *, unsigned int);
void swrite (int, NET_STREAM, const char *);
void swrite_n (int, NET_STREAM, const char *, size_t);
void send_eol (int method, NET_STREAM sd);
int recvline (int method, NET_STREAM sd, char **vptr, size_t * maxlen);
void get_response_smtp (int, NET_STREAM, char **, size_t *);
void close_socket (int sd);
void net_create_stream (NET_STREAM * str, int fd);
void net_close_stream (NET_STREAM * sd);
void smtp_reply_get (int method, NET_STREAM sd, ANUBIS_SMTP_REPLY reply);
/* daemon.c */
void daemonize (void);
void loop (int);
void stdinout (void);
void service_unavailable (NET_STREAM *);
void set_unprivileged_user (void);
void create_stdio_stream (NET_STREAM *s);
/* auth.c */
int auth_ident (struct sockaddr_in *, char **);
/* map.c */
void parse_transmap (int *, char *, char *, char **);
void translate_section_init (void);
/* tunnel.c */
void smtp_session (void);
void smtp_session_transparent (void);
void set_ehlo_domain (const char *domain, size_t len);
char *get_ehlo_domain (void);
void transfer_header (ANUBIS_LIST);
void transfer_body (MESSAGE);
void collect_headers (MESSAGE msg, char *init_line);
void collect_body (MESSAGE msg);
/* proclist.c */
void proclist_register (pid_t pid);
size_t proclist_cleanup (void (*fun) (size_t, pid_t, int));
void proclist_init (void);
size_t proclist_count (void);
/* message.c */
MESSAGE message_new (void);
const char *message_id (MESSAGE msg);
ANUBIS_LIST message_get_header (MESSAGE);
ANUBIS_LIST message_get_commands (MESSAGE);
const char *message_get_body (MESSAGE msg);
const char *message_get_boundary (MESSAGE msg);
ANUBIS_LIST message_get_mime_header (MESSAGE msg);
void message_replace_header (MESSAGE msg, ANUBIS_LIST list);
void message_replace_body (MESSAGE msg, char *body);
void message_replace_boundary (MESSAGE msg, char *boundary);
void message_add_body (MESSAGE, char *, char *);
void message_add_header (MESSAGE, char *, char *);
void message_add_command (MESSAGE, ASSOC *);
void message_append_mime_header (MESSAGE, const char *);
void message_remove_headers (MESSAGE, RC_REGEX *);
void message_modify_headers (MESSAGE, RC_REGEX *, char *, char *);
void message_modify_body (MESSAGE, RC_REGEX *, char *);
void message_modify_command (MESSAGE msg, RC_REGEX *regex, char *key,
char *value);
void message_proc_body (MESSAGE msg, int (*proc) (char **, char *, void *),
void *param);
void message_external_proc (MESSAGE, char **);
void message_reset (MESSAGE);
void message_free (MESSAGE);
MESSAGE message_dup (MESSAGE msg);
/* exec.c */
char **gen_execargs (const char *);
NET_STREAM make_local_connection (char *, char **);
char *external_program (int *, char *, char *, char *, int);
char *exec_argv (int *, char *, char **, char *, char *, int);
void cleanup_children (void);
/* esmtp.c */
int esmtp_auth (NET_STREAM *, const char *);
void anubis_set_client_mech_list (ANUBIS_LIST list);
void anubis_set_encryption_mech_list (ANUBIS_LIST list);
/* misc.c */
int anubis_free_list_item (void *item, void *data);
void assoc_free (ASSOC *);
ASSOC *header_assoc (char *);
int anubis_assoc_cmp (void *item, void *data);
ANUBIS_LIST assoc_list_dup (ANUBIS_LIST);
void destroy_assoc_list (ANUBIS_LIST *);
ANUBIS_LIST string_list_dup (ANUBIS_LIST orig);
void destroy_string_list (ANUBIS_LIST *);
void parse_mtaport (char *, char **, unsigned int *);
void parse_mtahost (char *, char **, unsigned int *);
void remline (char *, char *);
void remcrlf (char *);
char *substitute (char *, char **);
char *make_uppercase (char *);
char *make_lowercase (char *);
char *get_localname (void);
char *get_localdomain (void);
void assign_string (char **pstr, const char *s);
void assign_string_n (char **pstr, const char *s, size_t length);
/* mime.c */
void message_append_text_file (MESSAGE, char *, char *);
void message_append_signature_file (MESSAGE);
/* regex.c */
int anubis_regex_match (RC_REGEX *, const char *, int *, char ***);
RC_REGEX *anubis_regex_compile (char *, int);
void anubis_regex_free (RC_REGEX **);
char *anubis_regex_source (RC_REGEX *);
int anubis_regex_refcnt (RC_REGEX *);
char *anubis_regex_replace (RC_REGEX *, char *, char *);
void anubis_regex_print (RC_REGEX *);
/* rcfile.c */
void rc_system_init (void);
void auth_tunnel (void);
void open_rcfile (int);
void process_rcfile (int);
void rcfile_process_section (int, char *, void *, MESSAGE);
void rcfile_call_section (int, char *, char *, void *, MESSAGE);
char *user_rcfile_name (void);
typedef struct eval_env *EVAL_ENV;
struct rc_loc const *eval_env_locus (EVAL_ENV);
int eval_env_method (EVAL_ENV);
MESSAGE eval_env_message (EVAL_ENV);
void *eval_env_data (EVAL_ENV);
void eval_error (int retcode, EVAL_ENV env, const char *fmt, ...)
ANUBIS_PRINTFLIKE(3,4);
void eval_warning (EVAL_ENV env, const char *fmt, ...)
ANUBIS_PRINTFLIKE(2,3);
void rc_disable_keyword (int mask, const char *kw);
/* help.c */
void print_config_options (void);
/* quit.c */
void sig_exit (int);
void sig_timeout (int);
void free_mem (void);
void quit (int);
/* socks.c */
#ifdef USE_SOCKS_PROXY
int check_socks_proxy (int, char *, unsigned int);
#endif /* USE_SOCKS_PROXY */
/* tls.c */
#ifdef USE_SSL
void init_ssl_libs (void);
NET_STREAM start_ssl_client (NET_STREAM str, int verbose);
NET_STREAM start_ssl_server (NET_STREAM str, int verbose);
#endif /* USE_SSL */
/* gpg.c */
#ifdef HAVE_GPG
void gpg_free (void);
void gpg_section_init (void);
#endif /* HAVE_GPG */
/* guile.c */
#ifdef WITH_GUILE
void init_guile (void);
void guile_debug (int);
void guile_section_init (void);
void guile_init_anubis_log_port (void);
SCM guile_make_anubis_error_port (int err);
SCM guile_make_anubis_info_port (void);
#endif /* WITH_GUILE */
/* url.c */
typedef struct anubis_url
{
char *method;
char *host;
char *path;
char *user;
char *passwd;
int argc;
ASSOC *argv;
}
ANUBIS_URL;
void anubis_url_destroy (ANUBIS_URL ** url);
int anubis_url_parse (ANUBIS_URL ** url, char *str);
char *anubis_url_full_path (ANUBIS_URL * url);
const char *anubis_url_get_arg (ANUBIS_URL * url, const char *argname);
/* anubisdb.c */
typedef struct anubis_user
{
char *smtp_authid; /* ESMTP authentication ID */
char *smtp_passwd; /* A corresponding password */
char *username; /* System user name to switch to */
char *rcfile_name; /* Optional configuration file.
When NULL, defaults to
~username/.anubisrc */
}
ANUBIS_USER;
enum anubis_db_mode
{
anubis_db_rdonly,
anubis_db_rdwr
};
#define ANUBIS_DB_SUCCESS 0 /* Operations successful */
#define ANUBIS_DB_FAIL 1 /* Operation failed */
#define ANUBIS_DB_NOT_FOUND 2 /* Record not found (for db_get_record
only) */
typedef int (*anubis_db_open_t) (void **d, ANUBIS_URL * url,
enum anubis_db_mode mode, char const **errp);
typedef int (*anubis_db_close_t) (void *d);
typedef int (*anubis_db_io_t) (void *d, const char *key, ANUBIS_USER * rec,
int *ecode);
typedef const char *(*anubis_db_strerror_t) (void *d, int rc);
typedef int (*anubis_db_delete_t) (void *d, const char *key, int *ecode);
typedef int (*anubis_db_get_list_t) (void *d, ANUBIS_LIST list, int *ecode);
int anubis_db_register (const char *dbid,
anubis_db_open_t _db_open,
anubis_db_close_t _db_close,
anubis_db_io_t _db_get,
anubis_db_io_t _db_put,
anubis_db_delete_t _db_delete,
anubis_db_get_list_t _db_list,
anubis_db_strerror_t _db_strerror);
int anubis_db_open (char *arg, enum anubis_db_mode mode, void **dptr,
char const **errp);
int anubis_db_close (void **dptr);
int anubis_db_get_record (void *dptr, const char *key, ANUBIS_USER * rec);
int anubis_db_put_record (void *dptr, const char *key, ANUBIS_USER * rec);
int anubis_db_delete_record (void *dptr, const char *key);
int anubis_db_get_list (void *dptr, ANUBIS_LIST * list);
const char *anubis_db_strerror (void *dptr);
void anubis_db_free_record (ANUBIS_USER * rec);
/* dbtext.c */
void dbtext_init (void);
/* gdbm.c */
void db_gdbm_init (void);
/* mysql.c */
void mysql_db_init (void);
/* pgsql.c */
void pgsql_db_init (void);
/* transmode.c */
int anubis_transparent_mode (struct sockaddr_in *addr);
int anubis_proxy_mode (struct sockaddr_in *addr);
void session_prologue ();
/* authmode.c */
int anubis_authenticate_mode (struct sockaddr_in *addr);
void anubis_set_password_db (char *arg);
void asmtp_reply (int code, char *fmt, ...);
void asmtp_capa_add_prefix (char *prefix, char *name);
int anubis_get_db_record (const char *username, ANUBIS_USER * usr);
void authmode_section_init (void);
/* gsasl.c */
void auth_gsasl_init (void);
int anubis_auth_gsasl (char *auth_type, char *arg, ANUBIS_USER * usr);
#ifdef WITH_GSASL
void install_gsasl_stream (Gsasl_session *sess_ctx, NET_STREAM * stream);
#endif
/* gsasl_srv.c */
int anubis_name_cmp (void *item, void *data);
ANUBIS_LIST auth_method_list (const char *input);
void anubis_set_mech_list (ANUBIS_LIST *out, ANUBIS_LIST list);
void anubis_set_server_mech_list (ANUBIS_LIST list);
/* xdatabase.c */
int xdatabase (char *command);
void xdatabase_capability (ANUBIS_SMTP_REPLY reply);
/* md5sum.c */
int anubis_md5_file (int fd, unsigned char **out_digest, char const **err);
/* mda.c */
void mda (void);
void argv_free (char **argv);
char *argv_string (char **argv);
#ifdef USE_GCRYPT
char *idecrypt_username (char const *text, size_t len);
#else
static inline char *idecrypt_username (char const *text, size_t len) { return NULL; }
#endif
/* getpass.c */
int anubis_getpass (char const *prompt, char **pass);
/* mem.c */
void xnomem (void);
void *mem2nrealloc (void *p, size_t *pn, size_t s);
void *xmalloc (size_t s);
void *xcalloc (size_t nmemb, size_t size);
static inline void *xzalloc (size_t size) { return xcalloc (1, size); }
void *xrealloc (void *p, size_t s);
void *x2nrealloc (void *p, size_t *pn, size_t s);
static inline void *x2realloc (void *p, size_t *pn) {
return x2nrealloc (p, pn, 1);
}
char *xstrdup (char const *s);
char *xstrndup (const char *s, size_t n);
ssize_t xgetline (char **pbuf, size_t *psize, FILE *fp);
struct stringbuf
{
char *base; /* Buffer storage. */
size_t size; /* Size of buf. */
size_t len; /* Actually used length in buf. */
void (*nomem) (void); /* Out of memory handler. */
int err; /* Error indicator */
};
#define STRINGBUF_INITIALIZER { NULL, 0, 0, xnomem, 0 }
void stringbuf_init (struct stringbuf *sb, void (*nomem) (void));
void stringbuf_reset (struct stringbuf *sb);
char *stringbuf_finish (struct stringbuf *sb);
void stringbuf_free (struct stringbuf *sb);
int stringbuf_add (struct stringbuf *sb, char const *str, size_t len);
int stringbuf_add_char (struct stringbuf *sb, int c);
int stringbuf_add_string (struct stringbuf *sb, char const *str);
int stringbuf_vprintf (struct stringbuf *sb, char const *fmt, va_list ap);
int stringbuf_printf (struct stringbuf *sb, char const *fmt, ...)
ANUBIS_PRINTFLIKE(2,3);
char *stringbuf_set (struct stringbuf *sb, int c, size_t n);
int stringbuf_strftime (struct stringbuf *sb, char const *fmt,
const struct tm *tm);
static inline int
stringbuf_err (struct stringbuf *sb)
{
return sb->err;
}
static inline char *stringbuf_value (struct stringbuf *sb)
{
return sb->base;
}
static inline size_t stringbuf_len (struct stringbuf *sb)
{
return sb->len;
}
static inline void xstringbuf_init (struct stringbuf *sb)
{
stringbuf_init (sb, xnomem);
}
/* EOF */