#line 1026 "../../src/builtin/snarf.m4"
/* -*- buffer-read-only: t -*- vi: set ro:
   THIS FILE IS GENERATED AUTOMATICALLY.  PLEASE DO NOT EDIT.
*/
#line 1026
#ifdef HAVE_CONFIG_H
#line 1026
# include <config.h>
#line 1026
#endif
#line 1026
#include <sys/types.h>
#line 1026

#line 1026
#include "mailfromd.h"
#line 1026
#include "prog.h"
#line 1026
#include "builtin.h"
#line 1026

#line 1026

#line 1066 "../../src/builtin/snarf.m4"

/* End of snarf.m4 */
#line 1 "geoip2.bi"
/* This file is part of Mailfromd.             -*- c -*-
   Copyright (C) 2020-2025 Sergey Poznyakoff

   This program 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, or (at your option)
   any later version.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>. */


#ifdef WITH_GEOIP2
#line 18

#include <inttypes.h>
#include <maxminddb.h>

#define DEFAULT_DIR  "/usr/share/GeoIP"
#define DEFAULT_FILE "GeoLite2-City.mmdb"
#define GEOIP2_DEFAULT_DATABASE DEFAULT_DIR "/" DEFAULT_FILE
	
struct geoip2_storage {
	char *dbname;
	MMDB_s db;
	int is_open;
};

static void *
geoip2_alloc(void)
{
	struct geoip2_storage *gs = mu_zalloc(sizeof(*gs));
	gs->is_open = 0;
	return gs;
}

static void
geoip2_close(struct geoip2_storage *gs)
{
	if (gs->is_open) {
		MMDB_close(&gs->db);
		gs->is_open = 0;
	}
}

static void
geoip2_destroy(void *data)
{
	struct geoip2_storage *gs = data;
	geoip2_close(gs);
	free(gs->dbname);
	free(gs);
}


#line 58

#line 58
static int GEOIP2_id;
#line 58 "geoip2.bi"


static struct geoip2_storage *
geoip2_open(eval_environ_t env)
{
	int rc;
	struct geoip2_storage *gs = env_get_builtin_priv(env,GEOIP2_id);
	if (!gs->is_open) {
		if (!gs->dbname)
			gs->dbname = mu_strdup(GEOIP2_DEFAULT_DATABASE);
		rc = MMDB_open(gs->dbname, MMDB_MODE_MMAP, &gs->db);
			if (!(rc == MMDB_SUCCESS))
#line 69
		(
#line 69
	env_throw_bi(env, mfe_failure, NULL, "can't open database \"%s\": %s",gs->dbname,MMDB_strerror(rc))
#line 69
)
#line 72
;
		gs->is_open = 1;
	}
	return gs;
}

void
#line 78
bi_geoip2_dbname(eval_environ_t env)
#line 78

#line 78

#line 78 "geoip2.bi"
{
#line 78
	
#line 78

#line 78
        
#line 78

#line 78
        
#line 78
        adjust_stack(env, 0);
#line 78

#line 78

#line 78
	if (builtin_module_trace(BUILTIN_IDX_geoip2))
#line 78
		prog_trace(env, "geoip2_dbname");;
#line 78

{
	struct geoip2_storage *gs = env_get_builtin_priv(env,GEOIP2_id);
	
#line 81
do {
#line 81
  pushs(env, gs->dbname ? gs->dbname : GEOIP2_DEFAULT_DATABASE);
#line 81
  goto endlab;
#line 81
} while (0);
}
endlab:
#line 83
        env_function_cleanup_flush(env, CLEANUP_RETURN);
#line 83
	return;
#line 83
}

void
#line 85
bi_geoip2_open(eval_environ_t env)
#line 85

#line 85

#line 85 "geoip2.bi"
{
#line 85
	
#line 85

#line 85
        char *  name;
#line 85
        
#line 85

#line 85
        get_string_arg(env, 0, &name);
#line 85
        
#line 85
        adjust_stack(env, 1);
#line 85

#line 85

#line 85
	if (builtin_module_trace(BUILTIN_IDX_geoip2))
#line 85
		prog_trace(env, "geoip2_open %s",name);;
#line 85

{
	struct geoip2_storage *gs = env_get_builtin_priv(env,GEOIP2_id);
	geoip2_close(gs);
	free(gs->dbname);
	gs->dbname = mu_strdup(name);
	geoip2_open(env);
}

#line 93
        env_function_cleanup_flush(env, CLEANUP_RETURN);
#line 93
	return;
#line 93
}

