Rework string manipulation & minor improvements on webhook events

This commit is contained in:
Davidson Francis 2024-07-28 02:44:20 -03:00
parent db437fb6e3
commit 11079f41c1
6 changed files with 395 additions and 88 deletions

View File

@ -8,7 +8,7 @@ CFLAGS += -Wall -Wextra
LDLIBS += -pthread -lcurl LDLIBS += -pthread -lcurl
STRIP = strip STRIP = strip
VERSION = v0.1 VERSION = v0.1
OBJS = alertik.o events.o env_events.o notifiers.o log.o syslog.o OBJS = alertik.o events.o env_events.o notifiers.o log.o syslog.o str.o
ifeq ($(LOG_FILE),yes) ifeq ($(LOG_FILE),yes)
CFLAGS += -DUSE_FILE_AS_LOG CFLAGS += -DUSE_FILE_AS_LOG

View File

@ -15,6 +15,7 @@
#include "env_events.h" #include "env_events.h"
#include "alertik.h" #include "alertik.h"
#include "notifiers.h" #include "notifiers.h"
#include "str.h"
/* /*
* Environment events * Environment events
@ -105,30 +106,10 @@ get_event_idx(int ev_num, char *str, const char *const *str_list, int size)
panic("String parameter (%s) invalid for %s\n", env, str); panic("String parameter (%s) invalid for %s\n", env, str);
} }
/**
* @brief Appends a character to the destination buffer if there is space.
*
* @param dst Pointer to the destination buffer.
* @param dst_end End of the destination buffer.
* @param c Character to append.
*
* @return Returns 1 if the character was appended, 0 otherwise.
*/
static int append_dst(char **dst, const char *dst_end, char c) {
char *d = *dst;
if (d < dst_end) {
*d = c;
*dst = ++d;
return 1;
}
return 0;
}
/** /**
* @brief Handles match replacement in the event mask message. * @brief Handles match replacement in the event mask message.
* *
* @param dst Pointer to the destination buffer. * @param notif_message Pointer to the append buffer.
* @param dst_e End of the destination buffer.
* @param c_msk Pointer to the current position in the mask message. * @param c_msk Pointer to the current position in the mask message.
* @param e_msk End of the mask message. * @param e_msk End of the mask message.
* @param pmatch Array of regex matches. * @param pmatch Array of regex matches.
@ -138,7 +119,7 @@ static int append_dst(char **dst, const char *dst_end, char c) {
* @return Returns 1 if the replacement was handled, 0 otherwise. * @return Returns 1 if the replacement was handled, 0 otherwise.
*/ */
static int handle_match_replacement( static int handle_match_replacement(
char **dst, char *dst_e, struct str_ab *notif_message,
const char **c_msk, const char *e_msk, const char **c_msk, const char *e_msk,
regmatch_t *pmatch, regmatch_t *pmatch,
struct env_event *env, struct env_event *env,
@ -172,10 +153,8 @@ static int handle_match_replacement(
off = pmatch[match].rm_so; off = pmatch[match].rm_so;
len = pmatch[match].rm_eo - off; len = pmatch[match].rm_eo - off;
for (regoff_t i = 0; i < len; i++) { if (ab_append_str(notif_message, log_ev->msg + off, len) < 0)
if ( !append_dst(dst, dst_e, log_ev->msg[off + i]) )
return 0; return 0;
}
return 1; return 1;
} }
@ -192,22 +171,19 @@ static int handle_match_replacement(
* *
* @return Returns the pointer to the end of the masked message. * @return Returns the pointer to the end of the masked message.
*/ */
static char* static int
create_masked_message(struct env_event *env, regmatch_t *pmatch, create_masked_message(struct env_event *env, regmatch_t *pmatch,
struct log_event *log_ev, char *buf, size_t buf_size) struct log_event *log_ev, struct str_ab *notif_message)
{ {
char *dst, *dst_e;
const char *c_msk, *e_msk; const char *c_msk, *e_msk;
c_msk = env->ev_mask_msg; c_msk = env->ev_mask_msg;
e_msk = c_msk + strlen(c_msk); e_msk = c_msk + strlen(c_msk);
dst = buf;
dst_e = dst + buf_size;
for (; *c_msk != '\0'; c_msk++) for (; *c_msk != '\0'; c_msk++)
{ {
if (*c_msk != '@') { if (*c_msk != '@') {
if (!append_dst(&dst, dst_e, *c_msk)) if (ab_append_chr(notif_message, *c_msk) < 0)
break; break;
continue; continue;
} }
@ -220,7 +196,7 @@ create_masked_message(struct env_event *env, regmatch_t *pmatch,
* If next is also '@',escape it. * If next is also '@',escape it.
*/ */
if (c_msk[1] == '@') { if (c_msk[1] == '@') {
if (!append_dst(&dst, dst_e, *c_msk)) if (ab_append_chr(notif_message, *c_msk) < 0)
break; break;
else { else {
c_msk++; c_msk++;
@ -240,14 +216,16 @@ create_masked_message(struct env_event *env, regmatch_t *pmatch,
else else
{ {
c_msk++; c_msk++;
if (!handle_match_replacement(&dst, dst_e, &c_msk, e_msk, if (!handle_match_replacement(notif_message, &c_msk, e_msk,
pmatch, env, log_ev)) pmatch, env, log_ev))
{ {
break; break;
} }
} }
} }
return dst;
/* If we could parse the entire mask. */
return (*c_msk == '\0');
} }
/** /**
@ -262,10 +240,10 @@ static int handle_regex(struct log_event *ev, int idx_env)
{ {
char time_str[32] = {0}; char time_str[32] = {0};
regmatch_t pmatch[MAX_MATCHES] = {0}; regmatch_t pmatch[MAX_MATCHES] = {0};
char notification_message[2048] = {0}; struct str_ab notif_message;
int ret;
int notif_idx; int notif_idx;
char *notif_p;
struct env_event *env_ev; struct env_event *env_ev;
env_ev = &env_events[idx_env]; env_ev = &env_events[idx_env];
@ -280,34 +258,35 @@ static int handle_regex(struct log_event *ev, int idx_env)
log_msg("> amnt sub expr: %zu\n", env_ev->regex.re_nsub); log_msg("> amnt sub expr: %zu\n", env_ev->regex.re_nsub);
log_msg("> notifier : %s\n", notifiers_str[notif_idx]); log_msg("> notifier : %s\n", notifiers_str[notif_idx]);
notif_p = notification_message; ab_init(&notif_message);
/* Check if there are any subexpressions, if not, just format /* Check if there are any subexpressions, if not, just format
* the message. * the message.
*/ */
if (env_ev->regex.re_nsub) { if (env_ev->regex.re_nsub) {
notif_p = create_masked_message(env_ev, pmatch, ev, if (!create_masked_message(env_ev, pmatch, ev, &notif_message)) {
notification_message, sizeof(notification_message) - 1); log_msg("Unable to create masked message!\n");
return 0;
}
snprintf( if (ab_append_fmt(&notif_message, ", at: %s",
notif_p, get_formatted_time(ev->timestamp, time_str)))
(notification_message + sizeof(notification_message)) - notif_p, {
", at: %s", return 0;
get_formatted_time(ev->timestamp, time_str) }
);
} }
else { else {
snprintf( ret = ab_append_fmt(&notif_message,
notification_message,
sizeof(notification_message) - 1,
"%s, at: %s", "%s, at: %s",
env_ev->ev_mask_msg, env_ev->ev_mask_msg,
get_formatted_time(ev->timestamp, time_str) get_formatted_time(ev->timestamp, time_str));
);
if (ret)
return 0;
} }
if (notifiers[notif_idx].send_notification(notification_message) < 0) { if (notifiers[notif_idx].send_notification(notif_message.buff) < 0) {
log_msg("unable to send the notification through %s\n", log_msg("unable to send the notification through %s\n",
notifiers_str[notif_idx]); notifiers_str[notif_idx]);
} }
@ -325,10 +304,11 @@ static int handle_regex(struct log_event *ev, int idx_env)
*/ */
static int handle_substr(struct log_event *ev, int idx_env) static int handle_substr(struct log_event *ev, int idx_env)
{ {
int ret;
int notif_idx; int notif_idx;
char time_str[32] = {0}; char time_str[32] = {0};
struct env_event *env_ev; struct env_event *env_ev;
char notification_message[2048] = {0}; struct str_ab notif_message;
env_ev = &env_events[idx_env]; env_ev = &env_events[idx_env];
notif_idx = env_ev->ev_notifier_idx; notif_idx = env_ev->ev_notifier_idx;
@ -340,16 +320,19 @@ static int handle_substr(struct log_event *ev, int idx_env)
log_msg("> type: substr, match: (%s), notifier: %s\n", log_msg("> type: substr, match: (%s), notifier: %s\n",
env_ev->ev_match_str, notifiers_str[notif_idx]); env_ev->ev_match_str, notifiers_str[notif_idx]);
ab_init(&notif_message);
/* Format the message. */ /* Format the message. */
snprintf( ret = ab_append_fmt(&notif_message,
notification_message,
sizeof notification_message - 1,
"%s, at: %s", "%s, at: %s",
env_ev->ev_mask_msg, env_ev->ev_mask_msg,
get_formatted_time(ev->timestamp, time_str) get_formatted_time(ev->timestamp, time_str)
); );
if (notifiers[notif_idx].send_notification(notification_message) < 0) { if (ret)
return 0;
if (notifiers[notif_idx].send_notification(notif_message.buff) < 0) {
log_msg("unable to send the notification through %s\n", log_msg("unable to send the notification through %s\n",
notifiers_str[notif_idx]); notifiers_str[notif_idx]);
} }

View File

@ -12,6 +12,7 @@
#include "alertik.h" #include "alertik.h"
#include "notifiers.h" #include "notifiers.h"
#include "log.h" #include "log.h"
#include "str.h"
/* /*
* Static events * Static events
@ -252,18 +253,19 @@ static void handle_wifi_login_attempts(struct log_event *ev, int idx_env)
char time_str[32] = {0}; char time_str[32] = {0};
char mac_addr[32] = {0}; char mac_addr[32] = {0};
char wifi_iface[32] = {0}; char wifi_iface[32] = {0};
char notification_message[2048] = {0}; struct str_ab notif_message;
int notif_idx; int notif_idx;
int ret;
log_msg("> Login attempt detected!\n"); log_msg("> Login attempt detected!\n");
if (parse_login_attempt_msg(ev->msg, wifi_iface, mac_addr) < 0) if (parse_login_attempt_msg(ev->msg, wifi_iface, mac_addr) < 0)
return; return;
ab_init(&notif_message);
/* Send our notification. */ /* Send our notification. */
snprintf( ret = ab_append_fmt(&notif_message,
notification_message,
sizeof notification_message - 1,
"There is someone trying to connect " "There is someone trying to connect "
"to your WiFi: %s, with the mac-address: %s, at:%s", "to your WiFi: %s, with the mac-address: %s, at:%s",
wifi_iface, wifi_iface,
@ -271,10 +273,13 @@ static void handle_wifi_login_attempts(struct log_event *ev, int idx_env)
get_formatted_time(ev->timestamp, time_str) get_formatted_time(ev->timestamp, time_str)
); );
if (ret)
return;
log_msg("> Retrieved info, MAC: (%s), Interface: (%s)\n", mac_addr, wifi_iface); log_msg("> Retrieved info, MAC: (%s), Interface: (%s)\n", mac_addr, wifi_iface);
notif_idx = static_events[idx_env].ev_notifier_idx; notif_idx = static_events[idx_env].ev_notifier_idx;
if (notifiers[notif_idx].send_notification(notification_message) < 0) { if (notifiers[notif_idx].send_notification(notif_message.buff) < 0) {
log_msg("unable to send the notification!\n"); log_msg("unable to send the notification!\n");
return; return;
} }

View File

@ -11,6 +11,7 @@
#include "log.h" #include "log.h"
#include "notifiers.h" #include "notifiers.h"
#include "alertik.h" #include "alertik.h"
#include "str.h"
/* /*
* Notification handling/notifiers * Notification handling/notifiers
@ -69,7 +70,12 @@ static int setopts_get_curl(CURL *hnd, const char *url)
} }
/** /**
* @brief Cleanup all resources used by libcurl, including the handler,
* curl_slist and escape'd chars.
* *
* @param hnd curl handler
* @param escape Escape string if any (leave NULL if there's none).
* @param slist String list if any (leave NULL if there's none).
*/ */
static void do_curl_cleanup(CURL *hnd, char *escape, struct curl_slist *slist) static void do_curl_cleanup(CURL *hnd, char *escape, struct curl_slist *slist)
{ {
@ -80,10 +86,18 @@ static void do_curl_cleanup(CURL *hnd, char *escape, struct curl_slist *slist)
} }
/** /**
* @brief Finally sends a curl request, check its return code
* and then cleanup the resources allocated.
* *
* @param hnd curl handler
* @param escape Escape string if any (leave NULL if there's none).
* @param slist String list if any (leave NULL if there's none).
*
* @return Returns CURLE_OK if success, !CURLE_OK if error.
*/ */
static CURLcode do_curl(CURL *hnd, char *escape, struct curl_slist *slist) static CURLcode do_curl(CURL *hnd, char *escape, struct curl_slist *slist)
{ {
long response_code = 0;
CURLcode ret_curl = !CURLE_OK; CURLcode ret_curl = !CURLE_OK;
#ifndef DISABLE_NOTIFICATIONS #ifndef DISABLE_NOTIFICATIONS
@ -91,8 +105,14 @@ static CURLcode do_curl(CURL *hnd, char *escape, struct curl_slist *slist)
if (ret_curl != CURLE_OK) { if (ret_curl != CURLE_OK) {
log_msg("> Unable to send request!\n"); log_msg("> Unable to send request!\n");
goto error; goto error;
} else { }
log_msg("> Done!\n"); else {
curl_easy_getinfo(hnd, CURLINFO_RESPONSE_CODE, &response_code);
log_msg("> Done!\n", response_code);
if (response_code != 200) {
log_msg("(Info: Response code != 200 (%ld), your message might "
"not be correctly sent!)\n", response_code);
}
} }
#endif #endif
@ -143,36 +163,48 @@ static int setopts_post_json_curl(CURL *hnd, const char *url,
} }
/** /**
* @brief Sends a generic webhook POST request with JSON payload in the
* format {"text": "text here"}.
* *
* @param url Target webhook URL.
* @param text Text to be sent in the json payload.
*
* @return Returns CURLE_OK if success, 1 if error.
*/ */
static int send_generic_webhook(const char *url, const char *text) static int send_generic_webhook(const char *url, const char *text)
{ {
CURL *hnd = NULL; CURL *hnd = NULL;
struct curl_slist *s = NULL; struct curl_slist *s = NULL;
char payload_data[4096] = {0}; struct str_ab payload_data;
const char *t;
if (!(hnd = curl_easy_init())) { if (!(hnd = curl_easy_init())) {
log_msg("Failed to initialize libcurl!\n"); log_msg("Failed to initialize libcurl!\n");
return 1; return 1;
} }
snprintf( ab_init(&payload_data);
payload_data, ab_append_str(&payload_data, "{\"text\":\"", 9);
sizeof payload_data - 1,
"{\"text\":\"%s\"}",
text
);
/* Append the payload data text while escaping double
* quotes.
*/
for (t = text; *t != '\0'; t++) {
if (*t != '"') {
if (ab_append_chr(&payload_data, *t) < 0)
return 1;
}
else {
if (ab_append_str(&payload_data, "\\\"", 2) < 0)
return 1;
}
}
#if 0 /* End the string. */
TODO: if (ab_append_str(&payload_data, "\"}", 2) < 0)
- escape "" in the payload, otherwise it will silently return 1;
escape from the {"text": }
- think about the return code from the request and
emmit some message if not 200
#endif
if (setopts_post_json_curl(hnd, url, payload_data, &s)) if (setopts_post_json_curl(hnd, url, payload_data.buff, &s))
return 1; return 1;
log_msg("> Sending notification!\n"); log_msg("> Sending notification!\n");
@ -209,9 +241,10 @@ void setup_telegram(void)
static int send_telegram_notification(const char *msg) static int send_telegram_notification(const char *msg)
{ {
char full_request_url[4096] = {0}; struct str_ab full_request_url;
char *escaped_msg = NULL; char *escaped_msg = NULL;
CURL *hnd = NULL; CURL *hnd = NULL;
int ret;
if (!(hnd = curl_easy_init())) { if (!(hnd = curl_easy_init())) {
log_msg("Failed to initialize libcurl!\n"); log_msg("Failed to initialize libcurl!\n");
@ -224,13 +257,16 @@ static int send_telegram_notification(const char *msg)
do_curl_cleanup(hnd, escaped_msg, NULL); do_curl_cleanup(hnd, escaped_msg, NULL);
} }
snprintf( ab_init(&full_request_url);
full_request_url,
sizeof full_request_url - 1, ret = ab_append_fmt(&full_request_url,
"https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s", "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s",
telegram_bot_token, telegram_chat_id, escaped_msg); telegram_bot_token, telegram_chat_id, escaped_msg);
setopts_get_curl(hnd, full_request_url); if (ret)
return -1;
setopts_get_curl(hnd, full_request_url.buff);
log_msg("> Sending notification!\n"); log_msg("> Sending notification!\n");
return do_curl(hnd, escaped_msg, NULL); return do_curl(hnd, escaped_msg, NULL);
} }
@ -250,7 +286,7 @@ void setup_slack(void)
slack_webhook_url = getenv("SLACK_WEBHOOK_URL"); slack_webhook_url = getenv("SLACK_WEBHOOK_URL");
if (!slack_webhook_url) { if (!slack_webhook_url) {
panic("Unable to find env vars for, please check if you have set\n" panic("Unable to find env vars for Slack, please check if you have set "
"the SLACK_WEBHOOK_URL!!\n"); "the SLACK_WEBHOOK_URL!!\n");
} }
setup = 1; setup = 1;

244
str.c Normal file
View File

@ -0,0 +1,244 @@
/*
* Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers.
* This is free and unencumbered software released into the public domain.
*/
/*
* String/append buffer implementation based on Aqua:
* https://gist.github.com/Theldus/09ed2205aa5ba15cdf4571b71cd1c8fc
*/
#include "str.h"
#include "log.h"
/* Malloc is only used if AB_USE_MALLOC is defined. */
#ifdef AB_USE_MALLOC
#if defined(AB_CALLOC) && defined(AB_REALLOC) && defined(AB_FREE)
# define AB_USE_STDLIB
#elif !defined(AB_CALLOC) && !defined(AB_REALLOC) && !defined(AB_FREE)
# define AB_USE_STDLIB
#else
#error "For custom memory allocators, you should define all three routines!"
#error "Please define: AB_CALLOC, AB_REALLOC and AB_FREE!"
#endif
#endif
#ifndef AB_CALLOC
#define AB_CALLOC(nmemb,sz) calloc((nmemb),(sz))
#define AB_REALLOC(p,newsz) realloc((p),(newsz))
#define AB_FREE(p) free((p))
#endif
#ifdef AB_USE_STDLIB
#include <stdlib.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
/* ========================================================================= */
/* BUFFER ROUTINES */
/* ========================================================================= */
#ifdef AB_USE_MALLOC
/**
* @brief Rounds up to the next power of two.
*
* @param target Target number to be rounded.
*
* @return Returns the next power of two.
*/
static size_t next_power(size_t target)
{
target--;
target |= target >> 1;
target |= target >> 2;
target |= target >> 4;
target |= target >> 8;
target |= target >> 16;
target++;
return (target);
}
#endif
/**
* @brief Checks if the new size fits in the append buffer, if not,
* reallocates the buffer size by @p incr bytes.
*
* If the macro AB_USE_MALLOC is not defined (default), this only
* checks if the new size fits the buffer.
*
* @param sh Aqua highlight context.
* @param incr Size (in bytes) to be incremented.
*
* @return Returns 0 if success, -1 otherwise.
*
* @note The new size is the next power of two, that is capable
* to hold the required buffer size.
*/
static int increase_buff(struct str_ab *sh, size_t incr)
{
#ifndef AB_USE_MALLOC
if (sh->pos + incr >= MAX_LINE) {
log_msg("(increase buffer) Unable to fit appended message!\n");
log_msg("(static storage) incr: %zu, buff_len: %zu, pos: %zu\n",
incr, sh->pos, sh->buff_len);
return (-1);
}
#else
char *new;
size_t new_size;
if (sh->pos + incr >= sh->buff_len)
{
new_size = next_power(sh->buff_len + incr);
new = AB_REALLOC(sh->buff, new_size);
if (new == NULL)
{
AB_FREE(sh->buff);
sh->buff = NULL;
log_msg("(increase buffer) Unable to fit appended message!\n");
log_msg("(realloc storage) incr: %zu, buff_len: %zu, pos: %zu\n",
incr, sh->pos, sh->buff_len);
return (-1);
}
sh->buff_len = new_size;
sh->buff = new;
}
#endif
return (0);
}
/**
* @brief Initializes the append buffer context.
*
* @param ab Append buffer structure.
*
* @return Returns 0 if success, -1 otherwise.
*/
int ab_init(struct str_ab *ab)
{
if (!ab)
return (-1);
memset(ab, 0, sizeof(*ab));
#ifndef AB_USE_MALLOC
ab->buff_len = MAX_LINE;
#else
ab->buff = AB_CALLOC(MAX_LINE, 1);
if (!ab->buff)
return (-1);
ab->buff_len = MAX_LINE;
#endif
return (0);
}
/**
* @brief Append a given char @p c into the buffer.
*
* @param sh Aqua highlight context.
* @param c Char to be appended.
*
* @return Returns 0 if success, -1 otherwise.
*/
int ab_append_chr(struct str_ab *sh, char c)
{
if (increase_buff(sh, 2) < 0)
return (-1);
sh->buff[sh->pos + 0] = c;
sh->buff[sh->pos + 1] = '\0';
sh->pos++;
return (0);
}
/**
* @brief Appends a given string pointed by @p s of size @p len
* into the current buffer.
*
* If @p len is 0, the string is assumed to be null-terminated
* and its length is obtained.
*
* @param ab Append buffer context.
* @param s String to be append into the buffer.
* @param len String size, if 0, it's length is obtained.
*
* @return Returns 0 if success, -1 otherwise.
*/
int ab_append_str(struct str_ab *ab, const char *s, size_t len)
{
if (!len)
len = strlen(s);
if (increase_buff(ab, len + 1) < 0)
return (-1);
memcpy(ab->buff + ab->pos, s, len);
ab->pos += len;
ab->buff[ab->pos] = '\0';
return (0);
}
/**
* @brief Appends a given formatted string pointed by @p fmt.
*
* @param ab Append buffer context.
* @param fmt Formatted string to be appended.
*
* @return Returns 0 if success, -1 otherwise.
*/
int ab_append_fmt(struct str_ab *ab, const char *fmt, ...)
{
int str_len, ab_len, orig_ab_pos;
char *buff_st;
va_list ap;
buff_st = ab->buff + ab->pos;
ab_len = ab->buff_len - ab->pos;
va_start(ap, fmt);
str_len = vsnprintf(buff_st, ab_len, fmt, ap);
if (str_len < 0) {
log_msg("Unable to fit appended message!\n");
return (-1);
}
va_end(ap);
/* If it fits, just happily returns. */
if (str_len + 1 <= ab_len) {
ab->pos += str_len;
return (0);
}
/* Otherwise, adjust current pos and try to increase buffer. */
else {
orig_ab_pos = ab->pos;
/* temporarily advance our buffer
* to trick our increase buffer. */
ab->pos = ab->buff_len;
if (increase_buff(ab, (str_len + 1) - ab_len))
return (-1);
ab->pos = orig_ab_pos;
}
buff_st = ab->buff + ab->pos;
ab_len = ab->buff_len - ab->pos;
va_start(ap, fmt);
str_len = vsnprintf(buff_st, ab_len, fmt, ap);
if (str_len < 0 || (str_len + 1) > ab_len) {
log_msg("Unable to fit appended message!\n");
return (-1);
}
va_end(ap);
ab->pos += str_len;
return (0);
}

39
str.h Normal file
View File

@ -0,0 +1,39 @@
/*
* Alertik: a tiny 'syslog' server & notification tool for Mikrotik routers.
* This is free and unencumbered software released into the public domain.
*/
#ifndef STR_H
#define STR_H
#include <stddef.h>
/*
* Enable to disable malloc support and enable dinamically
* allocated buffer.
*/
#if 0
#define AB_USE_MALLOC
#endif
/* Maximum highlighted line len, when built without malloc. */
#define MAX_LINE 4096
/* Append buffer. */
struct str_ab
{
#ifndef AB_USE_MALLOC
char buff[MAX_LINE + 1];
#else
char *buff;
#endif
size_t buff_len;
size_t pos;
};
extern int ab_init(struct str_ab *ab);
extern int ab_append_chr(struct str_ab *sh, char c);
extern int ab_append_str(struct str_ab *ab, const char *s, size_t len);
extern int ab_append_fmt(struct str_ab *ab, const char *fmt, ...);
#endif /* STR_H. */