static int table_mysql_check(int service, struct dict *params, const char *key) { MYSQL_STMT *stmt; int r, s; if (config->db == NULL && config_connect(config) == 0) return (-1); stmt = table_mysql_query(key, service); if (stmt == NULL) return (-1); r = -1; s = mysql_stmt_fetch(stmt); if (s == 0) r = 1; else if (s == MYSQL_NO_DATA) r = 0; else log_warnx("warn: table-mysql: mysql_stmt_fetch: %s", mysql_stmt_error(stmt)); if (mysql_stmt_free_result(stmt)) log_warnx("warn: table-mysql: mysql_stmt_free_result: %s", mysql_stmt_error(stmt)); return (r); }
static int table_redis_check(int service, const char *key) { int r; redisReply *reply; if (config->db == NULL && config_connect(config) == 0) return (-1); reply = table_redis_query(key, service); if (reply == NULL) return (-1); switch (reply->type) { case REDIS_REPLY_INTEGER: case REDIS_REPLY_STRING: case REDIS_REPLY_ARRAY: r = 1; break; case REDIS_REPLY_NIL: r = 0; break; case REDIS_REPLY_STATUS: case REDIS_REPLY_ERROR: default: r = -1; break; } freeReplyObject(reply); return (r); }
static MYSQL_STMT * table_mysql_query(const char *key, int service) { MYSQL_STMT *stmt; MYSQL_BIND param[1]; unsigned long keylen; char buffer[SMTPD_MAXLINESIZE]; int i; retry: stmt = NULL; for(i = 0; i < SQL_MAX; i++) if (service == 1 << i) { stmt = config->statements[i]; break; } if (stmt == NULL) return (NULL); if (strlcpy(buffer, key, sizeof(buffer)) >= sizeof(buffer)) { log_warnx("warn: table-mysql: key too long: \"%s\"", key); return (NULL); } keylen = strlen(key); param[0].buffer_type = MYSQL_TYPE_STRING; param[0].buffer = buffer; param[0].buffer_length = sizeof(buffer); param[0].is_null = 0; param[0].length = &keylen; if (mysql_stmt_bind_param(stmt, param)) { log_warnx("warn: table-mysql: mysql_stmt_bind_param: %s", mysql_stmt_error(stmt)); return (NULL); } if (mysql_stmt_execute(stmt)) { if (mysql_stmt_errno(stmt) == CR_SERVER_LOST || mysql_stmt_errno(stmt) == CR_SERVER_GONE_ERROR || mysql_stmt_errno(stmt) == CR_COMMANDS_OUT_OF_SYNC) { log_warnx("warn: table-mysql: trying to reconnect after error: %s", mysql_stmt_error(stmt)); if (config_connect(config)) goto retry; return (NULL); } log_warnx("warn: table-mysql: mysql_stmt_execute: %s", mysql_stmt_error(stmt)); return (NULL); } return (stmt); }
int main(int argc, char **argv) { int ch, i; log_init(1); log_verbose(~0); while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: log_warnx("warn: table-mysql: bad option"); return (1); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc != 1) { log_warnx("warn: table-mysql: bogus argument(s)"); return (1); } conffile = argv[0]; for (i = 0; i < SQL_MAX_RESULT; i++) { results[i].buffer_type = MYSQL_TYPE_STRING; results[i].buffer = results_buffer[i]; results[i].buffer_length = SMTPD_MAXLINESIZE; results[i].is_null = 0; } config = config_load(conffile); if (config == NULL) { log_warnx("warn: table-mysql: error parsing config file"); return (1); } if (config_connect(config) == 0) { log_warnx("warn: table-mysql: could not connect"); return (1); } table_api_on_update(table_mysql_update); table_api_on_check(table_mysql_check); table_api_on_lookup(table_mysql_lookup); table_api_on_fetch(table_mysql_fetch); table_api_dispatch(); return (0); }
static int table_redis_update(void) { struct config *c; if ((c = config_load(conffile)) == NULL) return (0); if (config_connect(c) == 0) { config_free(c); return (0); } config_free(config); config = c; return (1); }
static int table_postgres_update(void) { struct config *c; if ((c = config_load(conffile)) == NULL) return 0; if (config_connect(c) == 0) { config_free(c); return 0; } config_free(config); config = c; return 1; }
int main(int argc, char **argv) { int ch; log_init(1); log_verbose(~0); while ((ch = getopt(argc, argv, "")) != -1) { switch (ch) { default: log_warnx("warn: table-postgres: bad option"); return (1); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc != 1) { log_warnx("warn: table-postgres: bogus argument(s)"); return (1); } conffile = argv[0]; config = config_load(conffile); if (config == NULL) { log_warnx("warn: table-postgres: error parsing config file"); return (1); } if (config_connect(config) == 0) { log_warnx("warn: table-postgres: could not connect"); return (1); } table_api_on_update(table_postgres_update); table_api_on_check(table_postgres_check); table_api_on_lookup(table_postgres_lookup); table_api_on_fetch(table_postgres_fetch); table_api_dispatch(); return (0); }
static int table_postgres_check(int service, const char *key) { PGresult *res; int r; if (config->db == NULL && config_connect(config) == 0) return (-1); res = table_postgres_query(key, service); if (res == NULL) return (-1); r = (PQntuples(res) == 0) ? 0 : 1; PQclear(res); return (r); }
static PGresult * table_postgres_query(const char *key, int service) { PGresult *res; const char *errfld; char *stmt; int i; retry: stmt = NULL; for(i = 0; i < SQL_MAX; i++) if (service == 1 << i) { stmt = config->statements[i]; break; } if (stmt == NULL) return NULL; res = PQexecPrepared(config->db, stmt, 1, &key, NULL, NULL, 0); if (PQresultStatus(res) != PGRES_TUPLES_OK) { errfld = PQresultErrorField(res, PG_DIAG_SQLSTATE); /* PQresultErrorField can return NULL if the connection to the server suddenly closed (e.g. server restart) */ if (errfld == NULL || (errfld[0] == '0' && errfld[1] == '8')) { log_warnx("warn: table-postgres: trying to reconnect after error: %s", PQerrorMessage(config->db)); PQclear(res); if (config_connect(config)) goto retry; return NULL; } log_warnx("warn: table-postgres: PQexecPrepared: %s", PQerrorMessage(config->db)); PQclear(res); return NULL; } return res; }
static redisReply * table_redis_query(const char *key, int service) { redisReply *res; char *stmt; int i; int retry_times; retry_times = 3; retry: --retry_times; if (retry_times < 0) { log_warnx("warn: table-redis: giving up: too many retries"); return (NULL); } stmt = NULL; for(i = 0; i < SQL_MAX; i++) if (service == 1 << i) { stmt = config->statements[i]; break; } if (stmt == NULL) return (NULL); res = redisCommand(config->db, stmt, key); if (res == NULL) { log_warnx("warn: table-redis: redisCommand: %s", config->db->errstr); if (config_connect(config)) goto retry; return (NULL); } return (res); }
static int table_postgres_fetch(int service, char *dst, size_t sz) { char *stmt; PGresult *res; const char *k, *errfld; int i; if (config->db == NULL && config_connect(config) == 0) return (-1); retry: if (service != K_SOURCE) return (-1); stmt = config->stmt_fetch_source; if (stmt == NULL) return (-1); if (config->source_ncall < config->source_refresh && time(NULL) - config->source_update < config->source_expire) goto fetch; res = PQexecPrepared(config->db, stmt, 0, NULL, NULL, NULL, 0); if (PQresultStatus(res) != PGRES_TUPLES_OK) { errfld = PQresultErrorField(res, PG_DIAG_SQLSTATE); if (errfld[0] == '0' && errfld[1] == '8') { log_warnx("warn: table-postgres: trying to reconnect after error: %s", PQerrorMessage(config->db)); PQclear(res); if (config_connect(config)) goto retry; return (-1); } log_warnx("warn: table-postgres: PQexecPrepared: %s", PQerrorMessage(config->db)); PQclear(res); return (-1); } config->source_iter = NULL; while (dict_poproot(&config->sources, NULL)) ; for (i = 0; i < PQntuples(res); i++) dict_set(&config->sources, PQgetvalue(res, i, 0), NULL); PQclear(res); config->source_update = time(NULL); config->source_ncall = 0; fetch: config->source_ncall += 1; if (! dict_iter(&config->sources, &config->source_iter, &k, (void **)NULL)) { config->source_iter = NULL; if (! dict_iter(&config->sources, &config->source_iter, &k, (void **)NULL)) return (0); } if (strlcpy(dst, k, sz) >= sz) return (-1); return (1); }
static int table_postgres_lookup(int service, const char *key, char *dst, size_t sz) { PGresult *res; int r, i; if (config->db == NULL && config_connect(config) == 0) return (-1); res = table_postgres_query(key, service); if (res == NULL) return (-1); if (PQntuples(res) == 0) { r = 0; goto end; } r = 1; switch(service) { case K_ALIAS: memset(dst, 0, sz); for (i = 0; i < PQntuples(res); i++) { if (dst[0] && strlcat(dst, ", ", sz) >= sz) { log_warnx("warn: table-postgres: result too large"); r = -1; break; } if (strlcat(dst, PQgetvalue(res, i, 0), sz) >= sz) { log_warnx("warn: table-postgres: result too large"); r = -1; break; } } break; case K_CREDENTIALS: if (snprintf(dst, sz, "%s:%s", PQgetvalue(res, 0, 0), PQgetvalue(res, 0, 1)) > (ssize_t)sz) { log_warnx("warn: table-postgres: result too large"); r = -1; } break; case K_USERINFO: if (snprintf(dst, sz, "%s:%s:%s", PQgetvalue(res, 0, 0), PQgetvalue(res, 0, 1), PQgetvalue(res, 0, 2)) > (ssize_t)sz) { log_warnx("warn: table-postgres: result too large"); r = -1; } break; case K_DOMAIN: case K_NETADDR: case K_SOURCE: case K_MAILADDR: case K_ADDRNAME: if (strlcpy(dst, PQgetvalue(res, 0, 0), sz) >= sz) { log_warnx("warn: table-postgres: result too large"); r = -1; } break; default: log_warnx("warn: table-postgres: unknown service %d", service); r = -1; } end: PQclear(res); return (r); }
static int table_mysql_fetch(int service, char *dst, size_t sz) { MYSQL_STMT *stmt; const char *k; int s; retry: if (service != K_SOURCE) return (-1); stmt = config->stmt_fetch_source; if (stmt == NULL) return (-1); if (config->source_ncall < config->source_refresh && time(NULL) - config->source_update < config->source_expire) goto fetch; if (mysql_stmt_execute(stmt)) { if (mysql_stmt_errno(stmt) == CR_SERVER_LOST || mysql_stmt_errno(stmt) == CR_SERVER_GONE_ERROR || mysql_stmt_errno(stmt) == CR_COMMANDS_OUT_OF_SYNC) { log_warnx("warn: table-mysql: trying to reconnect after error: %s", mysql_stmt_error(stmt)); if (config_connect(config)) goto retry; return (-1); } log_warnx("warn: table-mysql: mysql_stmt_execute: %s", mysql_stmt_error(stmt)); return (-1); } config->source_iter = NULL; while(dict_poproot(&config->sources, NULL, NULL)) ; while ((s = mysql_stmt_fetch(stmt)) == 0) dict_set(&config->sources, results_buffer[0], NULL); if (s && s != MYSQL_NO_DATA) log_warnx("warn: table-mysql: mysql_stmt_fetch: %s", mysql_stmt_error(stmt)); if (mysql_stmt_free_result(stmt)) log_warnx("warn: table-mysql: mysql_stmt_free_result: %s", mysql_stmt_error(stmt)); config->source_update = time(NULL); config->source_ncall = 0; fetch: config->source_ncall += 1; if (! dict_iter(&config->sources, &config->source_iter, &k, (void **)NULL)) { config->source_iter = NULL; if (! dict_iter(&config->sources, &config->source_iter, &k, (void **)NULL)) return (0); } if (strlcpy(dst, k, sz) >= sz) return (-1); return (1); }
static int table_mysql_lookup(int service, struct dict *params, const char *key, char *dst, size_t sz) { MYSQL_STMT *stmt; int r, s; if (config->db == NULL && config_connect(config) == 0) return (-1); stmt = table_mysql_query(key, service); if (stmt == NULL) return (-1); s = mysql_stmt_fetch(stmt); if (s == MYSQL_NO_DATA) { r = 0; goto end; } if (s != 0) { r = -1; log_warnx("warn: table-mysql: mysql_stmt_fetch: %s", mysql_stmt_error(stmt)); goto end; } r = 1; switch(service) { case K_ALIAS: memset(dst, 0, sz); do { if (dst[0] && strlcat(dst, ", ", sz) >= sz) { log_warnx("warn: table-mysql: result too large"); r = -1; break; } if (strlcat(dst, results_buffer[0], sz) >= sz) { log_warnx("warn: table-mysql: result too large"); r = -1; break; } s = mysql_stmt_fetch(stmt); } while (s == 0); if (s && s != MYSQL_NO_DATA) { log_warnx("warn: table-mysql: mysql_stmt_fetch: %s", mysql_stmt_error(stmt)); r = -1; } break; case K_CREDENTIALS: if (snprintf(dst, sz, "%s:%s", results_buffer[0], results_buffer[1]) > (ssize_t)sz) { log_warnx("warn: table-mysql: result too large"); r = -1; } break; case K_USERINFO: if (snprintf(dst, sz, "%s:%s:%s", results_buffer[0], results_buffer[1], results_buffer[2]) > (ssize_t)sz) { log_warnx("warn: table-mysql: result too large"); r = -1; } break; case K_DOMAIN: case K_NETADDR: case K_SOURCE: case K_MAILADDR: case K_ADDRNAME: if (strlcpy(dst, results_buffer[0], sz) >= sz) { log_warnx("warn: table-mysql: result too large"); r = -1; } break; default: log_warnx("warn: table-mysql: unknown service %d", service); r = -1; } end: if (mysql_stmt_free_result(stmt)) log_warnx("warn: table-mysql: mysql_stmt_free_result: %s", mysql_stmt_error(stmt)); return (r); }
static int table_redis_lookup(int service, const char *key, char *dst, size_t sz) { redisReply *reply, *elmt; unsigned int i; int r; if (config->db == NULL && config_connect(config) == 0) return (-1); reply = table_redis_query(key, service); if (reply == NULL) return (-1); r = 1; switch(service) { case K_ALIAS: if (reply->type == REDIS_REPLY_STRING) { if (dst[0] && strlcat(dst, ", ", sz) >= sz) { log_warnx("warn: table-redis: result too large"); r = -1; } if (strlcat(dst, reply->str, sz) >= sz) { log_warnx("warn: table-redis: result too large"); r = -1; } } else if (reply->type == REDIS_REPLY_ARRAY) { if (reply->elements == 0) r = 0; for (i = 0; i < reply->elements; i++) { elmt = reply->element[i]; if (elmt == NULL || elmt->type != REDIS_REPLY_STRING) { r = -1; break; } if (dst[0] && strlcat(dst, ", ", sz) >= sz) { log_warnx("warn: table-redis: result too large"); r = -1; } if (strlcat(dst, elmt->str, sz) >= sz) { log_warnx("warn: table-redis: result too large"); r = -1; } } } else r = -1; break; case K_CREDENTIALS: case K_USERINFO: case K_DOMAIN: case K_NETADDR: case K_SOURCE: case K_MAILADDR: case K_ADDRNAME: if (reply->type == REDIS_REPLY_STRING) { if (strlcpy(dst, reply->str, sz) >= sz) { log_warnx("warn: table-redis: result too large"); r = -1; } } else r = -1; break; default: log_warnx("warn: table-redis: unknown service %d", service); r = -1; } end: freeReplyObject(reply); return (r); }