static void
conv_utf_string(eval_environ_t env, MMDB_entry_data_s *dptr)
{
	heap_obstack_grow(env, (void*)dptr->utf8_string, dptr->data_size);
}

static void
conv_uint16(eval_environ_t env, MMDB_entry_data_s *dptr)
{
	heap_obstack_sprintf(env, "%" PRIu16, dptr->uint16);
}

static void
conv_uint32(eval_environ_t env, MMDB_entry_data_s *dptr)
{
	heap_obstack_sprintf(env, "%" PRIu32, dptr->uint32);
}

static void
conv_int32(eval_environ_t env, MMDB_entry_data_s *dptr)
{
	heap_obstack_sprintf(env, "%" PRIi32, dptr->int32);
}

static void
conv_bool(eval_environ_t env, MMDB_entry_data_s *dptr)
{
	heap_obstack_sprintf(env, "%01d", dptr->boolean ? 1 : 0);
}

static void
conv_double(eval_environ_t env, MMDB_entry_data_s *dptr)
{
	heap_obstack_sprintf(env, "%g", dptr->double_value);
}

static void
conv_float(eval_environ_t env, MMDB_entry_data_s *dptr)
{
	heap_obstack_sprintf(env, "%g", dptr->float_value);
}

static void (*entry_conv[]) (eval_environ_t, MMDB_entry_data_s *) = {
        [MMDB_DATA_TYPE_UTF8_STRING] = conv_utf_string,
        [MMDB_DATA_TYPE_UINT16]      = conv_uint16,
        [MMDB_DATA_TYPE_UINT32]      = conv_uint32,
        [MMDB_DATA_TYPE_INT32]       = conv_int32,
        [MMDB_DATA_TYPE_BOOLEAN]     = conv_bool,
        [MMDB_DATA_TYPE_DOUBLE]      = conv_double,
        [MMDB_DATA_TYPE_FLOAT]       = conv_float
};

void
#line 147
bi_geoip2_get(eval_environ_t env)
#line 147

#line 147

#line 147 "geoip2.bi"
{
#line 147
	
#line 147

#line 147
        char * MFL_DATASEG ip;
#line 147
        char * MFL_DATASEG pathstr;
#line 147
        
#line 147

#line 147
        get_string_arg(env, 0, &ip);
#line 147
        get_string_arg(env, 1, &pathstr);
#line 147
        
#line 147
        adjust_stack(env, 2);
#line 147

#line 147

#line 147
	if (builtin_module_trace(BUILTIN_IDX_geoip2))
#line 147
		prog_trace(env, "geoip2_get %s %s",ip, pathstr);;
#line 147

{
	struct geoip2_storage *gs = geoip2_open(env);
	MMDB_lookup_result_s result;
        int mmdb_error;
	int gai_error;
	MMDB_entry_data_s entry_data;
	int rc;
	struct mu_wordsplit ws;
	
	result = MMDB_lookup_string(&gs->db, ip, &gai_error, &mmdb_error);
		if (!(gai_error == 0))
#line 158
		(
#line 158
	env_throw_bi(env, mfe_failure, "geoip2_get", "%s: %s",ip,gai_strerror(gai_error))
#line 158
)
#line 161
;
	
		if (!(mmdb_error == MMDB_SUCCESS))
#line 163
		(
#line 163
	env_throw_bi(env, mfe_failure, "geoip2_get", "%s: %s: %s",pathstr,ip,MMDB_strerror(mmdb_error))
#line 163
)
#line 166
;

		if (!(result.found_entry != 0))
#line 168
		(
#line 168
	env_throw_bi(env, mfe_not_found, "geoip2_get", _("IP not found in the database"))
#line 168
)
#line 170
;

	ws.ws_delim = ".";
	rc = mu_wordsplit(pathstr, &ws, MU_WRDSF_DELIM|WRDSF_NOCMD|WRDSF_NOVAR);
	if (rc != 0) {
		char const *errstr = mu_wordsplit_strerror(&ws);
		mu_wordsplit_free(&ws);
		(
#line 177
	env_throw_bi(env, mfe_failure, "geoip2_get", "can't split string %s: %s",pathstr,errstr)
#line 177
);
#line 180
	}
	
	rc = MMDB_aget_value(&result.entry, &entry_data,
			     (const char * const* const) ws.ws_wordv);
	mu_wordsplit_free(&ws);

		if (!(rc == MMDB_SUCCESS))
#line 186
		(
#line 186
	env_throw_bi(env, (rc == MMDB_LOOKUP_PATH_DOES_NOT_MATCH_DATA_ERROR ||
#line 186
		   rc == MMDB_INVALID_LOOKUP_PATH_ERROR)
#line 186
		     ? mfe_range : mfe_failure, "geoip2_get", "%s %s: MMDB_aget_value %s",pathstr,ip,MMDB_strerror(rc))
#line 186
)
#line 191
;

	if (!entry_data.has_data)
		
#line 194
do {
#line 194
  pushs(env, "");
#line 194
  goto endlab;
#line 194
} while (0);
	
		if (!(entry_data.type >= 0
#line 196
		  && entry_data.type <= sizeof (entry_conv) / sizeof (entry_conv[0])
#line 196
		  && entry_conv[entry_data.type]))
#line 196
		(
#line 196
	env_throw_bi(env, mfe_failure, "geoip2_get", "%s: can't format %s of type %d",ip,pathstr,entry_data.type)
#line 196
)
#line 201
;

	heap_obstack_begin(env);
	heap_obstack_sprintf(env, "%s","string result:");
	entry_conv[entry_data.type] (env, &entry_data);
	do { char __c = 0; heap_obstack_grow(env, &__c, 1); } while(0);
	
#line 207
do {
#line 207
  push(env, (STKVAL) (heap_obstack_finish(env)));
#line 207
  goto endlab;
#line 207
} while (0);	
}
endlab:
#line 209
        env_function_cleanup_flush(env, CLEANUP_RETURN);
#line 209
	return;
#line 209
}

struct object_type {
	enum { OBJ_MAP, OBJ_ARRAY } type;
	size_t count;
	unsigned level;
	struct object_type *prev;
};

static inline void
object_type_push(struct object_type **otp, int type, size_t count)
{
	struct object_type *t = mu_alloc(sizeof(*t));
	t->type = type;
	t->count = count;
	t->prev = *otp;
	t->level = t->prev ? t->prev->level + 1 : 1;
	*otp = t;
}

static inline void
object_type_pop(struct object_type **otp)
{
	struct object_type *t = *otp;
	*otp = t->prev;
	free(t);
}

void
#line 237
bi_geoip2_get_json(eval_environ_t env)
#line 237

#line 237

#line 237 "geoip2.bi"
{
#line 237
	
#line 237

#line 237
        char * MFL_DATASEG ip;
#line 237
        long  indent;
#line 237
        
#line 237
        long __bi_argcnt;
#line 237
        get_numeric_arg(env, 0, &__bi_argcnt);
#line 237
        get_string_arg(env, 1, &ip);
#line 237
        if (__bi_argcnt > 1)
#line 237
                get_numeric_arg(env, 2, &indent);
#line 237
        
#line 237
        adjust_stack(env, __bi_argcnt + 1);
#line 237

#line 237

#line 237
	if (builtin_module_trace(BUILTIN_IDX_geoip2))
#line 237
		prog_trace(env, "geoip2_get_json %s %lu",ip, ((__bi_argcnt > 1) ? indent : 0));;
#line 237

{
	struct geoip2_storage *gs = geoip2_open(env);
	MMDB_lookup_result_s result;
        int mmdb_error;
	int gai_error;
	int rc;
	MMDB_entry_data_list_s *data_list, *p;
	struct object_type *type = NULL;
	int iskey = 1;
	size_t i;
	char *indent_str = NULL;
	
	result = MMDB_lookup_string(&gs->db, ip, &gai_error, &mmdb_error);
		if (!(gai_error == 0))
#line 251
		(
#line 251
	env_throw_bi(env, mfe_failure, "geoip2_get_json", "%s: %s",ip,gai_strerror(gai_error))
#line 251
)
#line 254
;
	
		if (!(mmdb_error == MMDB_SUCCESS))
#line 256
		(
#line 256
	env_throw_bi(env, mfe_failure, "geoip2_get_json", "%s: %s",ip,MMDB_strerror(mmdb_error))
#line 256
)
#line 259
;

		if (!(result.found_entry != 0))
#line 261
		(
#line 261
	env_throw_bi(env, mfe_not_found, "geoip2_get_json", _("IP not found in the database"))
#line 261
)
#line 263
;

	rc = MMDB_get_entry_data_list(&result.entry, &data_list);
		if (!(rc == MMDB_SUCCESS))
#line 266
		(
#line 266
	env_throw_bi(env, mfe_failure, "geoip2_get_json", "%s: MMDB_aget_value %s",ip,MMDB_strerror(rc))
#line 266
)
#line 269
;

	/*MMDB_dump_entry_data_list(stdout, data_list, 4);*/

	indent = ((__bi_argcnt > 1) ? indent : 0);
	if (indent) {
		indent_str = mu_alloc(indent+1);
		memset(indent_str, ' ', indent);
		indent_str[indent] = 0;
	}
	
	heap_obstack_begin(env);
	for (p = data_list; p; p = p->next) {
		if (iskey && type && indent_str) {
			do { char __c = '\n'; heap_obstack_grow(env, &__c, 1); } while(0);
			for (i = 0; i < type->level; i++)
				
#line 285
do {
#line 285
  char *__s = indent_str;
#line 285
  heap_obstack_grow(env, __s, strlen(__s));
#line 285
} while (0);
		}

		if (type == NULL && p->entry_data.type != MMDB_DATA_TYPE_MAP) {
			heap_obstack_cancel(env);
			free(indent_str);
			MMDB_free_entry_data_list(data_list);
			(
#line 292
	env_throw_bi(env, mfe_failure, "geoip2_get_json", "%s",_("malformed data list"))
#line 292
);
#line 295
		}
		
		switch (p->entry_data.type) {
		case MMDB_DATA_TYPE_MAP:
			object_type_push(&type, OBJ_MAP, p->entry_data.data_size);
			do { char __c = '{'; heap_obstack_grow(env, &__c, 1); } while(0);
			iskey = 1;
			continue;
			
		case MMDB_DATA_TYPE_ARRAY:
			object_type_push(&type, OBJ_ARRAY, p->entry_data.data_size);
			do { char __c = '['; heap_obstack_grow(env, &__c, 1); } while(0);
			continue;
			
		case MMDB_DATA_TYPE_UTF8_STRING:
			do { char __c = '"'; heap_obstack_grow(env, &__c, 1); } while(0);
			heap_obstack_grow(env, (char*)p->entry_data.utf8_string, p->entry_data.data_size);
			do { char __c = '"'; heap_obstack_grow(env, &__c, 1); } while(0);
			if (iskey)
				do { char __c = ':'; heap_obstack_grow(env, &__c, 1); } while(0);
			break;
			
		case MMDB_DATA_TYPE_DOUBLE:
			heap_obstack_sprintf(env, "%g",p->entry_data.double_value);
			break;
			
		case MMDB_DATA_TYPE_BYTES:
			do { char __c = '['; heap_obstack_grow(env, &__c, 1); } while(0);
			for (i = 0; i < p->entry_data.data_size; i++) {
				if (i) do { char __c = ','; heap_obstack_grow(env, &__c, 1); } while(0);
				heap_obstack_grow(env, "%d", p->entry_data.bytes[i]);
			}
			do { char __c = ']'; heap_obstack_grow(env, &__c, 1); } while(0);
			break;
			
		case MMDB_DATA_TYPE_UINT16:
			heap_obstack_sprintf(env, "%u",p->entry_data.uint16);
			break;
			
		case MMDB_DATA_TYPE_UINT32:
			heap_obstack_sprintf(env, "%" PRIu32,p->entry_data.uint32);
			break;
			
		case MMDB_DATA_TYPE_INT32:
			heap_obstack_sprintf(env, "%" PRIi32,p->entry_data.int32);
			break;
			
		case MMDB_DATA_TYPE_UINT64:
			heap_obstack_sprintf(env, "%" PRIu64,p->entry_data.uint64);
			break;
			
		case MMDB_DATA_TYPE_UINT128:
			
#line 347
do {
#line 347
  char *__s = "'N/A";
#line 347
  heap_obstack_grow(env, __s, strlen(__s));
#line 347
} while (0);//FIXME
			break;
			
		case MMDB_DATA_TYPE_BOOLEAN:
			
#line 351
do {
#line 351
  char *__s = p->entry_data.boolean ? "true" : "false";
#line 351
  heap_obstack_grow(env, __s, strlen(__s));
#line 351
} while (0);
			break;
					
		case MMDB_DATA_TYPE_FLOAT:
			heap_obstack_sprintf(env, "%g",p->entry_data.float_value);
			break;

		default:
			heap_obstack_cancel(env);
			free(indent_str);
			MMDB_free_entry_data_list(data_list);
			(
#line 362
	env_throw_bi(env, mfe_failure, "geoip2_get_json", _("unsupported MMDB data type %d"),p->entry_data.type)
#line 362
);
#line 365
		}

		if (type) {
			iskey = !iskey;
			if (iskey == 1) {
				while (type && --type->count == 0) {
					if (indent_str) {
						do { char __c = '\n'; heap_obstack_grow(env, &__c, 1); } while(0);
						for (i = 1; i < type->level; i++)
							
#line 374
do {
#line 374
  char *__s = indent_str;
#line 374
  heap_obstack_grow(env, __s, strlen(__s));
#line 374
} while (0);
					}
					do { char __c = type->type == OBJ_MAP
#line 376
							 ? '}' : ']'; heap_obstack_grow(env, &__c, 1); } while(0);
#line 378
					object_type_pop(&type);
				}
				if (type) {
					do { char __c = ','; heap_obstack_grow(env, &__c, 1); } while(0);
				}
			}
		}
	}
	do { char __c = 0; heap_obstack_grow(env, &__c, 1); } while(0);
	free(indent_str);
	MMDB_free_entry_data_list(data_list);

	if (type) {
		while (type)
			object_type_pop(&type);
		(
#line 393
	env_throw_bi(env, mfe_failure, "geoip2_get_json", _("malformed data list: reported and actual number of keys differ"))
#line 393
);
#line 395
	}
	
	
#line 397
do {
#line 397
  push(env, (STKVAL) (heap_obstack_finish(env)));
#line 397
  goto endlab;
#line 397
} while (0);	
}
endlab:
#line 399
        env_function_cleanup_flush(env, CLEANUP_RETURN);
#line 399
	return;
#line 399
}
#line 1026 "../../src/builtin/snarf.m4"

#line 1026

#line 1026
#endif /* WITH_GEOIP2 */
#line 1026

#line 1026
void
#line 1026
geoip2_init_builtin(void)
#line 1026
{
#line 1026
	
#line 1026
#ifdef WITH_GEOIP2
#line 1026
	pp_define("WITH_GEOIP2");
#line 1026
	#line 58 "geoip2.bi"
GEOIP2_id = builtin_priv_register(geoip2_alloc, geoip2_destroy,
#line 58
NULL);
#line 78 "geoip2.bi"
va_builtin_install_ex("geoip2_dbname", bi_geoip2_dbname, 0, dtype_string, 0, 0, 0|0, dtype_unspecified);
#line 85 "geoip2.bi"
va_builtin_install_ex("geoip2_open", bi_geoip2_open, 0, dtype_unspecified, 1, 0, 0|0, dtype_string);
#line 147 "geoip2.bi"
va_builtin_install_ex("geoip2_get", bi_geoip2_get, 0, dtype_string, 2, 0, 0|0, dtype_string, dtype_string);
#line 237 "geoip2.bi"
va_builtin_install_ex("geoip2_get_json", bi_geoip2_get_json, 0, dtype_string, 2, 1, 0|0, dtype_string, dtype_number);

#line 1026 "../../src/builtin/snarf.m4"
	
#line 1026
#endif /* WITH_GEOIP2 */
#line 1026
}
#line 1026 "../../src/builtin/snarf.m4"

