Your message dated Mon, 27 Jul 2026 14:35:25 +0000
with message-id <[email protected]>
and subject line Bug#1141384: fixed in geoip 1.6.12-12
has caused the Debian Bug report #1141384,
regarding geoip: diff for NMU version 1.6.12-11.2
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact [email protected]
immediately.)


-- 
1141384: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1141384
Debian Bug Tracking System
Contact [email protected] with problems
--- Begin Message ---
Package: geoip
Version: 1.6.12-11.1
Severity: normal
Tags: patch  pending

Dear maintainer,

I've uploaded an NMU for geoip (versioned as 1.6.12-11.2).
The diff is attached to this message.

This is a revert to 1.6.12-10 except for the time_t transition,
for reverting geoip-database to 20191224 (see #1134158).

cu
Adrian
diffstat for geoip-1.6.12 geoip-1.6.12

 changelog                     |    7 
 control                       |    1 
 copyright                     |    6 
 geoip-bin.install             |    5 
 geoip-bin.lintian-overrides   |    3 
 rules                         |    9 
 src/countryInfo.txt           |  303 ++++++++
 src/geoip-asn-csv-to-dat.cpp  |  898 +++++++++++++++++++++++++
 src/geoip-csv-to-dat.cpp      | 1508 ++++++++++++++++++++++++++++++++++++++++++
 src/geolite2-to-legacy-csv.sh |   99 ++
 src/v4-to-v6-layout.pl        |   31 
 11 files changed, 2870 insertions(+)

diff -Nru geoip-1.6.12/debian/changelog geoip-1.6.12/debian/changelog
--- geoip-1.6.12/debian/changelog	2024-02-28 18:54:53.000000000 +0200
+++ geoip-1.6.12/debian/changelog	2026-07-03 21:06:14.000000000 +0300
@@ -1,3 +1,10 @@
+geoip (1.6.12-11.2) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * Restore the generator scripts for geoip-database.
+
+ -- Adrian Bunk <[email protected]>  Fri, 03 Jul 2026 21:06:14 +0300
+
 geoip (1.6.12-11.1) unstable; urgency=medium
 
   * Non-maintainer upload.
diff -Nru geoip-1.6.12/debian/control geoip-1.6.12/debian/control
--- geoip-1.6.12/debian/control	2024-02-28 18:54:53.000000000 +0200
+++ geoip-1.6.12/debian/control	2026-07-03 21:06:03.000000000 +0300
@@ -50,6 +50,7 @@
 Architecture: any
 Pre-Depends: ${misc:Pre-Depends}
 Depends: ${misc:Depends},
+ libnetaddr-ip-perl,
  ${shlibs:Depends}
 Replaces: libgeoip1t64 (<= 1.4.4.dfsg-2)
 Multi-Arch: foreign
diff -Nru geoip-1.6.12/debian/copyright geoip-1.6.12/debian/copyright
--- geoip-1.6.12/debian/copyright	2023-07-04 16:32:46.000000000 +0300
+++ geoip-1.6.12/debian/copyright	2026-07-03 21:06:14.000000000 +0300
@@ -9,6 +9,12 @@
   2009, Kalle Olavi Niemitalo
 License: ISC
 
+Files: debian/src/geoip-csv-to-dat.cpp
+Copyright: 2014, Andrew Moise
+  2011, Patrick Matthäi
+  2009, Kalle Olavi Niemitalo
+License: ISC
+
 Files: libGeoIP/GeoIP.c
   libGeoIP/GeoIP.h
   libGeoIP/GeoIPCity.c
diff -Nru geoip-1.6.12/debian/geoip-bin.install geoip-1.6.12/debian/geoip-bin.install
--- geoip-1.6.12/debian/geoip-bin.install	2023-07-04 16:32:46.000000000 +0300
+++ geoip-1.6.12/debian/geoip-bin.install	2026-07-03 21:05:13.000000000 +0300
@@ -1 +1,6 @@
 debian/tmp/usr/bin/* usr/bin/
+debian/tmp/geoip-generator usr/lib/geoip/
+debian/tmp/geoip-generator-asn usr/lib/geoip/
+debian/src/v4-to-v6-layout.pl usr/lib/geoip/
+debian/src/geolite2-to-legacy-csv.sh usr/lib/geoip/
+debian/src/countryInfo.txt usr/share/geoip/
diff -Nru geoip-1.6.12/debian/geoip-bin.lintian-overrides geoip-1.6.12/debian/geoip-bin.lintian-overrides
--- geoip-1.6.12/debian/geoip-bin.lintian-overrides	1970-01-01 02:00:00.000000000 +0200
+++ geoip-1.6.12/debian/geoip-bin.lintian-overrides	2026-07-03 21:05:13.000000000 +0300
@@ -0,0 +1,3 @@
+geoip-bin: hardening-no-fortify-functions [usr/lib/geoip/geoip-generator*
+geoip-bin: package-contains-documentation-outside-usr-share-doc [usr/share/geoip/countryInfo.txt]
+geoip-bin: executable-in-usr-lib [usr/lib/geoip/*
diff -Nru geoip-1.6.12/debian/rules geoip-1.6.12/debian/rules
--- geoip-1.6.12/debian/rules	2023-07-04 16:32:46.000000000 +0300
+++ geoip-1.6.12/debian/rules	2026-07-03 21:05:13.000000000 +0300
@@ -6,11 +6,20 @@
 include /usr/share/dpkg/architecture.mk
 include /usr/share/dpkg/pkg-info.mk
 
+ifeq ($(origin CXX),default)
+	CXX = $(DEB_HOST_GNU_TYPE)-g++
+endif
+
 %:
 	dh $@ --with autoreconf
 
 override_dh_auto_install:
 	$(MAKE) install DESTDIR=$(CURDIR)/debian/tmp
+	# Build the build script.
+	$(CXX) $(CPPFLAGS) -std=gnu++11 $(LDFLAGS) -g debian/src/geoip-csv-to-dat.cpp -o debian/tmp/geoip-generator -lGeoIP \
+		-I $(CURDIR)/debian/tmp/usr/include/ -L $(CURDIR)/debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/
+	$(CXX) $(CPPFLAGS) -std=gnu++11 $(LDFLAGS) -g debian/src/geoip-asn-csv-to-dat.cpp -o debian/tmp/geoip-generator-asn -lGeoIP \
+		-I $(CURDIR)/debian/tmp/usr/include/ -L $(CURDIR)/debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/
 	chrpath -d -k debian/tmp/usr/bin/geoip*
 
 override_dh_auto_configure:
diff -Nru geoip-1.6.12/debian/src/countryInfo.txt geoip-1.6.12/debian/src/countryInfo.txt
--- geoip-1.6.12/debian/src/countryInfo.txt	1970-01-01 02:00:00.000000000 +0200
+++ geoip-1.6.12/debian/src/countryInfo.txt	2026-07-03 21:05:13.000000000 +0300
@@ -0,0 +1,303 @@
+# GeoNames.org Country Information																		
+# ================================																		
+#																		
+#																		
+# CountryCodes:																		
+# ============																		
+#																		
+# The official ISO country code for the United Kingdom is 'GB'. The code 'UK' is reserved.																		
+# 																		
+# A list of dependent countries is available here:																		
+# https://spreadsheets.google.com/ccc?key=pJpyPy-J5JSNhe7F_KxwiCA&hl=en 																		
+#																		
+#																		
+# The countrycode XK temporarily stands for Kosvo:																		
+# http://geonames.wordpress.com/2010/03/08/xk-country-code-for-kosovo/																		
+#																		
+#																		
+# CS (Serbia and Montenegro) with geonameId = 8505033 no longer exists.																		
+# AN (the Netherlands Antilles) with geonameId = 8505032 was dissolved on 10 October 2010.																		
+#																		
+#																		
+# Currencies :																		
+# ============																		
+#																		
+# A number of territories are not included in ISO 4217, because their currencies are not per se an independent currency, 																		
+# but a variant of another currency. These currencies are:																		
+#																		
+# 1. FO : Faroese krona (1:1 pegged to the Danish krone)																		
+# 2. GG : Guernsey pound (1:1 pegged to the pound sterling)																		
+# 3. JE : Jersey pound (1:1 pegged to the pound sterling)																		
+# 4. IM : Isle of Man pound (1:1 pegged to the pound sterling)																		
+# 5. TV : Tuvaluan dollar (1:1 pegged to the Australian dollar).																		
+# 6. CK : Cook Islands dollar (1:1 pegged to the New Zealand dollar).																		
+#																		
+# The following non-ISO codes are, however, sometimes used: GGP for the Guernsey pound, 																		
+# JEP for the Jersey pound and IMP for the Isle of Man pound (http://en.wikipedia.org/wiki/ISO_4217)																		
+#																		
+#																		
+# A list of currency symbols is available here : http://forum.geonames.org/gforum/posts/list/437.page																		
+# another list with fractional units is here: http://forum.geonames.org/gforum/posts/list/1961.page																		
+#																		
+#																		
+# Languages :																		
+# ===========																		
+#																		
+# The column 'languages' lists the languages spoken in a country ordered by the number of speakers. The language code is a 'locale' 																		
+# where any two-letter primary-tag is an ISO-639 language abbreviation and any two-letter initial subtag is an ISO-3166 country code.																		
+#																		
+# Example : es-AR is the Spanish variant spoken in Argentina.																		
+#																		
+#ISO	ISO3	ISO-Numeric	fips	Country	Capital	Area(in sq km)	Population	Continent	tld	CurrencyCode	CurrencyName	Phone	Postal Code Format	Postal Code Regex	Languages	geonameid	neighbours	EquivalentFipsCode
+AD	AND	020	AN	Andorra	Andorra la Vella	468	84000	EU	.ad	EUR	Euro	376	AD###	^(?:AD)*(\d{3})$	ca	3041565	ES,FR	
+AE	ARE	784	AE	United Arab Emirates	Abu Dhabi	82880	4975593	AS	.ae	AED	Dirham	971			ar-AE,fa,en,hi,ur	290557	SA,OM	
+AF	AFG	004	AF	Afghanistan	Kabul	647500	29121286	AS	.af	AFN	Afghani	93			fa-AF,ps,uz-AF,tk	1149361	TM,CN,IR,TJ,PK,UZ	
+AG	ATG	028	AC	Antigua and Barbuda	St. John's	443	86754	NA	.ag	XCD	Dollar	+1-268			en-AG	3576396		
+AI	AIA	660	AV	Anguilla	The Valley	102	13254	NA	.ai	XCD	Dollar	+1-264			en-AI	3573511		
+AL	ALB	008	AL	Albania	Tirana	28748	2986952	EU	.al	ALL	Lek	355	####	^(\d{4})$	sq,el	783754	MK,GR,ME,RS,XK	
+AM	ARM	051	AM	Armenia	Yerevan	29800	2968000	AS	.am	AMD	Dram	374	######	^(\d{6})$	hy	174982	GE,IR,AZ,TR	
+AO	AGO	024	AO	Angola	Luanda	1246700	13068161	AF	.ao	AOA	Kwanza	244			pt-AO	3351879	CD,NA,ZM,CG	
+AQ	ATA	010	AY	Antarctica		14000000	0	AN	.aq							6697173		
+AR	ARG	032	AR	Argentina	Buenos Aires	2766890	41343201	SA	.ar	ARS	Peso	54	@####@@@	^[A-Z]?\d{4}[A-Z]{0,3}$	es-AR,en,it,de,fr,gn	3865483	CL,BO,UY,PY,BR	
+AS	ASM	016	AQ	American Samoa	Pago Pago	199	57881	OC	.as	USD	Dollar	+1-684	#####-####	96799	en-AS,sm,to	5880801		
+AT	AUT	040	AU	Austria	Vienna	83858	8205000	EU	.at	EUR	Euro	43	####	^(\d{4})$	de-AT,hr,hu,sl	2782113	CH,DE,HU,SK,CZ,IT,SI,LI	
+AU	AUS	036	AS	Australia	Canberra	7686850	21515754	OC	.au	AUD	Dollar	61	####	^(\d{4})$	en-AU	2077456		
+AW	ABW	533	AA	Aruba	Oranjestad	193	71566	NA	.aw	AWG	Guilder	297			nl-AW,pap,es,en	3577279		
+AX	ALA	248		Aland Islands	Mariehamn	1580	26711	EU	.ax	EUR	Euro	+358-18	#####	^(?:FI)*(\d{5})$	sv-AX	661882		FI
+AZ	AZE	031	AJ	Azerbaijan	Baku	86600	8303512	AS	.az	AZN	Manat	994	AZ ####	^(?:AZ)*(\d{4})$	az,ru,hy	587116	GE,IR,AM,TR,RU	
+BA	BIH	070	BK	Bosnia and Herzegovina	Sarajevo	51129	4590000	EU	.ba	BAM	Marka	387	#####	^(\d{5})$	bs,hr-BA,sr-BA	3277605	HR,ME,RS	
+BB	BRB	052	BB	Barbados	Bridgetown	431	285653	NA	.bb	BBD	Dollar	+1-246	BB#####	^(?:BB)*(\d{5})$	en-BB	3374084		
+BD	BGD	050	BG	Bangladesh	Dhaka	144000	156118464	AS	.bd	BDT	Taka	880	####	^(\d{4})$	bn-BD,en	1210997	MM,IN	
+BE	BEL	056	BE	Belgium	Brussels	30510	10403000	EU	.be	EUR	Euro	32	####	^(\d{4})$	nl-BE,fr-BE,de-BE	2802361	DE,NL,LU,FR	
+BF	BFA	854	UV	Burkina Faso	Ouagadougou	274200	16241811	AF	.bf	XOF	Franc	226			fr-BF,mos	2361809	NE,BJ,GH,CI,TG,ML	
+BG	BGR	100	BU	Bulgaria	Sofia	110910	7148785	EU	.bg	BGN	Lev	359	####	^(\d{4})$	bg,tr-BG,rom	732800	MK,GR,RO,TR,RS	
+BH	BHR	048	BA	Bahrain	Manama	665	738004	AS	.bh	BHD	Dinar	973	####|###	^(\d{3}\d?)$	ar-BH,en,fa,ur	290291		
+BI	BDI	108	BY	Burundi	Bujumbura	27830	9863117	AF	.bi	BIF	Franc	257			fr-BI,rn	433561	TZ,CD,RW	
+BJ	BEN	204	BN	Benin	Porto-Novo	112620	9056010	AF	.bj	XOF	Franc	229			fr-BJ	2395170	NE,TG,BF,NG	
+BL	BLM	652	TB	Saint Barthelemy	Gustavia	21	8450	NA	.gp	EUR	Euro	590	#####	^(\d{5})$	fr	3578476		
+BM	BMU	060	BD	Bermuda	Hamilton	53	65365	NA	.bm	BMD	Dollar	+1-441	@@ ##	^([A-Z]{2}\d{2})$	en-BM,pt	3573345		
+BN	BRN	096	BX	Brunei	Bandar Seri Begawan	5770	395027	AS	.bn	BND	Dollar	673	@@####	^([A-Z]{2}\d{4})$	ms-BN,en-BN	1820814	MY	
+BO	BOL	068	BL	Bolivia	Sucre	1098580	9947418	SA	.bo	BOB	Boliviano	591			es-BO,qu,ay	3923057	PE,CL,PY,BR,AR	
+BQ	BES	535		Bonaire, Saint Eustatius and Saba 		328	18012	NA	.bq	USD	Dollar	599			nl,pap,en	7626844		
+BR	BRA	076	BR	Brazil	Brasilia	8511965	201103330	SA	.br	BRL	Real	55	#####-###	^\d{5}-\d{3}$	pt-BR,es,en,fr	3469034	SR,PE,BO,UY,GY,PY,GF,VE,CO,AR	
+BS	BHS	044	BF	Bahamas	Nassau	13940	301790	NA	.bs	BSD	Dollar	+1-242			en-BS	3572887		
+BT	BTN	064	BT	Bhutan	Thimphu	47000	699847	AS	.bt	BTN	Ngultrum	975			dz	1252634	CN,IN	
+BV	BVT	074	BV	Bouvet Island		49	0	AN	.bv	NOK	Krone					3371123		
+BW	BWA	072	BC	Botswana	Gaborone	600370	2029307	AF	.bw	BWP	Pula	267			en-BW,tn-BW	933860	ZW,ZA,NA	
+BY	BLR	112	BO	Belarus	Minsk	207600	9685000	EU	.by	BYN	Belarusian ruble	375	######	^(\d{6})$	be,ru	630336	PL,LT,UA,RU,LV	
+BZ	BLZ	084	BH	Belize	Belmopan	22966	314522	NA	.bz	BZD	Dollar	501			en-BZ,es	3582678	GT,MX	
+CA	CAN	124	CA	Canada	Ottawa	9984670	33679000	NA	.ca	CAD	Dollar	1	@#@ #@#	^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ]) ?(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$ 	en-CA,fr-CA,iu	6251999	US	
+CC	CCK	166	CK	Cocos Islands	West Island	14	628	AS	.cc	AUD	Dollar	61			ms-CC,en	1547376		
+CD	COD	180	CG	Democratic Republic of the Congo	Kinshasa	2345410	70916439	AF	.cd	CDF	Franc	243			fr-CD,ln,ktu,kg,sw,lua	203312	TZ,CF,SS,RW,ZM,BI,UG,CG,AO	
+CF	CAF	140	CT	Central African Republic	Bangui	622984	4844927	AF	.cf	XAF	Franc	236			fr-CF,sg,ln,kg	239880	TD,SD,CD,SS,CM,CG	
+CG	COG	178	CF	Republic of the Congo	Brazzaville	342000	3039126	AF	.cg	XAF	Franc	242			fr-CG,kg,ln-CG	2260494	CF,GA,CD,CM,AO	
+CH	CHE	756	SZ	Switzerland	Bern	41290	8484100	EU	.ch	CHF	Franc	41	####	^(\d{4})$	de-CH,fr-CH,it-CH,rm	2658434	DE,IT,LI,FR,AT	
+CI	CIV	384	IV	Ivory Coast	Yamoussoukro	322460	21058798	AF	.ci	XOF	Franc	225			fr-CI	2287781	LR,GH,GN,BF,ML	
+CK	COK	184	CW	Cook Islands	Avarua	240	21388	OC	.ck	NZD	Dollar	682			en-CK,mi	1899402		
+CL	CHL	152	CI	Chile	Santiago	756950	16746491	SA	.cl	CLP	Peso	56	#######	^(\d{7})$	es-CL	3895114	PE,BO,AR	
+CM	CMR	120	CM	Cameroon	Yaounde	475440	19294149	AF	.cm	XAF	Franc	237			en-CM,fr-CM	2233387	TD,CF,GA,GQ,CG,NG	
+CN	CHN	156	CH	China	Beijing	9596960	1330044000	AS	.cn	CNY	Yuan Renminbi	86	######	^(\d{6})$	zh-CN,yue,wuu,dta,ug,za	1814991	LA,BT,TJ,KZ,MN,AF,NP,MM,KG,PK,KP,RU,VN,IN	
+CO	COL	170	CO	Colombia	Bogota	1138910	47790000	SA	.co	COP	Peso	57	######	^(\d{6})$	es-CO	3686110	EC,PE,PA,BR,VE	
+CR	CRI	188	CS	Costa Rica	San Jose	51100	4516220	NA	.cr	CRC	Colon	506	#####	^(\d{5})$	es-CR,en	3624060	PA,NI	
+CU	CUB	192	CU	Cuba	Havana	110860	11423000	NA	.cu	CUP	Peso	53	CP #####	^(?:CP)*(\d{5})$	es-CU,pap	3562981	US	
+CV	CPV	132	CV	Cabo Verde	Praia	4033	508659	AF	.cv	CVE	Escudo	238	####	^(\d{4})$	pt-CV	3374766		
+CW	CUW	531	UC	Curacao	 Willemstad	444	141766	NA	.cw	ANG	Guilder	599			nl,pap	7626836		
+CX	CXR	162	KT	Christmas Island	Flying Fish Cove	135	1500	OC	.cx	AUD	Dollar	61	####	^(\d{4})$	en,zh,ms-CC	2078138		
+CY	CYP	196	CY	Cyprus	Nicosia	9250	1102677	EU	.cy	EUR	Euro	357	####	^(\d{4})$	el-CY,tr-CY,en	146669		
+CZ	CZE	203	EZ	Czechia	Prague	78866	10476000	EU	.cz	CZK	Koruna	420	### ##	^\d{3}\s?\d{2}$	cs,sk	3077311	PL,DE,SK,AT	
+DE	DEU	276	GM	Germany	Berlin	357021	81802257	EU	.de	EUR	Euro	49	#####	^(\d{5})$	de	2921044	CH,PL,NL,DK,BE,CZ,LU,FR,AT	
+DJ	DJI	262	DJ	Djibouti	Djibouti	23000	740528	AF	.dj	DJF	Franc	253			fr-DJ,ar,so-DJ,aa	223816	ER,ET,SO	
+DK	DNK	208	DA	Denmark	Copenhagen	43094	5484000	EU	.dk	DKK	Krone	45	####	^(\d{4})$	da-DK,en,fo,de-DK	2623032	DE	
+DM	DMA	212	DO	Dominica	Roseau	754	72813	NA	.dm	XCD	Dollar	+1-767			en-DM	3575830		
+DO	DOM	214	DR	Dominican Republic	Santo Domingo	48730	9823821	NA	.do	DOP	Peso	+1-809 and 1-829	#####	^(\d{5})$	es-DO	3508796	HT	
+DZ	DZA	012	AG	Algeria	Algiers	2381740	34586184	AF	.dz	DZD	Dinar	213	#####	^(\d{5})$	ar-DZ	2589581	NE,EH,LY,MR,TN,MA,ML	
+EC	ECU	218	EC	Ecuador	Quito	283560	14790608	SA	.ec	USD	Dollar	593	@####@	^([a-zA-Z]\d{4}[a-zA-Z])$	es-EC	3658394	PE,CO	
+EE	EST	233	EN	Estonia	Tallinn	45226	1291170	EU	.ee	EUR	Euro	372	#####	^(\d{5})$	et,ru	453733	RU,LV	
+EG	EGY	818	EG	Egypt	Cairo	1001450	80471869	AF	.eg	EGP	Pound	20	#####	^(\d{5})$	ar-EG,en,fr	357994	LY,SD,IL,PS	
+EH	ESH	732	WI	Western Sahara	El-Aaiun	266000	273008	AF	.eh	MAD	Dirham	212			ar,mey	2461445	DZ,MR,MA	
+ER	ERI	232	ER	Eritrea	Asmara	121320	5792984	AF	.er	ERN	Nakfa	291			aa-ER,ar,tig,kun,ti-ER	338010	ET,SD,DJ	
+ES	ESP	724	SP	Spain	Madrid	504782	46505963	EU	.es	EUR	Euro	34	#####	^(\d{5})$	es-ES,ca,gl,eu,oc	2510769	AD,PT,GI,FR,MA	
+ET	ETH	231	ET	Ethiopia	Addis Ababa	1127127	88013491	AF	.et	ETB	Birr	251	####	^(\d{4})$	am,en-ET,om-ET,ti-ET,so-ET,sid	337996	ER,KE,SD,SS,SO,DJ	
+FI	FIN	246	FI	Finland	Helsinki	337030	5244000	EU	.fi	EUR	Euro	358	#####	^(?:FI)*(\d{5})$	fi-FI,sv-FI,smn	660013	NO,RU,SE	
+FJ	FJI	242	FJ	Fiji	Suva	18270	875983	OC	.fj	FJD	Dollar	679			en-FJ,fj	2205218		
+FK	FLK	238	FK	Falkland Islands	Stanley	12173	2638	SA	.fk	FKP	Pound	500			en-FK	3474414		
+FM	FSM	583	FM	Micronesia	Palikir	702	107708	OC	.fm	USD	Dollar	691	#####	^(\d{5})$	en-FM,chk,pon,yap,kos,uli,woe,nkr,kpg	2081918		
+FO	FRO	234	FO	Faroe Islands	Torshavn	1399	48228	EU	.fo	DKK	Krone	298	###	^(?:FO)*(\d{3})$	fo,da-FO	2622320		
+FR	FRA	250	FR	France	Paris	547030	64768389	EU	.fr	EUR	Euro	33	#####	^(\d{5})$	fr-FR,frp,br,co,ca,eu,oc	3017382	CH,DE,BE,LU,IT,AD,MC,ES	
+GA	GAB	266	GB	Gabon	Libreville	267667	1545255	AF	.ga	XAF	Franc	241			fr-GA	2400553	CM,GQ,CG	
+GB	GBR	826	UK	United Kingdom	London	244820	62348447	EU	.uk	GBP	Pound	44	@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA	^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([A-Za-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z])))) [0-9][A-Za-z]{2})$	en-GB,cy-GB,gd	2635167	IE	
+GD	GRD	308	GJ	Grenada	St. George's	344	107818	NA	.gd	XCD	Dollar	+1-473			en-GD	3580239		
+GE	GEO	268	GG	Georgia	Tbilisi	69700	4630000	AS	.ge	GEL	Lari	995	####	^(\d{4})$	ka,ru,hy,az	614540	AM,AZ,TR,RU	
+GF	GUF	254	FG	French Guiana	Cayenne	91000	195506	SA	.gf	EUR	Euro	594	#####	^((97|98)3\d{2})$	fr-GF	3381670	SR,BR	
+GG	GGY	831	GK	Guernsey	St Peter Port	78	65228	EU	.gg	GBP	Pound	+44-1481	@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA	^((?:(?:[A-PR-UWYZ][A-HK-Y]\d[ABEHMNPRV-Y0-9]|[A-PR-UWYZ]\d[A-HJKPS-UW0-9])\s\d[ABD-HJLNP-UW-Z]{2})|GIR\s?0AA)$	en,nrf	3042362		
+GH	GHA	288	GH	Ghana	Accra	239460	24339838	AF	.gh	GHS	Cedi	233			en-GH,ak,ee,tw	2300660	CI,TG,BF	
+GI	GIB	292	GI	Gibraltar	Gibraltar	6.5	27884	EU	.gi	GIP	Pound	350			en-GI,es,it,pt	2411586	ES	
+GL	GRL	304	GL	Greenland	Nuuk	2166086	56375	NA	.gl	DKK	Krone	299	####	^(\d{4})$	kl,da-GL,en	3425505		
+GM	GMB	270	GA	Gambia	Banjul	11300	1593256	AF	.gm	GMD	Dalasi	220			en-GM,mnk,wof,wo,ff	2413451	SN	
+GN	GIN	324	GV	Guinea	Conakry	245857	10324025	AF	.gn	GNF	Franc	224			fr-GN	2420477	LR,SN,SL,CI,GW,ML	
+GP	GLP	312	GP	Guadeloupe	Basse-Terre	1780	443000	NA	.gp	EUR	Euro	590	#####	^((97|98)\d{3})$	fr-GP	3579143		
+GQ	GNQ	226	EK	Equatorial Guinea	Malabo	28051	1014999	AF	.gq	XAF	Franc	240			es-GQ,fr	2309096	GA,CM	
+GR	GRC	300	GR	Greece	Athens	131940	11000000	EU	.gr	EUR	Euro	30	### ##	^(\d{5})$	el-GR,en,fr	390903	AL,MK,TR,BG	
+GS	SGS	239	SX	South Georgia and the South Sandwich Islands	Grytviken	3903	30	AN	.gs	GBP	Pound				en	3474415		
+GT	GTM	320	GT	Guatemala	Guatemala City	108890	13550440	NA	.gt	GTQ	Quetzal	502	#####	^(\d{5})$	es-GT	3595528	MX,HN,BZ,SV	
+GU	GUM	316	GQ	Guam	Hagatna	549	159358	OC	.gu	USD	Dollar	+1-671	969##	^(969\d{2})$	en-GU,ch-GU	4043988		
+GW	GNB	624	PU	Guinea-Bissau	Bissau	36120	1565126	AF	.gw	XOF	Franc	245	####	^(\d{4})$	pt-GW,pov	2372248	SN,GN	
+GY	GUY	328	GY	Guyana	Georgetown	214970	748486	SA	.gy	GYD	Dollar	592			en-GY	3378535	SR,BR,VE	
+HK	HKG	344	HK	Hong Kong	Hong Kong	1092	6898686	AS	.hk	HKD	Dollar	852			zh-HK,yue,zh,en	1819730		
+HM	HMD	334	HM	Heard Island and McDonald Islands		412	0	AN	.hm	AUD	Dollar	 				1547314		
+HN	HND	340	HO	Honduras	Tegucigalpa	112090	7989415	NA	.hn	HNL	Lempira	504	@@####	^([A-Z]{2}\d{4})$	es-HN,cab,miq	3608932	GT,NI,SV	
+HR	HRV	191	HR	Croatia	Zagreb	56542	4284889	EU	.hr	HRK	Kuna	385	#####	^(?:HR)*(\d{5})$	hr-HR,sr	3202326	HU,SI,BA,ME,RS	
+HT	HTI	332	HA	Haiti	Port-au-Prince	27750	9648924	NA	.ht	HTG	Gourde	509	HT####	^(?:HT)*(\d{4})$	ht,fr-HT	3723988	DO	
+HU	HUN	348	HU	Hungary	Budapest	93030	9982000	EU	.hu	HUF	Forint	36	####	^(\d{4})$	hu-HU	719819	SK,SI,RO,UA,HR,AT,RS	
+ID	IDN	360	ID	Indonesia	Jakarta	1919440	242968342	AS	.id	IDR	Rupiah	62	#####	^(\d{5})$	id,en,nl,jv	1643084	PG,TL,MY	
+IE	IRL	372	EI	Ireland	Dublin	70280	4622917	EU	.ie	EUR	Euro	353	@@@ @@@@	^[A-Z]\d{2}$|^[A-Z]{3}[A-Z]{4}$	en-IE,ga-IE	2963597	GB	
+IL	ISR	376	IS	Israel	Jerusalem	20770	7353985	AS	.il	ILS	Shekel	972	#######	^(\d{7}|\d{5})$	he,ar-IL,en-IL,	294640	SY,JO,LB,EG,PS	
+IM	IMN	833	IM	Isle of Man	Douglas	572	75049	EU	.im	GBP	Pound	+44-1624	@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA	^((?:(?:[A-PR-UWYZ][A-HK-Y]\d[ABEHMNPRV-Y0-9]|[A-PR-UWYZ]\d[A-HJKPS-UW0-9])\s\d[ABD-HJLNP-UW-Z]{2})|GIR\s?0AA)$	en,gv	3042225		
+IN	IND	356	IN	India	New Delhi	3287590	1173108018	AS	.in	INR	Rupee	91	######	^(\d{6})$	en-IN,hi,bn,te,mr,ta,ur,gu,kn,ml,or,pa,as,bh,sat,ks,ne,sd,kok,doi,mni,sit,sa,fr,lus,inc	1269750	CN,NP,MM,BT,PK,BD	
+IO	IOT	086	IO	British Indian Ocean Territory	Diego Garcia	60	4000	AS	.io	USD	Dollar	246			en-IO	1282588		
+IQ	IRQ	368	IZ	Iraq	Baghdad	437072	29671605	AS	.iq	IQD	Dinar	964	#####	^(\d{5})$	ar-IQ,ku,hy	99237	SY,SA,IR,JO,TR,KW	
+IR	IRN	364	IR	Iran	Tehran	1648000	76923300	AS	.ir	IRR	Rial	98	##########	^(\d{10})$	fa-IR,ku	130758	TM,AF,IQ,AM,PK,AZ,TR	
+IS	ISL	352	IC	Iceland	Reykjavik	103000	308910	EU	.is	ISK	Krona	354	###	^(\d{3})$	is,en,de,da,sv,no	2629691		
+IT	ITA	380	IT	Italy	Rome	301230	60340328	EU	.it	EUR	Euro	39	#####	^(\d{5})$	it-IT,de-IT,fr-IT,sc,ca,co,sl	3175395	CH,VA,SI,SM,FR,AT	
+JE	JEY	832	JE	Jersey	Saint Helier	116	90812	EU	.je	GBP	Pound	+44-1534	@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA	^((?:(?:[A-PR-UWYZ][A-HK-Y]\d[ABEHMNPRV-Y0-9]|[A-PR-UWYZ]\d[A-HJKPS-UW0-9])\s\d[ABD-HJLNP-UW-Z]{2})|GIR\s?0AA)$	en,fr,nrf	3042142		
+JM	JAM	388	JM	Jamaica	Kingston	10991	2847232	NA	.jm	JMD	Dollar	+1-876			en-JM	3489940		
+JO	JOR	400	JO	Jordan	Amman	92300	6407085	AS	.jo	JOD	Dinar	962	#####	^(\d{5})$	ar-JO,en	248816	SY,SA,IQ,IL,PS	
+JP	JPN	392	JA	Japan	Tokyo	377835	127288000	AS	.jp	JPY	Yen	81	###-####	^\d{3}-\d{4}$	ja	1861060		
+KE	KEN	404	KE	Kenya	Nairobi	582650	40046566	AF	.ke	KES	Shilling	254	#####	^(\d{5})$	en-KE,sw-KE	192950	ET,TZ,SS,SO,UG	
+KG	KGZ	417	KG	Kyrgyzstan	Bishkek	198500	5776500	AS	.kg	KGS	Som	996	######	^(\d{6})$	ky,uz,ru	1527747	CN,TJ,UZ,KZ	
+KH	KHM	116	CB	Cambodia	Phnom Penh	181040	14453680	AS	.kh	KHR	Riels	855	#####	^(\d{5})$	km,fr,en	1831722	LA,TH,VN	
+KI	KIR	296	KR	Kiribati	Tarawa	811	92533	OC	.ki	AUD	Dollar	686			en-KI,gil	4030945		
+KM	COM	174	CN	Comoros	Moroni	2170	773407	AF	.km	KMF	Franc	269			ar,fr-KM	921929		
+KN	KNA	659	SC	Saint Kitts and Nevis	Basseterre	261	51134	NA	.kn	XCD	Dollar	+1-869			en-KN	3575174		
+KP	PRK	408	KN	North Korea	Pyongyang	120540	22912177	AS	.kp	KPW	Won	850	###-###	^(\d{6})$	ko-KP	1873107	CN,KR,RU	
+KR	KOR	410	KS	South Korea	Seoul	98480	48422644	AS	.kr	KRW	Won	82	#####	^(\d{5})$	ko-KR,en	1835841	KP	
+XK	XKX	0	KV	Kosovo	Pristina	10908	1800000	EU		EUR	Euro				sq,sr	831053	RS,AL,MK,ME	
+KW	KWT	414	KU	Kuwait	Kuwait City	17820	2789132	AS	.kw	KWD	Dinar	965	#####	^(\d{5})$	ar-KW,en	285570	SA,IQ	
+KY	CYM	136	CJ	Cayman Islands	George Town	262	44270	NA	.ky	KYD	Dollar	+1-345			en-KY	3580718		
+KZ	KAZ	398	KZ	Kazakhstan	Nur-Sultan	2717300	15340000	AS	.kz	KZT	Tenge	7	######	^(\d{6})$	kk,ru	1522867	TM,CN,KG,UZ,RU	
+LA	LAO	418	LA	Laos	Vientiane	236800	6368162	AS	.la	LAK	Kip	856	#####	^(\d{5})$	lo,fr,en	1655842	CN,MM,KH,TH,VN	
+LB	LBN	422	LE	Lebanon	Beirut	10400	4125247	AS	.lb	LBP	Pound	961	#### ####|####	^(\d{4}(\d{4})?)$	ar-LB,fr-LB,en,hy	272103	SY,IL	
+LC	LCA	662	ST	Saint Lucia	Castries	616	160922	NA	.lc	XCD	Dollar	+1-758			en-LC	3576468		
+LI	LIE	438	LS	Liechtenstein	Vaduz	160	35000	EU	.li	CHF	Franc	423	####	^(\d{4})$	de-LI	3042058	CH,AT	
+LK	LKA	144	CE	Sri Lanka	Colombo	65610	21513990	AS	.lk	LKR	Rupee	94	#####	^(\d{5})$	si,ta,en	1227603		
+LR	LBR	430	LI	Liberia	Monrovia	111370	3685076	AF	.lr	LRD	Dollar	231	####	^(\d{4})$	en-LR	2275384	SL,CI,GN	
+LS	LSO	426	LT	Lesotho	Maseru	30355	1919552	AF	.ls	LSL	Loti	266	###	^(\d{3})$	en-LS,st,zu,xh	932692	ZA	
+LT	LTU	440	LH	Lithuania	Vilnius	65200	2944459	EU	.lt	EUR	Euro	370	LT-#####	^(?:LT)*(\d{5})$	lt,ru,pl	597427	PL,BY,RU,LV	
+LU	LUX	442	LU	Luxembourg	Luxembourg	2586	497538	EU	.lu	EUR	Euro	352	L-####	^(?:L-)?\d{4}$	lb,de-LU,fr-LU	2960313	DE,BE,FR	
+LV	LVA	428	LG	Latvia	Riga	64589	2217969	EU	.lv	EUR	Euro	371	LV-####	^(?:LV)*(\d{4})$	lv,ru,lt	458258	LT,EE,BY,RU	
+LY	LBY	434	LY	Libya	Tripoli	1759540	6461454	AF	.ly	LYD	Dinar	218			ar-LY,it,en	2215636	TD,NE,DZ,SD,TN,EG	
+MA	MAR	504	MO	Morocco	Rabat	446550	33848242	AF	.ma	MAD	Dirham	212	#####	^(\d{5})$	ar-MA,ber,fr	2542007	DZ,EH,ES	
+MC	MCO	492	MN	Monaco	Monaco	1.95	32965	EU	.mc	EUR	Euro	377	#####	^(\d{5})$	fr-MC,en,it	2993457	FR	
+MD	MDA	498	MD	Moldova	Chisinau	33843	4324000	EU	.md	MDL	Leu	373	MD-####	^MD-\d{4}$	ro,ru,gag,tr	617790	RO,UA	
+ME	MNE	499	MJ	Montenegro	Podgorica	14026	666730	EU	.me	EUR	Euro	382	#####	^(\d{5})$	sr,hu,bs,sq,hr,rom	3194884	AL,HR,BA,RS,XK	
+MF	MAF	663	RN	Saint Martin	Marigot	53	35925	NA	.gp	EUR	Euro	590	#####	^(\d{5})$	fr	3578421	SX	
+MG	MDG	450	MA	Madagascar	Antananarivo	587040	21281844	AF	.mg	MGA	Ariary	261	###	^(\d{3})$	fr-MG,mg	1062947		
+MH	MHL	584	RM	Marshall Islands	Majuro	181.3	65859	OC	.mh	USD	Dollar	692	#####-####	^969\d{2}(-\d{4})$	mh,en-MH	2080185		
+MK	MKD	807	MK	North Macedonia	Skopje	25333	2062294	EU	.mk	MKD	Denar	389	####	^(\d{4})$	mk,sq,tr,rmm,sr	718075	AL,GR,BG,RS,XK	
+ML	MLI	466	ML	Mali	Bamako	1240000	13796354	AF	.ml	XOF	Franc	223			fr-ML,bm	2453866	SN,NE,DZ,CI,GN,MR,BF	
+MM	MMR	104	BM	Myanmar	Nay Pyi Taw	678500	53414374	AS	.mm	MMK	Kyat	95	#####	^(\d{5})$	my	1327865	CN,LA,TH,BD,IN	
+MN	MNG	496	MG	Mongolia	Ulaanbaatar	1565000	3086918	AS	.mn	MNT	Tugrik	976	######	^(\d{6})$	mn,ru	2029969	CN,RU	
+MO	MAC	446	MC	Macao	Macao	254	449198	AS	.mo	MOP	Pataca	853			zh,zh-MO,pt	1821275		
+MP	MNP	580	CQ	Northern Mariana Islands	Saipan	477	53883	OC	.mp	USD	Dollar	+1-670	#####	^9695\d{1}$	fil,tl,zh,ch-MP,en-MP	4041468		
+MQ	MTQ	474	MB	Martinique	Fort-de-France	1100	432900	NA	.mq	EUR	Euro	596	#####	^(\d{5})$	fr-MQ	3570311		
+MR	MRT	478	MR	Mauritania	Nouakchott	1030700	3205060	AF	.mr	MRO	Ouguiya	222			ar-MR,fuc,snk,fr,mey,wo	2378080	SN,DZ,EH,ML	
+MS	MSR	500	MH	Montserrat	Plymouth	102	9341	NA	.ms	XCD	Dollar	+1-664			en-MS	3578097		
+MT	MLT	470	MT	Malta	Valletta	316	403000	EU	.mt	EUR	Euro	356	@@@ ####	^[A-Z]{3}\s?\d{4}$	mt,en-MT	2562770		
+MU	MUS	480	MP	Mauritius	Port Louis	2040	1294104	AF	.mu	MUR	Rupee	230			en-MU,bho,fr	934292		
+MV	MDV	462	MV	Maldives	Male	300	395650	AS	.mv	MVR	Rufiyaa	960	#####	^(\d{5})$	dv,en	1282028		
+MW	MWI	454	MI	Malawi	Lilongwe	118480	15447500	AF	.mw	MWK	Kwacha	265			ny,yao,tum,swk	927384	TZ,MZ,ZM	
+MX	MEX	484	MX	Mexico	Mexico City	1972550	112468855	NA	.mx	MXN	Peso	52	#####	^(\d{5})$	es-MX	3996063	GT,US,BZ	
+MY	MYS	458	MY	Malaysia	Kuala Lumpur	329750	28274729	AS	.my	MYR	Ringgit	60	#####	^(\d{5})$	ms-MY,en,zh,ta,te,ml,pa,th	1733045	BN,TH,ID	
+MZ	MOZ	508	MZ	Mozambique	Maputo	801590	22061451	AF	.mz	MZN	Metical	258	####	^(\d{4})$	pt-MZ,vmw	1036973	ZW,TZ,SZ,ZA,ZM,MW	
+NA	NAM	516	WA	Namibia	Windhoek	825418	2128471	AF	.na	NAD	Dollar	264			en-NA,af,de,hz,naq	3355338	ZA,BW,ZM,AO	
+NC	NCL	540	NC	New Caledonia	Noumea	19060	216494	OC	.nc	XPF	Franc	687	#####	^(\d{5})$	fr-NC	2139685		
+NE	NER	562	NG	Niger	Niamey	1267000	15878271	AF	.ne	XOF	Franc	227	####	^(\d{4})$	fr-NE,ha,kr,dje	2440476	TD,BJ,DZ,LY,BF,NG,ML	
+NF	NFK	574	NF	Norfolk Island	Kingston	34.6	1828	OC	.nf	AUD	Dollar	672	####	^(\d{4})$	en-NF	2155115		
+NG	NGA	566	NI	Nigeria	Abuja	923768	154000000	AF	.ng	NGN	Naira	234	######	^(\d{6})$	en-NG,ha,yo,ig,ff	2328926	TD,NE,BJ,CM	
+NI	NIC	558	NU	Nicaragua	Managua	129494	5995928	NA	.ni	NIO	Cordoba	505	###-###-#	^(\d{7})$	es-NI,en	3617476	CR,HN	
+NL	NLD	528	NL	Netherlands	Amsterdam	41526	16645000	EU	.nl	EUR	Euro	31	#### @@	^(\d{4}[A-Z]{2})$	nl-NL,fy-NL	2750405	DE,BE	
+NO	NOR	578	NO	Norway	Oslo	324220	5009150	EU	.no	NOK	Krone	47	####	^(\d{4})$	no,nb,nn,se,fi	3144096	FI,RU,SE	
+NP	NPL	524	NP	Nepal	Kathmandu	140800	28951852	AS	.np	NPR	Rupee	977	#####	^(\d{5})$	ne,en	1282988	CN,IN	
+NR	NRU	520	NR	Nauru	Yaren	21	10065	OC	.nr	AUD	Dollar	674			na,en-NR	2110425		
+NU	NIU	570	NE	Niue	Alofi	260	2166	OC	.nu	NZD	Dollar	683			niu,en-NU	4036232		
+NZ	NZL	554	NZ	New Zealand	Wellington	268680	4252277	OC	.nz	NZD	Dollar	64	####	^(\d{4})$	en-NZ,mi	2186224		
+OM	OMN	512	MU	Oman	Muscat	212460	2967717	AS	.om	OMR	Rial	968	###	^(\d{3})$	ar-OM,en,bal,ur	286963	SA,YE,AE	
+PA	PAN	591	PM	Panama	Panama City	78200	3410676	NA	.pa	PAB	Balboa	507			es-PA,en	3703430	CR,CO	
+PE	PER	604	PE	Peru	Lima	1285220	29907003	SA	.pe	PEN	Sol	51			es-PE,qu,ay	3932488	EC,CL,BO,BR,CO	
+PF	PYF	258	FP	French Polynesia	Papeete	4167	270485	OC	.pf	XPF	Franc	689	#####	^((97|98)7\d{2})$	fr-PF,ty	4030656		
+PG	PNG	598	PP	Papua New Guinea	Port Moresby	462840	6064515	OC	.pg	PGK	Kina	675	###	^(\d{3})$	en-PG,ho,meu,tpi	2088628	ID	
+PH	PHL	608	RP	Philippines	Manila	300000	99900177	AS	.ph	PHP	Peso	63	####	^(\d{4})$	tl,en-PH,fil,ceb,tgl,ilo,hil,war,pam,bik,bcl,pag,mrw,tsg,mdh,cbk,krj,sgd,msb,akl,ibg,yka,mta,abx	1694008		
+PK	PAK	586	PK	Pakistan	Islamabad	803940	184404791	AS	.pk	PKR	Rupee	92	#####	^(\d{5})$	ur-PK,en-PK,pa,sd,ps,brh	1168579	CN,AF,IR,IN	
+PL	POL	616	PL	Poland	Warsaw	312685	38500000	EU	.pl	PLN	Zloty	48	##-###	^\d{2}-\d{3}$	pl	798544	DE,LT,SK,CZ,BY,UA,RU	
+PM	SPM	666	SB	Saint Pierre and Miquelon	Saint-Pierre	242	7012	NA	.pm	EUR	Euro	508	#####	^(97500)$	fr-PM	3424932		
+PN	PCN	612	PC	Pitcairn	Adamstown	47	46	OC	.pn	NZD	Dollar	870			en-PN	4030699		
+PR	PRI	630	RQ	Puerto Rico	San Juan	9104	3916632	NA	.pr	USD	Dollar	+1-787 and 1-939	#####-####	^00[679]\d{2}(?:-\d{4})?$	en-PR,es-PR	4566966		
+PS	PSE	275	WE	Palestinian Territory	East Jerusalem	5970	3800000	AS	.ps	ILS	Shekel	970			ar-PS	6254930	JO,IL,EG	
+PT	PRT	620	PO	Portugal	Lisbon	92391	10676000	EU	.pt	EUR	Euro	351	####-###	^\d{4}-\d{3}\s?[a-zA-Z]{0,25}$	pt-PT,mwl	2264397	ES	
+PW	PLW	585	PS	Palau	Melekeok	458	19907	OC	.pw	USD	Dollar	680	96940	^(96940)$	pau,sov,en-PW,tox,ja,fil,zh	1559582		
+PY	PRY	600	PA	Paraguay	Asuncion	406750	6375830	SA	.py	PYG	Guarani	595	####	^(\d{4})$	es-PY,gn	3437598	BO,BR,AR	
+QA	QAT	634	QA	Qatar	Doha	11437	840926	AS	.qa	QAR	Rial	974			ar-QA,es	289688	SA	
+RE	REU	638	RE	Reunion	Saint-Denis	2517	776948	AF	.re	EUR	Euro	262	#####	^((97|98)(4|7|8)\d{2})$	fr-RE	935317		
+RO	ROU	642	RO	Romania	Bucharest	237500	21959278	EU	.ro	RON	Leu	40	######	^(\d{6})$	ro,hu,rom	798549	MD,HU,UA,BG,RS	
+RS	SRB	688	RI	Serbia	Belgrade	88361	7344847	EU	.rs	RSD	Dinar	381	######	^(\d{6})$	sr,hu,bs,rom	6290252	AL,HU,MK,RO,HR,BA,BG,ME,XK	
+RU	RUS	643	RS	Russia	Moscow	17100000	140702000	EU	.ru	RUB	Ruble	7	######	^(\d{6})$	ru,tt,xal,cau,ady,kv,ce,tyv,cv,udm,tut,mns,bua,myv,mdf,chm,ba,inh,tut,kbd,krc,av,sah,nog	2017370	GE,CN,BY,UA,KZ,LV,PL,EE,LT,FI,MN,NO,AZ,KP	
+RW	RWA	646	RW	Rwanda	Kigali	26338	11055976	AF	.rw	RWF	Franc	250			rw,en-RW,fr-RW,sw	49518	TZ,CD,BI,UG	
+SA	SAU	682	SA	Saudi Arabia	Riyadh	1960582	25731776	AS	.sa	SAR	Rial	966	#####	^(\d{5})$	ar-SA	102358	QA,OM,IQ,YE,JO,AE,KW	
+SB	SLB	090	BP	Solomon Islands	Honiara	28450	559198	OC	.sb	SBD	Dollar	677			en-SB,tpi	2103350		
+SC	SYC	690	SE	Seychelles	Victoria	455	88340	AF	.sc	SCR	Rupee	248			en-SC,fr-SC	241170		
+SD	SDN	729	SU	Sudan	Khartoum	1861484	35000000	AF	.sd	SDG	Pound	249	#####	^(\d{5})$	ar-SD,en,fia	366755	SS,TD,EG,ET,ER,LY,CF	
+SS	SSD	728	OD	South Sudan	Juba	644329	8260490	AF		SSP	Pound	211			en	7909807	CD,CF,ET,KE,SD,UG	
+SE	SWE	752	SW	Sweden	Stockholm	449964	9828655	EU	.se	SEK	Krona	46	### ##	^(?:SE)?\d{3}\s\d{2}$	sv-SE,se,sma,fi-SE	2661886	NO,FI	
+SG	SGP	702	SN	Singapore	Singapore	692.7	4701069	AS	.sg	SGD	Dollar	65	######	^(\d{6})$	cmn,en-SG,ms-SG,ta-SG,zh-SG	1880251		
+SH	SHN	654	SH	Saint Helena	Jamestown	410	7460	AF	.sh	SHP	Pound	290	STHL 1ZZ	^(STHL1ZZ)$	en-SH	3370751		
+SI	SVN	705	SI	Slovenia	Ljubljana	20273	2007000	EU	.si	EUR	Euro	386	####	^(?:SI)*(\d{4})$	sl,sh	3190538	HU,IT,HR,AT	
+SJ	SJM	744	SV	Svalbard and Jan Mayen	Longyearbyen	62049	2550	EU	.sj	NOK	Krone	47	####	^(\d{4})$	no,ru	607072		
+SK	SVK	703	LO	Slovakia	Bratislava	48845	5455000	EU	.sk	EUR	Euro	421	### ##	^\d{3}\s?\d{2}$	sk,hu	3057568	PL,HU,CZ,UA,AT	
+SL	SLE	694	SL	Sierra Leone	Freetown	71740	5245695	AF	.sl	SLL	Leone	232			en-SL,men,tem	2403846	LR,GN	
+SM	SMR	674	SM	San Marino	San Marino	61.2	31477	EU	.sm	EUR	Euro	378	4789#	^(4789\d)$	it-SM	3168068	IT	
+SN	SEN	686	SG	Senegal	Dakar	196190	12323252	AF	.sn	XOF	Franc	221	#####	^(\d{5})$	fr-SN,wo,fuc,mnk	2245662	GN,MR,GW,GM,ML	
+SO	SOM	706	SO	Somalia	Mogadishu	637657	10112453	AF	.so	SOS	Shilling	252	@@  #####	^([A-Z]{2}\d{5})$	so-SO,ar-SO,it,en-SO	51537	ET,KE,DJ	
+SR	SUR	740	NS	Suriname	Paramaribo	163270	492829	SA	.sr	SRD	Dollar	597			nl-SR,en,srn,hns,jv	3382998	GY,BR,GF	
+ST	STP	678	TP	Sao Tome and Principe	Sao Tome	1001	197700	AF	.st	STD	Dobra	239			pt-ST	2410758		
+SV	SLV	222	ES	El Salvador	San Salvador	21040	6052064	NA	.sv	USD	Dollar	503	CP ####	^(?:CP)*(\d{4})$	es-SV	3585968	GT,HN	
+SX	SXM	534	NN	Sint Maarten	Philipsburg	21	37429	NA	.sx	ANG	Guilder	599			nl,en	7609695	MF	
+SY	SYR	760	SY	Syria	Damascus	185180	22198110	AS	.sy	SYP	Pound	963			ar-SY,ku,hy,arc,fr,en	163843	IQ,JO,IL,TR,LB	
+SZ	SWZ	748	WZ	Eswatini	Mbabane	17363	1354051	AF	.sz	SZL	Lilangeni	268	@###	^([A-Z]\d{3})$	en-SZ,ss-SZ	934841	ZA,MZ	
+TC	TCA	796	TK	Turks and Caicos Islands	Cockburn Town	430	20556	NA	.tc	USD	Dollar	+1-649	TKCA 1ZZ	^(TKCA 1ZZ)$	en-TC	3576916		
+TD	TCD	148	CD	Chad	N'Djamena	1284000	10543464	AF	.td	XAF	Franc	235			fr-TD,ar-TD,sre	2434508	NE,LY,CF,SD,CM,NG	
+TF	ATF	260	FS	French Southern Territories	Port-aux-Francais	7829	140	AN	.tf	EUR	Euro				fr	1546748		
+TG	TGO	768	TO	Togo	Lome	56785	6587239	AF	.tg	XOF	Franc	228			fr-TG,ee,hna,kbp,dag,ha	2363686	BJ,GH,BF	
+TH	THA	764	TH	Thailand	Bangkok	514000	67089500	AS	.th	THB	Baht	66	#####	^(\d{5})$	th,en	1605651	LA,MM,KH,MY	
+TJ	TJK	762	TI	Tajikistan	Dushanbe	143100	7487489	AS	.tj	TJS	Somoni	992	######	^(\d{6})$	tg,ru	1220409	CN,AF,KG,UZ	
+TK	TKL	772	TL	Tokelau		10	1466	OC	.tk	NZD	Dollar	690			tkl,en-TK	4031074		
+TL	TLS	626	TT	Timor Leste	Dili	15007	1154625	OC	.tl	USD	Dollar	670			tet,pt-TL,id,en	1966436	ID	
+TM	TKM	795	TX	Turkmenistan	Ashgabat	488100	4940916	AS	.tm	TMT	Manat	993	######	^(\d{6})$	tk,ru,uz	1218197	AF,IR,UZ,KZ	
+TN	TUN	788	TS	Tunisia	Tunis	163610	10589025	AF	.tn	TND	Dinar	216	####	^(\d{4})$	ar-TN,fr	2464461	DZ,LY	
+TO	TON	776	TN	Tonga	Nuku'alofa	748	122580	OC	.to	TOP	Pa'anga	676			to,en-TO	4032283		
+TR	TUR	792	TU	Turkey	Ankara	780580	77804122	AS	.tr	TRY	Lira	90	#####	^(\d{5})$	tr-TR,ku,diq,az,av	298795	SY,GE,IQ,IR,GR,AM,AZ,BG	
+TT	TTO	780	TD	Trinidad and Tobago	Port of Spain	5128	1328019	NA	.tt	TTD	Dollar	+1-868			en-TT,hns,fr,es,zh	3573591		
+TV	TUV	798	TV	Tuvalu	Funafuti	26	10472	OC	.tv	AUD	Dollar	688			tvl,en,sm,gil	2110297		
+TW	TWN	158	TW	Taiwan	Taipei	35980	22894384	AS	.tw	TWD	Dollar	886	#####	^(\d{5})$	zh-TW,zh,nan,hak	1668284		
+TZ	TZA	834	TZ	Tanzania	Dodoma	945087	41892895	AF	.tz	TZS	Shilling	255			sw-TZ,en,ar	149590	MZ,KE,CD,RW,ZM,BI,UG,MW	
+UA	UKR	804	UP	Ukraine	Kyiv	603700	45415596	EU	.ua	UAH	Hryvnia	380	#####	^(\d{5})$	uk,ru-UA,rom,pl,hu	690791	PL,MD,HU,SK,BY,RO,RU	
+UG	UGA	800	UG	Uganda	Kampala	236040	33398682	AF	.ug	UGX	Shilling	256			en-UG,lg,sw,ar	226074	TZ,KE,SS,CD,RW	
+UM	UMI	581		United States Minor Outlying Islands		0	0	OC	.um	USD	Dollar	1			en-UM	5854968		
+US	USA	840	US	United States	Washington	9629091	310232863	NA	.us	USD	Dollar	1	#####-####	^\d{5}(-\d{4})?$	en-US,es-US,haw,fr	6252001	CA,MX,CU	
+UY	URY	858	UY	Uruguay	Montevideo	176220	3477000	SA	.uy	UYU	Peso	598	#####	^(\d{5})$	es-UY	3439705	BR,AR	
+UZ	UZB	860	UZ	Uzbekistan	Tashkent	447400	27865738	AS	.uz	UZS	Som	998	######	^(\d{6})$	uz,ru,tg	1512440	TM,AF,KG,TJ,KZ	
+VA	VAT	336	VT	Vatican	Vatican City	0.44	921	EU	.va	EUR	Euro	379	#####	^(\d{5})$	la,it,fr	3164670	IT	
+VC	VCT	670	VC	Saint Vincent and the Grenadines	Kingstown	389	104217	NA	.vc	XCD	Dollar	+1-784			en-VC,fr	3577815		
+VE	VEN	862	VE	Venezuela	Caracas	912050	27223228	SA	.ve	VES	Bolivar Soberano	58	####	^(\d{4})$	es-VE	3625428	GY,BR,CO	
+VG	VGB	092	VI	British Virgin Islands	Road Town	153	21730	NA	.vg	USD	Dollar	+1-284			en-VG	3577718		
+VI	VIR	850	VQ	U.S. Virgin Islands	Charlotte Amalie	352	108708	NA	.vi	USD	Dollar	+1-340	#####-####	^008\d{2}(?:-\d{4})?$	en-VI	4796775		
+VN	VNM	704	VM	Vietnam	Hanoi	329560	89571130	AS	.vn	VND	Dong	84	######	^(\d{6})$	vi,en,fr,zh,km	1562822	CN,LA,KH	
+VU	VUT	548	NH	Vanuatu	Port Vila	12200	221552	OC	.vu	VUV	Vatu	678			bi,en-VU,fr-VU	2134431		
+WF	WLF	876	WF	Wallis and Futuna	Mata Utu	274	16025	OC	.wf	XPF	Franc	681	#####	^(986\d{2})$	wls,fud,fr-WF	4034749		
+WS	WSM	882	WS	Samoa	Apia	2944	192001	OC	.ws	WST	Tala	685			sm,en-WS	4034894		
+YE	YEM	887	YM	Yemen	Sanaa	527970	23495361	AS	.ye	YER	Rial	967			ar-YE	69543	SA,OM	
+YT	MYT	175	MF	Mayotte	Mamoudzou	374	159042	AF	.yt	EUR	Euro	262	#####	^(\d{5})$	fr-YT	1024031		
+ZA	ZAF	710	SF	South Africa	Pretoria	1219912	49000000	AF	.za	ZAR	Rand	27	####	^(\d{4})$	zu,xh,af,nso,en-ZA,tn,st,ts,ss,ve,nr	953987	ZW,SZ,MZ,BW,NA,LS	
+ZM	ZMB	894	ZA	Zambia	Lusaka	752614	13460305	AF	.zm	ZMW	Kwacha	260	#####	^(\d{5})$	en-ZM,bem,loz,lun,lue,ny,toi	895949	ZW,TZ,MZ,CD,NA,MW,AO	
+ZW	ZWE	716	ZI	Zimbabwe	Harare	390580	13061000	AF	.zw	ZWL	Dollar	263			en-ZW,sn,nr,nd	878675	ZA,MZ,BW,ZM	
+CS	SCG	891	YI	Serbia and Montenegro	Belgrade	102350	10829175	EU	.cs	RSD	Dinar	381	#####	^(\d{5})$	cu,hu,sq,sr	8505033	AL,HU,MK,RO,HR,BA,BG	
+AN	ANT	530	NT	Netherlands Antilles	Willemstad	960	300000	NA	.an	ANG	Guilder	599			nl-AN,en,es	8505032	GP	
diff -Nru geoip-1.6.12/debian/src/geoip-asn-csv-to-dat.cpp geoip-1.6.12/debian/src/geoip-asn-csv-to-dat.cpp
--- geoip-1.6.12/debian/src/geoip-asn-csv-to-dat.cpp	1970-01-01 02:00:00.000000000 +0200
+++ geoip-1.6.12/debian/src/geoip-asn-csv-to-dat.cpp	2026-07-03 21:06:14.000000000 +0300
@@ -0,0 +1,898 @@
+/* geoip-csv-to-dat - convert a country database from CSV to GeoIP binary format
+ *
+ * Copyright (c) 2009 Kalle Olavi Niemitalo.
+ * Copyright (c) 2011 Patrick Matthäi
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define _GNU_SOURCE 1
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cerrno>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <error.h>
+#include <fstream>
+#include <getopt.h>
+#include <iostream>
+#include <queue>
+#include <set>
+#include <stack>
+#include <string>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sysexits.h>
+#include <vector>
+#include <GeoIP.h>
+#include <map>
+
+// Format of GeoIP Country database files
+// ======================================
+//
+// 1. Binary trie mapping IP addresses to countries.
+// 2. Optional unused data.
+// 3. Optional database-info block.
+// 4. Optional structure-info block.
+//
+// Binary trie
+// -----------
+//
+// The trie treats IP addresses as bit sequences and maps them to
+// numbers.  In the country database, each such number is 0xFFFF00 +
+// the the country ID that GeoIP_id_by_ipnum() would return.  The
+// meanings of country IDs are hardcoded in libGeoIP and cannot be
+// overridden by the database.
+//
+// The root node of the trie is at the beginning of the file, and the
+// other nodes then follow it.	Each node has the same size and
+// consists of two little-endian pointers that correspond to the two
+// possible values of a bit.  In the country database, the pointers are
+// 24-bit, and each node is thus 6 bytes long.
+//
+// Each pointer is one of:
+// - The number that the whole lookup should return, i.e. 0xFFFF00 + id
+//   in the country database.
+// - The number of the node that should be examined next, counting from
+//   0 at the beginning of the file.  Pointing back to nodes with
+//   smaller numbers is allowed, but loops are not allowed.
+//
+// Optional unused data
+// --------------------
+//
+// The file format seems to permit extra data between the binary trie
+// and the optional blocks.
+//
+// Optional database-info block
+// ----------------------------
+//
+// Near the end of the file, there may be a three-byte tag (0x00 0x00
+// 0x00) followed by at most DATABASE_INFO_MAX_SIZE - 1 = 99 bytes of
+// text that describes the database.  GeoIP_database_info() returns
+// this text and appends a terminating '\0'.
+//
+// The GeoLite Country IPv4 database downloadable from MaxMind
+// includes this database-info block.
+//
+// Optional structure-info block
+// -----------------------------
+//
+// At the very end of the file, there may be a three-byte tag (0xFF
+// 0xFF 0xFF) followed by at most STRUCTURE_INFO_MAX_SIZE - 1 = 19
+// bytes.  The first byte is the database type,
+// e.g. GEOIP_COUNTRY_EDITION = 1 or GEOIP_COUNTRY_EDITION_V6 = 12,
+// possibly with 105 added to it.  Type-specific information then
+// follows.  There is no type-specific information for the country
+// editions.
+//
+// The GeoLite Country IPv4 database downloadable from MaxMind does
+// not include this structure-info block.
+
+namespace {
+	class binary_trie
+	{
+	public:
+		typedef uint_fast32_t edge_type;
+
+		explicit binary_trie(edge_type leaf);
+		void set_range(
+			const uint8_t range_min[],
+			const uint8_t range_max[],
+			std::size_t bit_count,
+			edge_type leaf);
+		void reorder_depth_first();
+		void reorder_in_blocks(std::size_t bytes_per_block);
+		void write_binary(std::ostream &dat_stream) const;
+		void write_segment(std::ostream &dat_stream) const;
+		void update_records();
+
+	private:
+		struct node
+		{
+			edge_type edges[2];
+		};
+		std::vector<node> nodes;
+
+		// This could be std::vector<bool> but that seems slower.
+		typedef std::vector<uint8_t> bits_vector;
+
+		void set_range_in_node(
+			const bits_vector *min_bits,
+			const bits_vector *max_bits,
+			std::size_t bit_pos,
+			edge_type edit_node,
+			edge_type leaf);
+		void set_range_in_edge(
+			const bits_vector *min_bits,
+			const bits_vector *max_bits,
+			std::size_t bit_pos,
+			edge_type edit_node,
+			bool bit,
+			edge_type leaf);
+		void reorder(
+			const std::vector<edge_type> &old_to_new,
+			const std::vector<edge_type> &new_to_old);
+	};
+}
+
+/** Construct a binary trie and its root node.
+ *
+ * \param leaf
+ * Both edges of the root node will initially point to this leaf.
+ * The caller should provide a value that means nothing was found.  */
+binary_trie::binary_trie(edge_type leaf)
+{
+	const node node = {{ leaf, leaf }};
+	nodes.push_back(node);
+}
+
+/** Edit the trie so it maps a range of bit sequences to the same
+ * leaf.
+ *
+ * \param range_min
+ * The first bit sequence in the range.  Eight bits are packed in each
+ * byte.  The most significant bit of the whole sequence is in the
+ * most significant bit of the first byte.
+ *
+ * \param range_max
+ * The last bit sequence in the range.
+ *
+ * \param bit_count
+ * The number of bits in both sequences.
+ *
+ * \param leaf
+ * The leaf to which all the bit sequences in the range should be
+ * mapped.  */
+void
+binary_trie::set_range(
+	const uint8_t range_min[],
+	const uint8_t range_max[],
+	std::size_t bit_count,
+	edge_type leaf)
+{
+	bits_vector min_bits(bit_count);
+	bits_vector max_bits(bit_count);
+	for (std::size_t i = 0; i < bit_count; ++i) {
+		std::size_t byte_pos = i / 8;
+		uint8_t mask = 1 << ((~i) % 8);
+		min_bits[i] = ((range_min[byte_pos] & mask) != 0);
+		max_bits[i] = ((range_max[byte_pos] & mask) != 0);
+	}
+	set_range_in_node(&min_bits, &max_bits, 0, 0, leaf);
+}
+
+/** Edit a node in the trie so it maps a range of bit sequences to the
+ * same leaf.
+ *
+ * \param min_bits
+ * The first bit sequence in the range, or NULL if unbounded.
+ *
+ * \param max_bits
+ * The last bit sequence in the range, or NULL if unbounded.
+ *
+ * \param bit_pos
+ * Which bit in the sequences corresponds to \a edit_node.
+ *
+ * \param edit_node
+ * The node to be modified.
+ *
+ * \param leaf
+ * The leaf to which all the bit sequences in the range should be
+ * mapped.  */
+void
+binary_trie::set_range_in_node(
+	const bits_vector *min_bits,
+	const bits_vector *max_bits,
+	std::size_t bit_pos,
+	edge_type edit_node,
+	edge_type leaf)
+{
+	if (!min_bits || (*min_bits)[bit_pos] == false) {
+		set_range_in_edge(min_bits,
+				  (max_bits && (*max_bits)[bit_pos] == false)
+				  ? max_bits : NULL,
+				  bit_pos + 1, edit_node, false, leaf);
+	}
+	if (!max_bits || (*max_bits)[bit_pos] == true) {
+		set_range_in_edge((min_bits && (*min_bits)[bit_pos] == true)
+				  ? min_bits : NULL,
+				  max_bits,
+				  bit_pos + 1, edit_node, true, leaf);
+	}
+}
+
+/** Edit an edge in the trie so it maps a range of bit sequences to
+ * the same leaf.
+ *
+ * \param min_bits
+ * The first bit sequence in the range, or NULL if unbounded.
+ *
+ * \param max_bits
+ * The last bit sequence in the range, or NULL if unbounded.
+ *
+ * \param bit_pos
+ * Which bit in the sequences corresponds to \a bit.
+ *
+ * \param edit_node
+ * The node in which the edge to be modified is located.
+ *
+ * \param bit
+ * Which edge of \a edit_node should be modified.
+ *
+ * \param leaf
+ * The leaf to which all the bit sequences in the range should be
+ * mapped.  */
+void
+binary_trie::set_range_in_edge(
+	const bits_vector *min_bits,
+	const bits_vector *max_bits,
+	std::size_t bit_pos,
+	edge_type edit_node,
+	bool bit,
+	edge_type leaf)
+{
+	// Check if the range fills this edge entirely.
+	bool entire = true;
+	if (min_bits
+	    && std::find(min_bits->begin() + bit_pos, min_bits->end(),
+			 true) != min_bits->end())
+		entire = false;
+	if (max_bits
+	    && std::find(max_bits->begin() + bit_pos, max_bits->end(),
+			 false) != max_bits->end())
+		entire = false;
+
+	if (entire) {
+		nodes[edit_node].edges[bit] = leaf;
+	} else {
+		edge_type next = nodes[edit_node].edges[bit];
+		if (next >= nodes.size()) {
+			const node new_node = {{ next, next }};
+			next = nodes.size();
+			nodes.push_back(new_node);
+			nodes[edit_node].edges[bit] = next;
+		}
+
+		set_range_in_node(min_bits, max_bits, bit_pos,
+				  next, leaf);
+	}
+}
+
+/** Renumber the nodes in depth-first order.  */
+void
+binary_trie::reorder_depth_first()
+{
+	std::vector<edge_type> old_to_new, new_to_old;
+	std::stack<edge_type> depth_first;
+	old_to_new.resize(nodes.size(), -1);
+	new_to_old.reserve(nodes.size());
+	depth_first.push(0);
+	while (!depth_first.empty()) {
+		const edge_type edge = depth_first.top();
+		depth_first.pop();
+		if (edge < nodes.size()) {
+			old_to_new[edge] = new_to_old.size();
+			new_to_old.push_back(edge);
+			depth_first.push(nodes[edge].edges[1]);
+			depth_first.push(nodes[edge].edges[0]);
+		}
+	}
+	reorder(old_to_new, new_to_old);
+}
+
+/** Renumber the nodes to make lookups use CPU and disk caches more
+ * effectively.
+ *
+ * First group the nodes into blocks so that each block contains the
+ * root of a subtrie and as many levels of its descendants as will
+ * fit.  This way, after the root is paged in, the next few lookup
+ * steps need not page in anything else.  Then, sort the nodes of each
+ * block in depth-first order.  That should give each lookup almost
+ * 1/2 chance to find the next node immediately adjacent.
+ *
+ * With a block size of 1024 bytes, this renumbering reduces the time
+ * required for random lookups by about 1.1%, compared to a plain
+ * depth-first order.  However, it's still 2.3% slower than the
+ * database optimized by MaxMind.  */
+void
+binary_trie::reorder_in_blocks(
+	std::size_t bytes_per_block)
+{
+	const edge_type none = -1;
+	std::vector<edge_type> old_to_new, new_to_old;
+	ssize_t bytes_left = bytes_per_block;
+	old_to_new.resize(nodes.size(), none);
+	new_to_old.reserve(nodes.size());
+	for (edge_type subtrie = 0; subtrie < nodes.size(); ++subtrie) {
+		// If subtrie has already been added to the output,
+		// ignore it.
+		if (old_to_new[subtrie] != none)
+			continue;
+
+		// Walk breadth-first from subtrie until we have a
+		// block full of nodes or the subtrie runs out.  Don't
+		// add these nodes immediately to the output, however.
+		// Instead just list them in nodes_in_block.
+		std::set<edge_type> nodes_in_block;
+		std::queue<edge_type> breadth_first;
+		breadth_first.push(subtrie);
+		if (bytes_left <= 0)
+			bytes_left += bytes_per_block;
+		while (bytes_left > 0 && !breadth_first.empty()) {
+			edge_type edge = breadth_first.front();
+			breadth_first.pop();
+			if (edge >= nodes.size())
+				continue;
+
+			// Let the last node of the block straddle the
+			// block boundary.  That's better than making
+			// the hotter first node do so.
+			bytes_left -= 6;
+			nodes_in_block.insert(edge);
+
+			breadth_first.push(nodes[edge].edges[0]);
+			breadth_first.push(nodes[edge].edges[1]);
+		}
+
+		// Add the nodes from nodes_in_block to the output in
+		// depth-first order.  This assumes they are all
+		// reachable from subtrie.
+		std::stack<edge_type> depth_first;
+		depth_first.push(subtrie);
+		while (!depth_first.empty()) {
+			edge_type edge = depth_first.top();
+			depth_first.pop();
+			if (nodes_in_block.find(edge)
+			    == nodes_in_block.end())
+				continue;
+
+			old_to_new[edge] = new_to_old.size();
+			new_to_old.push_back(edge);
+
+			depth_first.push(nodes[edge].edges[1]);
+			depth_first.push(nodes[edge].edges[0]);
+		}
+	}
+	reorder(old_to_new, new_to_old);
+}
+
+void
+binary_trie::reorder(
+	const std::vector<edge_type> &old_to_new,
+	const std::vector<edge_type> &new_to_old)
+{
+	std::vector<node> new_nodes;
+	new_nodes.reserve(new_to_old.size());
+	for (std::vector<edge_type>::const_iterator
+		     it = new_to_old.begin();
+	     it != new_to_old.end(); ++it) {
+		node new_node;
+		for (int bit = 0; bit <= 1; ++bit) {
+			edge_type old_edge = nodes[*it].edges[bit];
+			if (old_edge < nodes.size())
+				new_node.edges[bit] = old_to_new[old_edge];
+			else
+				new_node.edges[bit] = old_edge;
+		}
+		new_nodes.push_back(new_node);
+	}
+	swap(new_nodes, nodes);
+}
+
+/** Add the size of the trie (number of nodes) to data records*/
+void
+binary_trie::update_records()
+{
+	for (std::vector<node>::iterator it = nodes.begin();
+	     it != nodes.end(); ++it) {
+	// previously, we commandeered the MSB in order to indicate which records
+	// were data records, rather than pointers to other nodes in the trie.
+	// Here, we remove that bit, and increment the record by the number of nodes,
+	// because this is how libGeoIP determines whether a node points to an entry
+	// inside the data section or another node.
+		for (int i = 0;i<2;++i) {
+			if (it->edges[i] == 0x80000000)
+				it->edges[i] = nodes.size();
+			else if (it->edges[i] & 0x80000000)
+			{
+				it->edges[i] = (it->edges[i] & 0x7FFFFFFF) + nodes.size();
+			}
+		}
+	}
+}
+
+/** Write the 3 byte segment offset. **/
+void
+binary_trie::write_segment(std::ostream &dat_stream) const
+{
+	int len = nodes.size();
+	dat_stream << (char) (0xFF & len);
+	dat_stream << (char) (0xFF & (len >> 8));
+	dat_stream << (char) (0xFF & (len >> 16));
+}
+
+/** Write the trie to a stream in GeoIP binary format.	*/
+void
+binary_trie::write_binary(std::ostream &dat_stream) const
+{
+	for (std::vector<node>::const_iterator it = nodes.begin();
+	     it != nodes.end(); ++it) {
+		union {
+			uint8_t bytes[6];
+			char chars[6];
+		} binary = {{
+			(it->edges[0]	   ) & 0xFF,
+			(it->edges[0] >>  8) & 0xFF,
+			(it->edges[0] >> 16) & 0xFF,
+			(it->edges[1]	   ) & 0xFF,
+			(it->edges[1] >>  8) & 0xFF,
+			(it->edges[1] >> 16) & 0xFF
+		}};
+		dat_stream.write(binary.chars, 6);
+		if (dat_stream.bad())
+			return;
+	}
+}
+
+
+namespace {
+	void
+	v4_csv_line_to_vector(
+		const std::string line,
+		std::vector<std::string> &fields)
+	{
+		std::string buf(line);
+		std::string delim = ",";
+		std::size_t fs;
+		for(int i = 0; i<2;++i) {
+			fs = buf.find(delim);
+			fields.push_back(buf.substr(0,fs));
+			buf.erase(0,fs + delim.length());
+		}
+		if (buf[0] == '"' || buf[0] == '\'')
+		fields.push_back(buf.substr(1, buf.length() - 2));
+		else
+			fields.push_back(buf);
+	}
+
+	void
+	v6_csv_line_to_vector(
+		const std::string line,
+		std::vector<std::string> & fields)
+	{
+		std::string buf(line);
+		std::string delim = ",";
+		std::size_t fs;
+		for(int i = 0; i<3;++i) {
+			fs = buf.rfind(delim);
+			fields.push_back(buf.substr(fs+delim.length(), buf.length()));
+			buf.erase(fs,buf.length());
+		}
+		if (buf[0] == '"' || buf[0] == '\'')
+			fields.push_back(buf.substr(1, buf.length() - 2));
+		else
+		fields.push_back(buf.substr(0, buf.length()));
+	}
+
+	/** Load ranges of IP addresses from a CSV-formatted stream to
+	 * a trie.
+	 *
+	 * \param trie
+	 * Load the ranges to this trie, overwriting original values.
+	 *
+	 * \param csv_file_name
+	 * The name of the file that \a csv_stream is reading.
+	 * This string is used only for error messages.
+	 *
+	 * \param csv_stream
+	 * Load the ranges from this stream.
+	 *
+	 * \param address_family
+	 * The type of IP addresses in the CSV data: either AF_INET * for IPv4 or AF_INET6 for IPv6.  */
+	void
+	csv_stream_to_trie_db(
+		binary_trie &trie,
+		std::string &database_segment,
+		const char *csv_file_name,
+		std::istream &csv_stream,
+		int address_family)
+	{
+		enum {
+			V4_CSV_FIELD_MIN_DECIMAL,
+			V4_CSV_FIELD_MAX_DECIMAL,
+			V4_CSV_FIELD_ASNUM_DESCRIPTION,
+			V4_CSV_FIELDS
+		};
+
+		enum {
+			V6_CSV_FIELD_NET_BITS,
+			V6_CSV_FIELD_MAX_TEXT,
+			V6_CSV_FIELD_MIN_TEXT,
+			V6_CSV_FIELD_ASNUM_DESCRIPTION,
+			V6_CSV_FIELDS
+		};
+
+		std::string csv_line;
+		std::map<std::string,int> segment_offset;
+		std::vector<std::string> csv_fields;
+		// create a map to track which as descriptions are added already
+		int csv_line_number = 0;
+		database_segment += '\0'; // padding so that record 0 is not at the start of the db.
+		while (getline(csv_stream, csv_line)) {
+			++csv_line_number;
+			std::string as;
+			switch (address_family) {
+			case AF_INET:
+				v4_csv_line_to_vector(csv_line, csv_fields);
+				if (csv_fields.size() != V4_CSV_FIELDS) {
+					error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+						      "Wrong number of fields");
+				}
+				as = csv_fields[V4_CSV_FIELD_ASNUM_DESCRIPTION];
+				if (segment_offset.find(as) == segment_offset.end()) { // no key found
+					segment_offset[as] = database_segment.length(); // start of record
+					database_segment += csv_fields[V4_CSV_FIELD_ASNUM_DESCRIPTION] + '\x00';
+				}
+				break;
+			case AF_INET6:
+				v6_csv_line_to_vector(csv_line, csv_fields);
+				if (csv_fields.size() != V6_CSV_FIELDS) {
+					error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+						      "Wrong number of fields");
+				}
+				as = csv_fields[V6_CSV_FIELD_ASNUM_DESCRIPTION];
+				if (segment_offset.find(as) == segment_offset.end()) { // no key found
+					segment_offset[as] = database_segment.length(); // start of record
+					database_segment += csv_fields[V6_CSV_FIELD_ASNUM_DESCRIPTION] + '\x00';
+				}
+				break;
+			default:
+				abort();
+			}
+
+			// use the MSB to indicate that this is a data record. Later, the field
+			// is set to the segment offset + the nubmer of nodes in the trie.
+			const binary_trie::edge_type leaf = 0x80000000 | segment_offset[as];
+			union {
+				struct in_addr inet;
+				uint8_t inetbytes[4];
+				struct in6_addr inet6;
+			} minaddr, maxaddr;
+
+			switch (address_family) {
+			case AF_INET:
+				inet_aton(csv_fields[V4_CSV_FIELD_MIN_DECIMAL].c_str(), &(minaddr.inet));
+				inet_aton(csv_fields[V4_CSV_FIELD_MAX_DECIMAL].c_str(), &(maxaddr.inet));
+				trie.set_range(minaddr.inetbytes, maxaddr.inetbytes, 32, leaf);
+				break;
+
+			case AF_INET6:
+				if (inet_pton(address_family, csv_fields[V6_CSV_FIELD_MIN_TEXT].c_str(), &minaddr) <= 0) {
+					error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+						      "Cannot parse minimum address: %s",
+						      csv_fields[V6_CSV_FIELD_MIN_TEXT].c_str());
+				}
+				if (inet_pton(address_family, csv_fields[V6_CSV_FIELD_MAX_TEXT].c_str(), &maxaddr) <= 0) {
+					error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+						      "Cannot parse maximum address: %s",
+						      csv_fields[V6_CSV_FIELD_MAX_TEXT].c_str());
+				}
+				trie.set_range(minaddr.inet6.s6_addr, maxaddr.inet6.s6_addr,
+					       128, leaf);
+				break;
+
+			default:
+				abort();
+			}
+			csv_fields.clear();
+		}
+		if (csv_stream.bad()) {
+			error(EX_IOERR, errno, "%s", csv_file_name);
+		}
+	}
+
+	/** Load ranges of IP addresses from a CSV-formatted file or
+	 * standard input to a trie.
+	 *
+	 * \param trie
+	 * Load the ranges to this trie, overwriting original values.
+	 *
+	 * \param csv_file_name
+	 * The name of the CSV file that should be read, or "-" for
+	 * standard input.
+	 *
+	 * \param address_family
+	 * The type of IP addresses in the CSV data: either AF_INET
+	 * for IPv4 or AF_INET6 for IPv6.  */
+	void
+	csv_file_to_trie_db(
+		binary_trie &trie,
+		std::string &database_segment,
+		const char *csv_file_name,
+		int address_family)
+	{
+		if (std::strcmp(csv_file_name, "-") == 0) {
+			csv_stream_to_trie_db(trie, database_segment, csv_file_name, std::cin, address_family);
+		} else {
+			std::ifstream csv_stream(csv_file_name, std::ios::in);
+			if (!csv_stream) {
+				error(EX_NOINPUT, errno, "%s", csv_file_name);
+			}
+			csv_stream_to_trie_db(trie, database_segment, csv_file_name, csv_stream, address_family);
+		}
+	}
+
+	/** Write a GeoIP binary database to a stream.
+	 *
+	 * \param trie
+	 * Mapping from IP addresses to country codes or other values.
+	 *
+	 * \param dat_file_name
+	 * The name of the file that \a dat_stream is writing.
+	 * This string is used only for error messages.
+	 *
+	 * \param dat_stream
+	 * Write the database to this stream.
+	 *
+	 * \param database_info
+	 * Copyright or other information about the database, or NULL.
+	 * GeoIP_database_info() will return this string.
+	 *
+	 * \param address_family
+	 * The type of IP addresses in the database: either AF_INET
+	 * for IPv4 or AF_INET6 for IPv6.  */
+	void
+	write_dat_stream(
+		const binary_trie &trie,
+		const char *dat_file_name,
+		std::ostream &dat_stream,
+		const char *database_info,
+		std::string database_segment,
+		int address_family)
+	{
+
+		trie.write_binary(dat_stream);
+		if (dat_stream.bad()) {
+			error(EX_IOERR, errno, "%s", dat_file_name);
+		}
+
+		// or open the file and read the length
+		if (database_segment.length() > 0) {
+			dat_stream << database_segment;
+			if (dat_stream.bad()) {
+				error(EX_IOERR, errno, "%s", dat_file_name);
+			}
+
+		}
+
+		// write the metadata section
+		if (database_info) {
+			const char tag[3] = { 0, 0, 0 };
+			dat_stream.write(tag, 3);
+			dat_stream.write(database_info, std::strlen(database_info));
+			if (dat_stream.bad()) {
+				error(EX_IOERR, errno, "%s", dat_file_name);
+			}
+		}
+
+		switch (address_family) {
+		case AF_INET: {
+			const unsigned char structure_info[4] = { 0xFF, 0xFF, 0xFF, 9 };
+			dat_stream.write((const char *)structure_info, 4);
+			break;
+		}
+		case AF_INET6: {
+			const unsigned char structure_info[4] = { 0xFF, 0xFF, 0xFF, 21 };
+			dat_stream.write((const char *)structure_info, 4);
+			break;
+		}
+		default:
+			abort();
+		}
+
+		trie.write_segment(dat_stream);
+		if (dat_stream.bad()) {
+			error(EX_IOERR, errno, "%s", dat_file_name);
+		}
+	}
+
+	/** Write a GeoIP binary database to a file or standard output.
+	 *
+	 * \param trie
+	 * Mapping from IP addresses to country codes or other values.
+	 *
+	 * \param csv_file_name
+	 * The name of the file that should be written, or "-" for
+	 * standard output.
+	 *
+	 * \param database_info
+	 * Copyright or other information about the database, or NULL.
+	 * GeoIP_database_info() will return this string.
+	 *
+	 * \param address_family
+	 * The type of IP addresses in the database: either AF_INET
+	 * for IPv4 or AF_INET6 for IPv6.  */
+	void
+	write_dat_file(
+		const binary_trie &trie,
+		const char *dat_file_name,
+		const char *database_info,
+		std::string  database_segment,
+		int address_family)
+	{
+		if (std::strcmp(dat_file_name, "-") == 0) {
+			write_dat_stream(trie, dat_file_name, std::cout,
+					 database_info, database_segment, address_family);
+		} else {
+			std::ofstream dat_stream(
+				dat_file_name,
+				std::ios::out | std::ios::binary);
+			if (!dat_stream) {
+				error(EX_CANTCREAT, errno, "%s", dat_file_name);
+			}
+			write_dat_stream(trie, dat_file_name, dat_stream,
+					 database_info, database_segment, address_family);
+		}
+	}
+
+	struct cmdline {
+		const char *csv_file_name;
+		const char *dat_file_name;
+		int address_family;
+		const char *database_info;
+		bool verbose;
+
+		cmdline(int argc, char **argv);
+	};
+}
+
+cmdline::cmdline(int argc, char **argv):
+	csv_file_name("-"),
+	dat_file_name("-"),
+	address_family(AF_INET),
+	database_info(NULL),
+	verbose(false)
+{
+	enum {
+		OPT_HELP = -2
+	};
+
+	static const struct option long_options[] = {
+		{ "info", required_argument, NULL, 'i' },
+		{ "output", required_argument, NULL, 'o' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "help", no_argument, NULL, OPT_HELP },
+		{ NULL, 0, NULL, 0 }
+	};
+	static const char *const usage = "\
+Usage: %s [OPTION] [CSV-FILE]...\n\
+Convert a country database from CSV to GeoIP binary format.\n\
+\n\
+  -i, --info=TEXT       add copyright or other info TEXT to output\n\
+  -o, --output=FILE     write the binary data to FILE, not stdout\n\
+  -v, --verbose         show what is going on\n\
+      --help            display this help and exit\n";
+
+	for (;;) {
+		int optret = getopt_long(argc, argv, "46i:o:v", long_options, NULL);
+
+		if (optret == -1)
+			break;
+		switch (optret) {
+		case '4':
+			address_family = AF_INET;
+			break;
+		case '6':
+			address_family = AF_INET6;
+			break;
+		case 'i':
+			database_info = optarg;
+			if (std::strlen(database_info) > 99) {
+				error(EX_USAGE, 0,
+				      "Database info must not be longer than 99 bytes");
+			}
+			break;
+		case 'o':
+			dat_file_name = optarg;
+			break;
+		case 'v':
+			verbose = true;
+			break;
+		case OPT_HELP:
+			std::printf(usage, program_invocation_name);
+			std::exit(EX_OK);
+		case '?':
+			std::fprintf(stderr,
+				     "Try `%s --help' for more information.\n",
+				     program_invocation_name);
+			std::exit(EX_USAGE);
+		default:
+			std::abort();
+		}
+	}
+
+	if (optind < argc)
+		csv_file_name = argv[optind++];
+
+	if (optind < argc) {
+		error(EX_USAGE, 0,
+		      "Only one non-option argument is allowed");
+	}
+}
+
+int
+main(int argc, char **argv)
+{
+	cmdline cmdline(argc, argv);
+
+	std::ostream *verbose_stream;
+	if (!cmdline.verbose)
+		verbose_stream = NULL;
+	else if (strcmp(cmdline.dat_file_name, "-") == 0)
+		verbose_stream = &std::cerr;
+	else
+		verbose_stream = &std::cout;
+
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Reading CSV and building the trie"
+				<< std::endl;
+	}
+	/* Initialize the trie with a value that will point to the start of the
+	 * data section, e.g. an empty record. See binary_trie::update_records()
+	 */
+	binary_trie trie(0x80000000);
+	std::string database_segment;
+	csv_file_to_trie_db(trie, database_segment, cmdline.csv_file_name, cmdline.address_family);
+
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Optimizing" << std::endl;
+	}
+	trie.reorder_depth_first();
+	trie.reorder_in_blocks(1024);
+	trie.update_records();
+
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Writing output" << std::endl;
+	}
+	write_dat_file(trie, cmdline.dat_file_name, cmdline.database_info,
+			database_segment, cmdline.address_family);
+
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": All done" << std::endl; }
+}
diff -Nru geoip-1.6.12/debian/src/geoip-csv-to-dat.cpp geoip-1.6.12/debian/src/geoip-csv-to-dat.cpp
--- geoip-1.6.12/debian/src/geoip-csv-to-dat.cpp	1970-01-01 02:00:00.000000000 +0200
+++ geoip-1.6.12/debian/src/geoip-csv-to-dat.cpp	2026-07-03 21:06:14.000000000 +0300
@@ -0,0 +1,1508 @@
+/* geoip-csv-to-dat - convert a country database from CSV to GeoIP binary format
+ *
+ * Copyright (c) 2009 Kalle Olavi Niemitalo.
+ * Copyright (c) 2011 Patrick Matthäi
+ * Copyright (c) 2014 Andrew Moise
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define _GNU_SOURCE 1
+#include <algorithm>
+#include <arpa/inet.h>
+#include <cerrno>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <error.h>
+#include <fstream>
+#include <getopt.h>
+#include <iostream>
+#include <queue>
+#include <set>
+#include <sstream>
+#include <stack>
+#include <string>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sysexits.h>
+#include <vector>
+#include <GeoIP.h>
+
+// Format of GeoIP Database files
+// ======================================
+//
+// 1. Binary trie mapping IP addresses to locations.
+// 2. Information about each location (only in city DBs).
+// 3. Optional unused data.
+// 4. Optional database-info block.
+// 5. Optional structure-info block.
+//
+// Binary trie
+// -----------
+//
+// The trie treats IP addresses as bit sequences and maps them to
+// location numbers.
+//
+// In the country database format, each such number is a country ID
+// that GeoIP_id_by_ipnum() would return.  The meanings of country IDs
+// are hardcoded in libGeoIP and cannot be overridden by the database.
+//
+// In the city database format, each such number is a position to seek
+// to, within section #2 (information about each location), in order
+// to find a record giving information about the location associated
+// with that IP.
+//
+// The root node of the trie is at the beginning of the file, and the
+// other nodes then follow it.	Each node has the same size and
+// consists of two little-endian pointers that correspond to the two
+// possible values of a bit.  The pointers are 24-bit, and each node
+// is thus 6 bytes long.
+//
+// To traverse the trie, you go one bit at a time through the IP
+// address you're looking up -- starting at bit 0, and starting at the
+// root node of the trie. For each bit, you look at the current node,
+// and take the left branch (the first of the two pointers) if the bit
+// is 0, otherwise take the right branch. It is allowed for a node
+// pointer to refer back to earlier nodes in the file, but loops are
+// not allowed.
+//
+// If the pointer you're looking at is less than the total number of
+// nodes in the trie, it indicates the next node you should
+// examine. If the pointer you're looking at is greater than or equal
+// to the total number of nodes, it indicates a leaf -- the end of
+// your search.
+//
+// The meaning of the leaf pointers depends on the database type:
+//
+// In country databases, 0xFFFF?? indicates that the country ID
+// for that IP address is equal to the ?? part. 0xFFFF00 indicates
+// that the IP address you queried is not in the database.
+//
+// City databases contain an extra segment which contains location
+// information records. A leaf pointer equal to (number of nodes in
+// the trie + X) indicates that the location information for the IP
+// address you queried can be found in the location information
+// segment, at offset X. A leaf pointer exactly equal to the number of
+// nodes in the trie indicates that the location information for the
+// IP address you queried is not stored in this database.
+//
+// Location segment
+// --------------------
+//
+// City databases contain a location segment. Each record in this
+// segment contains information about a single location which IP
+// addresses may be mapped to. Pointers to records in this segment are
+// contained within leaf nodes of the trie; each record contains, in
+// this order:
+//
+// * A country ID, as a single byte
+// * A "region" (generally state or province), as a NULL-terminated string
+// * A city name, as a NULL-terminated string
+// * A postal code, as a NULL-terminated string
+// * Encoded latitude, as a little-endian 3-byte integer. To convert
+//   back to actual latitude, divide by 10000 and subtract 180.
+// * Encoded longitude, as a little-endian 3-byte integer. To convert
+//   back to actual longitude, divide by 10000 and subtract 180.
+// * Area code and metro code (ONLY if the country is US). These are
+//   encoded into a single little-endian 3-byte integer. The area code
+//   is the encoded value modulo 1000, and the metro code is the
+//   encoded value divided by 1000. If the country is not US, this
+//   field is not present.
+//
+// The string fields may be equal to empty strings, but all fields are
+// always included (except area/metro code, which is included if and
+// only if the country is US).
+//
+// All strings seem to be in ISO-8859-1 encoding.
+//
+// Optional unused data
+// --------------------
+//
+// The file format permits any amount of extra data between the binary
+// trie and the optional blocks.
+//
+// Optional database-info block
+// ----------------------------
+//
+// Near the end of the file, there may be a three-byte tag (0x00 0x00
+// 0x00) followed by at most DATABASE_INFO_MAX_SIZE - 1 = 99 bytes of
+// text that describes the database.  GeoIP_database_info() returns
+// this text and appends a terminating '\0'.
+//
+// The GeoLite Country IPv4 database downloadable from MaxMind
+// includes this database-info block.
+//
+// Optional structure-info block
+// -----------------------------
+//
+// At the very end of the file, there may be a three-byte tag (0xFF
+// 0xFF 0xFF) followed by at most STRUCTURE_INFO_MAX_SIZE - 1 = 19
+// bytes.  The first byte is the database type,
+// e.g. GEOIP_COUNTRY_EDITION = 1 or GEOIP_COUNTRY_EDITION_V6 = 12,
+// possibly with 105 added to it.  Type-specific information then
+// follows.  There is no type-specific information for the country
+// editions.
+//
+// The GeoLite Country IPv4 database downloadable from MaxMind does
+// not include this structure-info block.
+
+
+
+/*************************************************
+ * Binary trie
+ *
+ * This section implements a data structure representing the trie
+ * which, within a .dat file, maps IP address ranges to locations.
+ *************************************************/
+
+namespace {
+	class binary_trie
+	{
+	public:
+		typedef uint_fast32_t edge_type;
+		struct node
+		{
+			edge_type edges[2];
+		};
+
+		explicit binary_trie(edge_type leaf);
+		void set_range(
+			const uint8_t range_min[],
+			const uint8_t range_max[],
+			std::size_t bit_count,
+			edge_type leaf);
+		void reorder_depth_first();
+		void reorder_in_blocks(std::size_t bytes_per_block);
+
+		std::vector<node>::iterator nodes_begin() { return nodes.begin(); }
+		std::vector<node>::iterator nodes_end() { return nodes.end(); }
+
+	private:
+		std::vector<node> nodes;
+
+		// This could be std::vector<bool> but that seems slower.
+		typedef std::vector<uint8_t> bits_vector;
+
+		void set_range_in_node(
+			const bits_vector *min_bits,
+			const bits_vector *max_bits,
+			std::size_t bit_pos,
+			edge_type edit_node,
+			edge_type leaf);
+		void set_range_in_edge(
+			const bits_vector *min_bits,
+			const bits_vector *max_bits,
+			std::size_t bit_pos,
+			edge_type edit_node,
+			bool bit,
+			edge_type leaf);
+		void reorder(
+			const std::vector<edge_type> &old_to_new,
+			const std::vector<edge_type> &new_to_old);
+	};
+}
+
+/** Construct a binary trie and its root node.
+ *
+ * \param leaf
+ * Both edges of the root node will initially point to this leaf.
+ * The caller should provide a value that means nothing was found.  */
+binary_trie::binary_trie(edge_type leaf)
+{
+	const node node = {{ leaf, leaf }};
+	nodes.push_back(node);
+}
+
+/** Edit the trie so it maps a range of bit sequences to the same
+ * leaf.
+ *
+ * \param range_min
+ * The first bit sequence in the range.  Eight bits are packed in each
+ * byte.  The most significant bit of the whole sequence is in the
+ * most significant bit of the first byte.
+ *
+ * \param range_max
+ * The last bit sequence in the range.
+ *
+ * \param bit_count
+ * The number of bits in both sequences.
+ *
+ * \param leaf
+ * The leaf to which all the bit sequences in the range should be
+ * mapped.  */
+void
+binary_trie::set_range(
+	const uint8_t range_min[],
+	const uint8_t range_max[],
+	std::size_t bit_count,
+	edge_type leaf)
+{
+	bits_vector min_bits(bit_count);
+	bits_vector max_bits(bit_count);
+	for (std::size_t i = 0; i < bit_count; ++i) {
+		std::size_t byte_pos = i / 8;
+		uint8_t mask = 1 << ((~i) % 8);
+		min_bits[i] = ((range_min[byte_pos] & mask) != 0);
+		max_bits[i] = ((range_max[byte_pos] & mask) != 0);
+	}
+	set_range_in_node(&min_bits, &max_bits, 0, 0, leaf);
+}
+
+/** Edit a node in the trie so it maps a range of bit sequences to the
+ * same leaf.
+ *
+ * \param min_bits
+ * The first bit sequence in the range, or NULL if unbounded.
+ *
+ * \param max_bits
+ * The last bit sequence in the range, or NULL if unbounded.
+ *
+ * \param bit_pos
+ * Which bit in the sequences corresponds to \a edit_node.
+ *
+ * \param edit_node
+ * The node to be modified.
+ *
+ * \param leaf
+ * The leaf to which all the bit sequences in the range should be
+ * mapped.  */
+void
+binary_trie::set_range_in_node(
+	const bits_vector *min_bits,
+	const bits_vector *max_bits,
+	std::size_t bit_pos,
+	edge_type edit_node,
+	edge_type leaf)
+{
+	if (!min_bits || (*min_bits)[bit_pos] == false) {
+		set_range_in_edge(min_bits,
+				  (max_bits && (*max_bits)[bit_pos] == false)
+				  ? max_bits : NULL,
+				  bit_pos + 1, edit_node, false, leaf);
+	}
+	if (!max_bits || (*max_bits)[bit_pos] == true) {
+		set_range_in_edge((min_bits && (*min_bits)[bit_pos] == true)
+				  ? min_bits : NULL,
+				  max_bits,
+				  bit_pos + 1, edit_node, true, leaf);
+	}
+}
+
+/** Edit an edge in the trie so it maps a range of bit sequences to
+ * the same leaf.
+ *
+ * \param min_bits
+ * The first bit sequence in the range, or NULL if unbounded.
+ *
+ * \param max_bits
+ * The last bit sequence in the range, or NULL if unbounded.
+ *
+ * \param bit_pos
+ * Which bit in the sequences corresponds to \a bit.
+ *
+ * \param edit_node
+ * The node in which the edge to be modified is located.
+ *
+ * \param bit
+ * Which edge of \a edit_node should be modified.
+ *
+ * \param leaf
+ * The leaf to which all the bit sequences in the range should be
+ * mapped.  */
+void
+binary_trie::set_range_in_edge(
+	const bits_vector *min_bits,
+	const bits_vector *max_bits,
+	std::size_t bit_pos,
+	edge_type edit_node,
+	bool bit,
+	edge_type leaf)
+{
+	// Check if the range fills this edge entirely.
+	bool entire = true;
+	if (min_bits
+	    && std::find(min_bits->begin() + bit_pos, min_bits->end(),
+			 true) != min_bits->end())
+		entire = false;
+	if (max_bits
+	    && std::find(max_bits->begin() + bit_pos, max_bits->end(),
+			 false) != max_bits->end())
+		entire = false;
+
+	if (entire) {
+		nodes[edit_node].edges[bit] = leaf;
+	} else {
+		edge_type next = nodes[edit_node].edges[bit];
+		if (next >= nodes.size()) {
+			const node new_node = {{ next, next }};
+			next = nodes.size();
+			nodes.push_back(new_node);
+			nodes[edit_node].edges[bit] = next;
+		}
+
+		set_range_in_node(min_bits, max_bits, bit_pos,
+				  next, leaf);
+	}
+}
+
+/** Renumber the nodes in depth-first order.  */
+void
+binary_trie::reorder_depth_first()
+{
+	std::vector<edge_type> old_to_new, new_to_old;
+	std::stack<edge_type> depth_first;
+	old_to_new.resize(nodes.size(), -1);
+	new_to_old.reserve(nodes.size());
+	depth_first.push(0);
+	while (!depth_first.empty()) {
+		const edge_type edge = depth_first.top();
+		depth_first.pop();
+		if (edge < nodes.size()) {
+			old_to_new[edge] = new_to_old.size();
+			new_to_old.push_back(edge);
+			depth_first.push(nodes[edge].edges[1]);
+			depth_first.push(nodes[edge].edges[0]);
+		}
+	}
+	reorder(old_to_new, new_to_old);
+}
+
+/** Renumber the nodes to make lookups use CPU and disk caches more
+ * effectively.
+ *
+ * First group the nodes into blocks so that each block contains the
+ * root of a subtrie and as many levels of its descendants as will
+ * fit.  This way, after the root is paged in, the next few lookup
+ * steps need not page in anything else.  Then, sort the nodes of each
+ * block in depth-first order.  That should give each lookup almost
+ * 1/2 chance to find the next node immediately adjacent.
+ *
+ * With a block size of 1024 bytes, this renumbering reduces the time
+ * required for random lookups by about 1.1%, compared to a plain
+ * depth-first order.  However, it's still 2.3% slower than the
+ * database optimized by MaxMind.  */
+void
+binary_trie::reorder_in_blocks(
+	std::size_t bytes_per_block)
+{
+	const edge_type none = -1;
+	std::vector<edge_type> old_to_new, new_to_old;
+	ssize_t bytes_left = bytes_per_block;
+	old_to_new.resize(nodes.size(), none);
+	new_to_old.reserve(nodes.size());
+	for (edge_type subtrie = 0; subtrie < nodes.size(); ++subtrie) {
+		// If subtrie has already been added to the output,
+		// ignore it.
+		if (old_to_new[subtrie] != none)
+			continue;
+
+		// Walk breadth-first from subtrie until we have a
+		// block full of nodes or the subtrie runs out.  Don't
+		// add these nodes immediately to the output, however.
+		// Instead just list them in nodes_in_block.
+		std::set<edge_type> nodes_in_block;
+		std::queue<edge_type> breadth_first;
+		breadth_first.push(subtrie);
+		if (bytes_left <= 0)
+			bytes_left += bytes_per_block;
+		while (bytes_left > 0 && !breadth_first.empty()) {
+			edge_type edge = breadth_first.front();
+			breadth_first.pop();
+			if (edge >= nodes.size())
+				continue;
+
+			// Let the last node of the block straddle the
+			// block boundary.  That's better than making
+			// the hotter first node do so.
+			bytes_left -= 6;
+			nodes_in_block.insert(edge);
+
+			breadth_first.push(nodes[edge].edges[0]);
+			breadth_first.push(nodes[edge].edges[1]);
+		}
+
+		// Add the nodes from nodes_in_block to the output in
+		// depth-first order.  This assumes they are all
+		// reachable from subtrie.
+		std::stack<edge_type> depth_first;
+		depth_first.push(subtrie);
+		while (!depth_first.empty()) {
+			edge_type edge = depth_first.top();
+			depth_first.pop();
+			if (nodes_in_block.find(edge)
+			    == nodes_in_block.end())
+				continue;
+
+			old_to_new[edge] = new_to_old.size();
+			new_to_old.push_back(edge);
+
+			depth_first.push(nodes[edge].edges[1]);
+			depth_first.push(nodes[edge].edges[0]);
+		}
+	}
+	reorder(old_to_new, new_to_old);
+}
+
+void
+binary_trie::reorder(
+	const std::vector<edge_type> &old_to_new,
+	const std::vector<edge_type> &new_to_old)
+{
+	std::vector<node> new_nodes;
+	new_nodes.reserve(new_to_old.size());
+	for (std::vector<edge_type>::const_iterator
+		     it = new_to_old.begin();
+	     it != new_to_old.end(); ++it) {
+		node new_node;
+		for (int bit = 0; bit <= 1; ++bit) {
+			edge_type old_edge = nodes[*it].edges[bit];
+			if (old_edge < nodes.size())
+				new_node.edges[bit] = old_to_new[old_edge];
+			else
+				new_node.edges[bit] = old_edge;
+		}
+		new_nodes.push_back(new_node);
+	}
+	swap(new_nodes, nodes);
+}
+
+/*************************************************
+ * CSV file support
+ *
+ * This section implements reading from .csv files.
+ *************************************************/
+
+namespace {
+	/** Interface for classes interested in .csv data -- this should be
+	 * implemented and then passed to csv_read_file(), which will then
+	 * call read_csv_line(), providing the data in the .csv file. */
+	class csv_data_reader
+	{
+	public:
+		virtual ~csv_data_reader() {}
+
+		virtual void read_csv_line(const char *csv_file_name,
+					   int csv_line_number,
+					   std::vector<std::string> &fields) = 0;
+	};
+}
+
+namespace {
+	/** Convert a line from a .csv file into a vector of
+	 *  tokens. For internal use by the .csv reading code. */
+	void
+	csv_line_to_vector(
+		const std::string &line,
+		std::vector<std::string> &fields)
+	{
+		fields.clear();
+		std::vector<char> field;
+		bool quoted = false;
+		bool spaces_after_comma = false;
+		for (std::string::const_iterator it = line.begin();
+		     it != line.end(); ++it) {
+			if (*it == '"') {
+				quoted = !quoted;
+				spaces_after_comma = false;
+			} else if (*it == ',' && !quoted) {
+				fields.push_back(std::string(field.begin(), field.end()));
+				field.clear();
+				spaces_after_comma = true;
+			} else if (*it == ' ' && spaces_after_comma) {
+			} else {
+				field.push_back(*it);
+				spaces_after_comma = false;
+			}
+		}
+		fields.push_back(std::string(field.begin(), field.end()));
+	}
+
+	/** Load data from a CSV-formatted stream.
+	 *
+	 * \param reader
+	 * The reader to call for each line of the CSV
+	 *
+	 * \param csv_file_name
+	 * The name of the file that \a csv_stream is reading.
+	 * This string is used only for error messages.
+	 *
+	 * \param csv_stream
+	 * The stream to read from.   */
+	void
+	csv_read_stream(
+		csv_data_reader &reader,
+		const char *csv_file_name,
+		std::istream &csv_stream)
+	{
+		std::string csv_line;
+		std::vector<std::string> csv_fields;
+		int csv_line_number = 0;
+		while (getline(csv_stream, csv_line)) {
+			++csv_line_number;
+			csv_line_to_vector(csv_line, csv_fields);
+			reader.read_csv_line(csv_file_name, csv_line_number, csv_fields);
+		}
+		if (csv_stream.bad()) {
+			error(EX_IOERR, errno, "%s", csv_file_name);
+		}
+	}
+
+	/** Load data from a CSV-formatted file or standard input.
+	 *
+	 * \param reader
+	 * The reader to call for each line of the CSV.
+	 *
+	 * \param csv_file_name
+	 * The name of the CSV file that should be read, or "-" for
+	 * standard input.   */
+	void
+	csv_read_file(
+		csv_data_reader &reader,
+		const char *csv_file_name)
+	{
+		if (std::strcmp(csv_file_name, "-") == 0) {
+			csv_read_stream(reader, csv_file_name, std::cin);
+		} else {
+			std::ifstream csv_stream(csv_file_name, std::ios::in);
+			if (!csv_stream) {
+				error(EX_NOINPUT, errno, "%s", csv_file_name);
+			}
+			csv_read_stream(reader, csv_file_name, csv_stream);
+		}
+	}
+}
+
+/*************************************************
+ * .dat file support
+ *
+ * This section implements support code for writing out .dat files in
+ * Maxmind DB format.
+ *************************************************/
+
+namespace {
+
+	/** .dat file writer class
+	 *
+	 * To write out a .dat file, construct a dat_writer, then call
+	 * (in this order):
+	 *
+	 * write_trie()
+	 * write_database_info (optional)
+	 * write_structure_info()
+	 *
+	 * Setting dat_file_name to "-" will write to standard output;
+	 * otherwise, a file will be created, and closed when the
+	 * dat_writer is deleted. */
+
+	class dat_writer
+	{
+	public:
+		dat_writer(const char *dat_file_name, GeoIPDBTypes database_type);
+		virtual ~dat_writer();
+
+		void write_trie(binary_trie &trie);
+		void write_database_info(const char *database_info);
+		virtual void write_structure_info();
+
+	protected:
+		std::ostream *dat_stream;
+		bool need_to_delete_stream;
+		std::string dat_file_name;
+		GeoIPDBTypes database_type;
+	};
+
+}
+
+dat_writer::dat_writer(const char *dat_file_name, GeoIPDBTypes database_type):
+	dat_file_name(dat_file_name),
+	database_type(database_type)
+{
+	if (std::strcmp(dat_file_name, "-") == 0) {
+		dat_stream = &std::cout;
+		need_to_delete_stream = false;
+	} else {
+		dat_stream = new std::ofstream(dat_file_name, std::ios::out | std::ios::binary);
+		if (!dat_stream) {
+			error(EX_CANTCREAT, errno, "%s", dat_file_name);
+		}
+		need_to_delete_stream = true;
+	}
+}
+
+dat_writer::~dat_writer()
+{
+	if (need_to_delete_stream)
+		delete dat_stream;
+}
+
+void dat_writer::write_trie(binary_trie &trie)
+{
+	for (std::vector<binary_trie::node>::iterator it = trie.nodes_begin();
+	     it != trie.nodes_end(); ++it)
+	{
+		union {
+			uint8_t bytes[6];
+			char chars[6];
+		} binary = {{
+			(it->edges[0]	   ) & 0xFF,
+			(it->edges[0] >>  8) & 0xFF,
+			(it->edges[0] >> 16) & 0xFF,
+			(it->edges[1]	   ) & 0xFF,
+			(it->edges[1] >>  8) & 0xFF,
+			(it->edges[1] >> 16) & 0xFF
+		}};
+		dat_stream->write(binary.chars, 6);
+		if (dat_stream->bad())
+			error(EX_IOERR, errno, "%s", dat_file_name.c_str());
+	}
+}
+
+void dat_writer::write_database_info(const char *database_info)
+{
+	const char tag[3] = { 0, 0, 0 };
+	dat_stream->write(tag, 3);
+	dat_stream->write(database_info, std::strlen(database_info));
+	if (dat_stream->bad()) {
+		error(EX_IOERR, errno, "%s", dat_file_name.c_str());
+	}
+}
+
+void dat_writer::write_structure_info()
+{
+	const unsigned char structure_info[4] = { 0xFF, 0xFF, 0xFF, database_type };
+	dat_stream->write((const char *)structure_info, 4);
+}
+
+/*************************************************
+ * .dat file writer class, extended for city DBs
+ *************************************************/
+
+namespace
+{
+
+	class city_dat_writer : public dat_writer
+	{
+	public:
+		// All serialized location information, in one big
+		// undifferentiated block
+		std::stringstream location_stream;
+
+		// Seek offset of each location within
+		// location_stream (relative to the beginning of
+		// location_stream). An offset of -1 means that that
+		// location is not in the table (can happen if the
+		// location info's out of order).
+		std::vector<int> location_pos;
+
+		// Set of location IDs that are actually going to be used;
+		// we'll silently ignore any locations not in this set.
+		std::set<int> needed_locations;
+	  
+		city_dat_writer(const char *dat_file_name, GeoIPDBTypes database_type);
+
+		// Notify of a location ID we need -- this MUST be
+		// called for every location ID you care about before
+		// the location CSV is read; any ID not explicitly
+		// notified will be discarded.
+		void notify_need_location(int loc_id);
+		
+		void serialize_location_info(std::vector<std::string> &info,
+					     const char *input_file_name,
+					     int input_line_number);
+
+		void finalize_location_offsets(binary_trie &trie);
+		void write_locations();
+		virtual void write_structure_info(binary_trie &trie);
+	};
+
+}
+
+city_dat_writer::city_dat_writer(const char *dat_file_name, GeoIPDBTypes database_type)
+	: dat_writer(dat_file_name, database_type)
+{ }
+
+void city_dat_writer::notify_need_location(int loc_id)
+{
+	needed_locations.insert(loc_id);
+}
+
+void city_dat_writer::finalize_location_offsets(binary_trie &trie)
+{
+	// We're going to convert the location numbers in the trie
+	// into the final location numbers we're going to want to
+	// write to disk. Previous to this call, leaf nodes in the
+	// trie have the value:
+	//
+	// 0x1000000 + the location number
+	//
+	// After this call, leaf nodes in the trie have the value:
+	//
+	// (total number of nodes in the trie) + (offset of location
+	// record in the location segment)
+	//
+	// Absence of a record is indicated by the value 0x1000000
+	// before this call, and by the value (total number of nodes
+	// in the trie) after this call.
+
+	int trie_size = std::distance(trie.nodes_begin(), trie.nodes_end());
+
+	for(std::vector<binary_trie::node>::iterator it = trie.nodes_begin();
+	    it != trie.nodes_end(); ++it)
+	{
+		if (it->edges[0] == 0x1000000) // No data
+			it->edges[0] = trie_size;
+		else if (it->edges[0] > 0x1000000) { // Ptr to location block
+			int loc_id = it->edges[0] - 0x1000000;
+			if (loc_id >= location_pos.size() || location_pos[loc_id] == -1)
+				error(EX_DATAERR, 1, "Location %d exists in blocks but not in locations", loc_id);
+
+			int offset = location_pos[loc_id] + trie_size;
+			if (offset > 0xFFFFFF)
+				error(EX_DATAERR, 1, "Overflow! Offset for location %d too large (0x%x > 0xFFFFFF)", loc_id, offset);
+			it->edges[0] = offset;
+		}
+		// Any other value would indicate a non-leaf node
+
+		if (it->edges[1] == 0x1000000) // No data
+			it->edges[1] = trie_size;
+		else if (it->edges[1] > 0x1000000) { // Ptr to location block
+			int loc_id = it->edges[1] - 0x1000000;
+			if (loc_id >= location_pos.size() || location_pos[loc_id] == -1)
+				error(EX_DATAERR, 1, "Location %d exists in blocks but not in locations", loc_id);
+
+			int offset = location_pos[loc_id] + trie_size;
+			if (offset > 0xFFFFFF)
+				error(EX_DATAERR, 1, "Overflow! Offset for location %d too large (0x%x > 0xFFFFFF)", loc_id, offset);
+			it->edges[1] = offset;
+		}
+		// Any other value would indicate a non-leaf node
+	}
+}
+
+void city_dat_writer::write_locations()
+{
+	*dat_stream << location_stream.rdbuf();
+
+	if (dat_stream->bad())
+	{
+		error(EX_IOERR, errno, "%s", dat_file_name.c_str());
+	}
+}
+
+void city_dat_writer::write_structure_info(binary_trie &trie)
+{
+	int trie_size = std::distance(trie.nodes_begin(), trie.nodes_end());
+
+	const unsigned char structure_info[7] = { 0xFF,
+						  0xFF,
+						  0xFF,
+						  database_type,
+						  (trie_size      ) & 0xFF,
+						  (trie_size >> 8 ) & 0xFF,
+						  (trie_size >> 16) & 0xFF};
+	dat_stream->write((const char *)structure_info, 7);
+}
+
+/** Convert location info into on-disk format
+ *
+ *  \param info the location info read from the .csv file:
+ *
+ *  info[CSV_LOCATION_FIELD_COUNTRY] is the country id
+ *  info[CSV_LOCATION_FIELD_REGION] is the region
+ *  info[CSV_LOCATION_FIELD_CITY] is the city
+ *
+ *  ... and so on.
+ *
+ *  \param result a vector to append the on-disk converted information
+ *  to.
+ *
+ *  \param input_line_number input file line number (for error
+ *  notifications)
+ **/
+
+void city_dat_writer::serialize_location_info(std::vector<std::string> &info,
+					      const char *input_file_name,
+					      int input_line_number)
+{
+	// First, we determine the offset of this location block.
+	int loc_id = ::atoi(info[0].c_str());
+
+	if (needed_locations.find(loc_id) == needed_locations.end()) {
+		// We don't need this location, so we skip serializing
+		// it altogether.
+
+		return;
+	}
+
+	if (loc_id >= location_pos.size()) {
+		// We need to add to the location table (this is the
+		// usual case).
+		
+		while(loc_id > location_pos.size()) {
+			// If some numbers were skipped in the data,
+			// then we need to add some empty locations to
+			// the table before we find our spot.
+			location_pos.push_back(-1);
+		}
+
+		// Now we have our spot, insert this location.
+		location_pos.push_back(location_stream.tellp());
+	} else {
+		// We already have a space in the table for this location --
+		// if it's not empty, then we have two locations with the same
+		// ID, and we print an error.
+		if (location_pos[loc_id] != -1) {
+			error_at_line(EX_DATAERR, 0, input_file_name,
+				      input_line_number,
+				      "Duplicate location info for ID %d",
+				      loc_id);
+		}
+		location_pos[loc_id] = location_stream.tellp();
+	}
+
+	// Country ID
+	int country_id;
+	if (info[1] != "AN")
+		country_id = GeoIP_id_by_code(info[1].c_str());
+	else
+		country_id = GeoIP_id_by_code("CW");
+
+	if (country_id == 0) {
+		error(EX_DATAERR, 1, dat_file_name.c_str(), input_line_number,
+		      "Unrecognized country code: %s", info[1].c_str());
+	}
+	location_stream.put(country_id);
+
+	// Region
+	location_stream << info[2];
+	location_stream.put('\0');
+
+	// City
+	location_stream << info[3];
+	location_stream.put('\0');
+
+	// Postal code
+	location_stream << info[4];
+	location_stream.put('\0');
+
+	// Latitude
+	double latitude_dbl = ::atof(info[5].c_str());
+	int latitude_int = (latitude_dbl + 180) * 10000;
+	location_stream.put((latitude_int >>  0) & 0xFF);
+	location_stream.put((latitude_int >>  8) & 0xFF);
+	location_stream.put((latitude_int >> 16) & 0xFF);
+
+	// Longitude
+	double longitude_dbl = ::atof(info[6].c_str());
+	int longitude_int = (longitude_dbl + 180) * 10000;
+	location_stream.put((longitude_int >>  0) & 0xFF);
+	location_stream.put((longitude_int >>  8) & 0xFF);
+	location_stream.put((longitude_int >> 16) & 0xFF);
+
+	// Area code and metro code
+	if (info[1] == "US") {
+		int metro_code = ::atoi(info[7].c_str());
+		int area_code = ::atoi(info[8].c_str());
+		int area_metro_combined = metro_code * 1000 + area_code;
+		location_stream.put((area_metro_combined >>  0) & 0xFF);
+		location_stream.put((area_metro_combined >>  8) & 0xFF);
+		location_stream.put((area_metro_combined >> 16) & 0xFF);
+	}
+}
+
+/*************************************************
+ * Command line and options
+ *
+ * This section implements the command line parsing and stores the
+ * options for controlling the program's behavior.
+ *************************************************/
+
+namespace {
+
+	struct cmdline {
+		const char *ip_block_csv_file_name;
+		const char *location_csv_file_name;
+		const char *dat_file_name;
+		int address_family;
+	        GeoIPDBTypes database_type;
+		const char *database_info;
+		bool verbose;
+
+		cmdline(int argc, char **argv);
+	};
+}
+
+cmdline::cmdline(int argc, char **argv):
+	ip_block_csv_file_name("-"),
+	location_csv_file_name(NULL),
+	dat_file_name("-"),
+	address_family(AF_INET),
+	database_type(GEOIP_COUNTRY_EDITION),
+	database_info(NULL),
+	verbose(false)
+{
+	enum {
+		OPT_HELP = -2
+	};
+
+	static const struct option long_options[] = {
+		{ "inet", no_argument, NULL, '4' },
+		{ "inet6", no_argument, NULL, '6' },
+		{ "info", required_argument, NULL, 'i' },
+		{ "location-csv", required_argument, NULL, 'l' },
+		{ "output", required_argument, NULL, 'o' },
+		{ "type", required_argument, NULL, 't' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "help", no_argument, NULL, OPT_HELP },
+		{ NULL, 0, NULL, 0 }
+	};
+	static const char *const usage = "\
+Usage: %s [OPTION] [CSV-FILE]...\n\
+Convert a GeoIP database from CSV to GeoIP binary format.\n\
+\n\
+  -4, --inet              set database type to GEOIP_COUNTRY_EDITION, v4 addresses (default)\n\
+  -6, --inet6             set database type to GEOIP_COUNTRY_EDITION_V6, v6 addresses\n\
+  -t, --type=TYPE         set database type explicitly (e.g. to GEOIP_CITY_EDITION_REV1)\n\
+  -i, --info=TEXT         add copyright or other info TEXT to output\n\
+  -l, --location-csv=FILE set location CSV file name (required for GEOIP_CITY_EDITION_REV1)\n\
+  -o, --output=FILE       write the binary data to FILE, not stdout\n\
+  -v, --verbose           show what is going on\n\
+      --help              display this help and exit\n";
+
+	for (;;) {
+		int optret = getopt_long(argc, argv, "46i:l:o:t:v", long_options, NULL);
+
+		if (optret == -1)
+			break;
+		switch (optret) {
+		case '4':
+			address_family = AF_INET;
+			break;
+		case '6':
+			database_type = GEOIP_COUNTRY_EDITION_V6;
+			address_family = AF_INET6;
+			break;
+		case 'i':
+			database_info = optarg;
+			if (std::strlen(database_info) > 99) {
+				error(EX_USAGE, 0,
+				      "Database info must not be longer than 99 bytes");
+			}
+			break;
+		case 'l':
+			location_csv_file_name = optarg;
+			break;
+		case 'o':
+			dat_file_name = optarg;
+			break;
+		case 't':
+			if (!strcmp(optarg, "GEOIP_COUNTRY_EDITION")) {
+				database_type = GEOIP_COUNTRY_EDITION;
+			} else if (!strcmp(optarg, "GEOIP_COUNTRY_EDITION_V6")) {
+				database_type = GEOIP_COUNTRY_EDITION_V6;
+				address_family = AF_INET6;
+			} else if (!strcmp(optarg, "GEOIP_CITY_EDITION_REV1")) {
+				database_type = GEOIP_CITY_EDITION_REV1;
+			} else {
+				error(EX_USAGE, 0,
+				      "Unrecognized database type (we support GEOIP_COUNTRY_EDITION, GEOIP_COUNTRY_EDITION_V6, \
+GEOIP_CITY_EDITION_REV1)");
+			}
+			break;
+		case 'v':
+			verbose = true;
+			break;
+		case OPT_HELP:
+			std::printf(usage, program_invocation_name);
+			std::exit(EX_OK);
+		case '?':
+			std::fprintf(stderr,
+				     "Try `%s --help' for more information.\n",
+				     program_invocation_name);
+			std::exit(EX_USAGE);
+		default:
+			std::abort();
+		}
+	}
+
+	if (optind < argc)
+		ip_block_csv_file_name = argv[optind++];
+
+	if (database_type == GEOIP_CITY_EDITION_REV1 && location_csv_file_name == NULL) {
+		error(EX_USAGE, 0,
+		      "Must specify -l option when type is GEOIP_CITY_EDITION_REV1");
+	}
+
+	if (optind < argc) {
+		error(EX_USAGE, 0,
+		      "Only one non-option argument is allowed");
+	}
+}
+
+/*************************************************
+ * Country DB reading and writing
+ *
+ * This section contains code implementing coverting a country .csv
+ * file to a country .dat file.
+ *************************************************/
+
+namespace {
+
+	class country_db_impl : public csv_data_reader
+	{
+	public:
+		binary_trie trie;
+		struct cmdline &cmdline;
+
+		enum {
+			CSV_FIELD_MIN_TEXT,
+			CSV_FIELD_MAX_TEXT,
+			CSV_FIELD_MIN_DECIMAL,
+			CSV_FIELD_MAX_DECIMAL,
+			CSV_FIELD_COUNTRY_CODE,
+			CSV_FIELD_COUNTRY_NAME,
+			CSV_FIELDS
+		};
+
+		country_db_impl(struct cmdline &cmdline);
+		void convert_db(std::ostream *verbose_stream);
+		void read_csv_line(const char *csv_file_name,
+				   int csv_line_number,
+				   std::vector<std::string> &fields);
+	};
+
+}
+
+country_db_impl::country_db_impl(struct cmdline &in_cmdline):
+	cmdline(in_cmdline),
+	trie(0xFFFF00)
+{ }
+
+/** Callback for receiving .csv data (see csv_read_file()) */
+
+void country_db_impl::read_csv_line(const char *csv_file_name,
+				    int csv_line_number,
+				    std::vector<std::string> &csv_fields)
+{
+	if (csv_fields.size() != CSV_FIELDS) {
+		error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+			      "Wrong number of fields");
+	}
+
+	if (csv_fields[CSV_FIELD_COUNTRY_CODE] == "AN") {
+		csv_fields[CSV_FIELD_COUNTRY_CODE] = "CW";
+	}
+	else if (csv_fields[CSV_FIELD_COUNTRY_CODE] == "XK") {
+		csv_fields[CSV_FIELD_COUNTRY_CODE] = "RS";
+	}
+
+	const int countryid = GeoIP_id_by_code(csv_fields[CSV_FIELD_COUNTRY_CODE].c_str());
+	if (countryid == 0) {
+		error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+			      "Unrecognized country code: %s",
+			      csv_fields[CSV_FIELD_COUNTRY_CODE].c_str());
+	}
+	const binary_trie::edge_type leaf = 0xFFFF00 + countryid;
+
+	union {
+		struct in_addr inet;
+		uint8_t inetbytes[4];
+		struct in6_addr inet6;
+	} minaddr, maxaddr;
+	if (inet_pton(cmdline.address_family, csv_fields[CSV_FIELD_MIN_TEXT].c_str(), &minaddr) <= 0) {
+		error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+			      "Cannot parse minimum address: %s",
+			      csv_fields[CSV_FIELD_MIN_TEXT].c_str());
+	}
+	if (inet_pton(cmdline.address_family, csv_fields[CSV_FIELD_MAX_TEXT].c_str(), &maxaddr) <= 0) {
+		error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+			      "Cannot parse maximum address: %s",
+			      csv_fields[CSV_FIELD_MAX_TEXT].c_str());
+	}
+	switch (cmdline.address_family) {
+	case AF_INET:
+		trie.set_range(minaddr.inetbytes, maxaddr.inetbytes,
+			       32, leaf);
+		break;
+	case AF_INET6:
+		trie.set_range(minaddr.inet6.s6_addr, maxaddr.inet6.s6_addr,
+			       128, leaf);
+		break;
+	default:
+		abort();
+	}
+}
+
+/** Convert a country DB from .csv to .dat. Parameters are mainly
+ *  controlled by the cmdline object. verbose_stream is (if non-NULL)
+ *  the stream to write verbose information to. */
+
+void country_db_impl::convert_db(std::ostream *verbose_stream)
+{
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Reading CSV and building the trie"
+				<< std::endl;
+	}
+	csv_read_file(*this, cmdline.ip_block_csv_file_name);
+
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Optimizing" << std::endl;
+	}
+	trie.reorder_depth_first();
+	trie.reorder_in_blocks(1024);
+
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Writing output" << std::endl;
+	}
+
+	dat_writer writer(cmdline.dat_file_name, cmdline.database_type);
+	writer.write_trie(trie);
+	if (cmdline.database_info)
+		writer.write_database_info(cmdline.database_info);
+	writer.write_structure_info();
+}
+
+/*************************************************
+ * City DB conversion
+ *
+ * This section implements converting the two .csv files storing city
+ * data to a city .dat file.
+ *************************************************/
+
+namespace {
+
+	/** Implementation for converting a city DB
+	 */
+
+	class city_db_impl : public csv_data_reader
+	{
+	public:
+		// Trie mapping IP ranges to location blocks
+		binary_trie trie;
+
+		// Writer for .dat file
+		city_dat_writer writer;
+
+		enum {
+			STAGE_READING_BLOCKS,
+			STAGE_READING_LOCATIONS
+		};
+
+		// Which stage of CSV reading we're at (out of above
+		// enum)
+		int which_stage;
+
+		struct cmdline &cmdline;
+
+		enum {
+			CSV_BLOCK_FIELD_MIN_DECIMAL,
+			CSV_BLOCK_FIELD_MAX_DECIMAL,
+			CSV_BLOCK_FIELD_LOC,
+			CSV_BLOCK_FIELDS
+		};
+
+		enum {
+			CSV_LOCATION_FIELD_ID,
+			CSV_LOCATION_FIELD_COUNTRY,
+			CSV_LOCATION_FIELD_REGION,
+			CSV_LOCATION_FIELD_CITY,
+			CSV_LOCATION_FIELD_POSTALCODE,
+			CSV_LOCATION_FIELD_LATITUDE,
+			CSV_LOCATION_FIELD_LONGITUDE,
+			CSV_LOCATION_FIELD_METROCODE,
+			CSV_LOCATION_FIELD_AREACODE,
+			CSV_LOCATION_FIELDS
+		};
+
+		city_db_impl(struct cmdline &cmdline);
+		void convert_db(std::ostream *verbose_stream);
+		void read_csv_line(const char *csv_file_name,
+				   int csv_line_number,
+				   std::vector<std::string> &fields);
+
+		void read_location_line(const char *csv_file_name,
+					int csv_line_number,
+					std::vector<std::string> &fields);
+		void read_block_line(const char *csv_file_name,
+				     int csv_line_number,
+				     std::vector<std::string> &fields);
+
+		// Check that a token within the "header" of the CSV
+		// files is what we expect it to be, and cause a data
+		// error if not.
+		void check_csv_header_token(std::vector<std::string> &tokens,
+					    int token_number,
+					    const char *token_expected,
+					    const char *csv_file_name,
+					    int csv_line_number);
+	};
+
+}
+
+city_db_impl::city_db_impl(struct cmdline &in_cmdline):
+	trie(0x1000000), // We use 0x1000000 as the beginning of the
+	                 // location information, since we don't know
+	                 // the real value and we'll need to remap all
+			 // the offsets later anyway.
+	writer(in_cmdline.dat_file_name, in_cmdline.database_type),
+	cmdline(in_cmdline),
+	which_stage(STAGE_READING_BLOCKS)
+{ }
+
+/** Convert a city DB from .csv to .dat. Parameters are mainly
+ *  controlled by the cmdline object. verbose_stream is (if non-NULL)
+ *  the stream to write verbose information to. */
+
+void
+city_db_impl::convert_db(std::ostream *verbose_stream)
+{
+	// Read the block data from CSV
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Reading block CSV and building the trie"
+				<< std::endl;
+	}
+
+	which_stage = STAGE_READING_BLOCKS;
+	csv_read_file(*this, cmdline.ip_block_csv_file_name);
+
+	if (verbose_stream) {
+		int trie_size = std::distance(trie.nodes_begin(), trie.nodes_end());
+
+		*verbose_stream << program_invocation_name
+				<< ": Done reading blocks, trie size is "
+				<< trie_size
+				<< std::endl;
+	}
+
+	// Read the location data from CSV
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Reading location CSV"
+				<< std::endl;
+	}
+
+	which_stage = STAGE_READING_LOCATIONS;
+	csv_read_file(*this, cmdline.location_csv_file_name);
+
+	// Optimize
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Optimizing" << std::endl;
+	}
+
+	trie.reorder_depth_first();
+	trie.reorder_in_blocks(1024);
+
+	// Finalize offsets
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Linking location and block data" << std::endl;
+	}
+
+	writer.finalize_location_offsets(trie);
+
+	// Write
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": Writing output" << std::endl;
+	}
+
+	writer.write_trie(trie);
+	writer.write_locations();
+	if (cmdline.database_info)
+		writer.write_database_info(cmdline.database_info);
+	writer.write_structure_info(trie);
+}
+
+/** Callback for receiving CSV data (see csv_read_file()). We use
+ *  this both for reading the location CSV and the block CSV; which
+ *  stage we're at is indicated by the which_stage variable. */
+
+void city_db_impl::read_csv_line(const char *csv_file_name,
+				 int csv_line_number,
+				 std::vector<std::string> &csv_fields)
+{
+	switch(which_stage) {
+	case STAGE_READING_BLOCKS:
+		read_block_line(csv_file_name, csv_line_number, csv_fields);
+		break;
+	case STAGE_READING_LOCATIONS:
+		read_location_line(csv_file_name, csv_line_number, csv_fields);
+		break;
+	default:
+		error(EX_SOFTWARE, 1, "Invalid which_stage value: %d", which_stage);
+	}
+}
+
+/** Callback for reading one line of the block CSV. */
+
+void city_db_impl::read_block_line(const char *csv_file_name,
+				   int csv_line_number,
+				   std::vector<std::string> &csv_fields)
+{
+	if (csv_line_number == 1)
+		return; // Assume this is copyright information and
+			// skip doing anything to it
+
+	if (csv_fields.size() != CSV_BLOCK_FIELDS) {
+		error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+			      "Wrong number of fields");
+		return;
+	}
+
+	if (csv_line_number == 2) {
+		// Assume this is header information -- we check it to
+		// make sure we're looking at the right format of file.
+		check_csv_header_token(csv_fields, CSV_BLOCK_FIELD_MIN_DECIMAL, "startIpNum",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_BLOCK_FIELD_MAX_DECIMAL, "endIpNum",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_BLOCK_FIELD_LOC, "locId",
+				       csv_file_name, csv_line_number);
+
+		// Format checks out, we're now done with this line
+		return;
+	}
+
+	const int loc_id = atoi(csv_fields[CSV_BLOCK_FIELD_LOC].c_str());
+	const binary_trie::edge_type leaf = 0x1000000 + loc_id;
+
+	if (cmdline.address_family != AF_INET) {
+		error(EX_SOFTWARE, 1, "IPv6 with city database is unimplemented.");
+	}
+
+	union {
+		struct in_addr inet;
+		uint8_t inetbytes[4];
+	} minaddr, maxaddr;
+
+	if (inet_aton(csv_fields[CSV_BLOCK_FIELD_MIN_DECIMAL].c_str(), &(minaddr.inet)) == 0) {
+		error_at_line(EX_DATAERR, 1, csv_file_name, csv_line_number,
+			      "Invalid min IP address");
+	}
+	if (inet_aton(csv_fields[CSV_BLOCK_FIELD_MAX_DECIMAL].c_str(), &(maxaddr.inet)) == 0) {
+		error_at_line(EX_DATAERR, 1, csv_file_name, csv_line_number,
+			      "Invalid max IP address");
+	}
+
+	writer.notify_need_location(loc_id);
+	trie.set_range(minaddr.inetbytes, maxaddr.inetbytes, 32, leaf);
+}
+
+/** Callback for reading one line of the location CSV. */
+
+void city_db_impl::read_location_line(const char *csv_file_name,
+				      int csv_line_number,
+				      std::vector<std::string> &csv_fields)
+{
+	if (csv_line_number == 1)
+		return; // Assume this is copyright information and
+			// skip it entirely
+
+	if (csv_fields.size() != CSV_LOCATION_FIELDS) {
+		error_at_line(EX_DATAERR, 0, csv_file_name, csv_line_number,
+			      "Wrong number of fields");
+		return;
+	}
+
+	if (csv_line_number == 2) {
+		// Assume this is header information -- we check it to
+		// make sure we're looking at the right format of file.
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_ID, "locId",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_COUNTRY, "country",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_REGION, "region",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_CITY, "city",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_POSTALCODE, "postalCode",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_LATITUDE, "latitude",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_LONGITUDE, "longitude",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_METROCODE, "metroCode",
+				       csv_file_name, csv_line_number);
+		check_csv_header_token(csv_fields, CSV_LOCATION_FIELD_AREACODE, "areaCode",
+				       csv_file_name, csv_line_number);
+
+		// Format checks out, we're now done with this line
+		return;
+	}
+
+	writer.serialize_location_info(csv_fields, csv_file_name, csv_line_number);
+}
+
+void city_db_impl::check_csv_header_token(std::vector<std::string> &tokens,
+					  int token_number,
+					  const char *token_expected,
+					  const char *csv_file_name,
+					  int csv_line_number)
+{
+	if (tokens[token_number] != token_expected) {
+		error_at_line(EX_DATAERR, 1, csv_file_name, csv_line_number,
+			      "Incorrect format: field %d is \"%s\", but we expected \"%s\"",
+			      token_number, tokens[token_number].c_str(), token_expected);
+	}
+}
+
+/*************************************************
+ * Main program
+ *
+ * This is the entry point.
+ *************************************************/
+
+int
+main(int argc, char **argv)
+{
+	cmdline cmdline(argc, argv);
+
+	std::ostream *verbose_stream;
+	if (!cmdline.verbose)
+		verbose_stream = NULL;
+	else if (strcmp(cmdline.dat_file_name, "-") == 0)
+		verbose_stream = &std::cerr;
+	else
+		verbose_stream = &std::cout;
+
+	switch(cmdline.database_type) {
+	case GEOIP_COUNTRY_EDITION:
+	case GEOIP_COUNTRY_EDITION_V6:
+		{
+			country_db_impl country_db(cmdline);
+			country_db.convert_db(verbose_stream);
+			break;
+		}
+
+	case GEOIP_CITY_EDITION_REV1:
+		{
+			city_db_impl city_db(cmdline);
+			city_db.convert_db(verbose_stream);
+			break;
+		}
+	}
+
+	if (verbose_stream) {
+		*verbose_stream << program_invocation_name
+				<< ": All done" << std::endl;
+	}
+
+	return 0;
+}
diff -Nru geoip-1.6.12/debian/src/geolite2-to-legacy-csv.sh geoip-1.6.12/debian/src/geolite2-to-legacy-csv.sh
--- geoip-1.6.12/debian/src/geolite2-to-legacy-csv.sh	1970-01-01 02:00:00.000000000 +0200
+++ geoip-1.6.12/debian/src/geolite2-to-legacy-csv.sh	2026-07-03 21:05:13.000000000 +0300
@@ -0,0 +1,99 @@
+#!/usr/bin/perl -w
+use strict;
+use diagnostics;
+use NetAddr::IP;
+use Getopt::Long;
+
+my $quiet = 0;
+GetOptions(
+    'quiet' => \$quiet,
+    ) or die("bad args");
+
+unless(-s "$ARGV[0]"){
+	print STDERR "Specify Country DB to use on the command line.\n";
+	exit 1;
+}
+
+# Prime country data with additional continent codes
+# http://download.geonames.org/export/dump/readme.txt
+my $countryinfo;
+$countryinfo->{'6255146'}->{'code'} = 'AF';
+$countryinfo->{'6255146'}->{'name'} = 'Africa';
+$countryinfo->{'6255147'}->{'code'} = 'AS';
+$countryinfo->{'6255147'}->{'name'} = 'Asia';
+$countryinfo->{'6255148'}->{'code'} = 'EU';
+$countryinfo->{'6255148'}->{'name'} = 'Europe';
+$countryinfo->{'6255149'}->{'code'} = 'NA';
+$countryinfo->{'6255149'}->{'name'} = 'North America';
+$countryinfo->{'6255150'}->{'code'} = 'SA';
+$countryinfo->{'6255150'}->{'name'} = 'South America';
+$countryinfo->{'6255151'}->{'code'} = 'OC';
+$countryinfo->{'6255151'}->{'name'} = 'Oceania';
+$countryinfo->{'6255152'}->{'code'} = 'AN';
+$countryinfo->{'6255152'}->{'name'} = 'Antarctica';
+
+# Read the countryinfo file
+open my $fh_in, "<", "$ARGV[0]" or die "Can't open $ARGV[0]: $!\n";
+foreach my $line (<$fh_in>){
+	chomp $line;
+	next if ($line =~ /^#/);
+	my @fields = (split "\t", $line);
+	my $code = $fields[0];
+	my $name = $fields[4];
+	my $id   = $fields[16];
+	$countryinfo->{$id}->{'code'} = $code;
+	$countryinfo->{$id}->{'name'} = $name;
+}
+close $fh_in;
+
+# Convert actual GeoLite2 data from STDIN
+my $counter;
+foreach my $line (<STDIN>){
+	next unless ($line =~ /^\d/);
+	chomp $line;
+	$counter++;
+	my @fields = (split ",", $line);
+	my $network = $fields[0];
+	my $geoname_id = $fields[1];
+	my $registered_country_geoname_id = $fields[2];
+	my $represented_country_geoname_id = $fields[3];
+	my $is_anonymous_proxy = $fields[4];
+	my $is_satellite_provider = $fields[5];
+	my $ip = NetAddr::IP->new($network);
+	my $start_ip = $ip->canon();
+	my $end_ip = $ip->broadcast();
+	my $start_int = $ip->bigint();
+	my $end_int = $end_ip->bigint();
+	my $code;
+	my $name;
+	if ($is_anonymous_proxy){
+		$code = "A1";
+		$name = "Anonymous Proxy";
+	}elsif ($is_satellite_provider){
+		$code = "A2";
+		$name = "Satellite Provider";
+	}elsif($countryinfo->{$geoname_id}){
+		$code = $countryinfo->{$geoname_id}->{'code'};
+		$name = $countryinfo->{$geoname_id}->{'name'};
+	}elsif($countryinfo->{$represented_country_geoname_id}){
+		$code = $countryinfo->{$represented_country_geoname_id}->{'code'};
+		$name = $countryinfo->{$represented_country_geoname_id}->{'name'};
+	}elsif($countryinfo->{$registered_country_geoname_id}){
+		$code = $countryinfo->{$registered_country_geoname_id}->{'code'};
+		$name = $countryinfo->{$registered_country_geoname_id}->{'name'};
+	}else{
+		print STDERR "Unknown Geoname ID, panicking. This is a bug.\n";
+		print STDERR "ID: $geoname_id\n";
+		print STDERR "ID Registered: $registered_country_geoname_id\n";
+		print STDERR "ID Represented $represented_country_geoname_id\n";
+		exit 1;
+	}
+
+	# Legacy GeoIP listing format:
+	# "1.0.0.0","1.0.0.255","16777216","16777471","AU","Australia"
+	printf "\"%s\",\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"\n", 
+		$start_ip, $end_ip->canon(), $start_int, $end_int, $code, $name;
+	if (!$quiet && $counter % 10000 == 0) {
+		print STDERR "$counter\n";
+	}
+}
diff -Nru geoip-1.6.12/debian/src/v4-to-v6-layout.pl geoip-1.6.12/debian/src/v4-to-v6-layout.pl
--- geoip-1.6.12/debian/src/v4-to-v6-layout.pl	1970-01-01 02:00:00.000000000 +0200
+++ geoip-1.6.12/debian/src/v4-to-v6-layout.pl	2026-07-03 21:05:13.000000000 +0300
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+# Script from Boris Zentner (Maxmind)
+# This script converts the IPv4 csv database to the same
+# format as the IPv6 database.
+
+sub _x {
+	sprintf("%02x%02x:%02x%02x", split(/\./, $_[0]));
+}
+
+while(<STDIN>){
+	chomp;
+	my($f, $t, $tail) = split /,/, $_, 3;
+
+	for($t, $f){
+		s/^"//;
+		s/"$//;
+	}
+	my $xxxxf = _x($f);
+	my $xxxxt = _x($t);
+	print <<__OUT__;
+"::$f", "::$t", $tail
+"::ffff:$f", "::ffff:$t", $tail
+"2002:${xxxxf}::", "2002:${xxxxt}:ffff:ffff:ffff:ffff:ffff", $tail
+__OUT__
+}
+
+exit(0);

--- End Message ---
--- Begin Message ---
Source: geoip
Source-Version: 1.6.12-12
Done: Patrick Matthäi <[email protected]>

We believe that the bug you reported is fixed in the latest version of
geoip, which is due to be installed in the Debian FTP archive.

A summary of the changes between this version and the previous one is
attached.

Thank you for reporting the bug, which will now be closed.  If you
have further comments please address them to [email protected],
and the maintainer will reopen the bug report if appropriate.

Debian distribution maintenance software
pp.
Patrick Matthäi <[email protected]> (supplier of updated geoip package)

(This message was generated automatically at their request; if you
believe that there is a problem with it please contact the archive
administrators by mailing [email protected])


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Format: 1.8
Date: Mon, 27 Jul 2026 16:05:35 +0200
Source: geoip
Architecture: source
Version: 1.6.12-12
Distribution: unstable
Urgency: medium
Maintainer: Patrick Matthäi <[email protected]>
Changed-By: Patrick Matthäi <[email protected]>
Closes: 1141384
Changes:
 geoip (1.6.12-12) unstable; urgency=medium
 .
   * Acknowledge NMU from Steve Langasek.
   * Acknowledge NMU from Adrian Bunk.
     Closes: #1141384
   * Bump Standards-Version to 4.7.3 (Remove priority field).
   * Remove Rules-Requires-Root control field.
   * Wrap build dependencies.
   * Downgrade geoip-database from recommends to suggests.
   * Update debian/watch to version 5.
   * Update debian/copyright years.
   * Remove unused lintian overrides.
Checksums-Sha1:
 c5d89f980c396f406c3a62979cd3a7e548efa66f 1869 geoip_1.6.12-12.dsc
 90cfafee605c94cfa24858fe77193fa7fa949ac8 160826 geoip_1.6.12.orig.tar.gz
 a0560e1bdbf3de591454c9b54e592abe95a162e1 38100 geoip_1.6.12-12.debian.tar.xz
 df9ee99bac9c1814c9bf2f422818be4d726adc07 6123 geoip_1.6.12-12_source.buildinfo
Checksums-Sha256:
 a28e8e3614187c37d016496bf790dbbd57f6e89317555097ff8328761602b724 1869 
geoip_1.6.12-12.dsc
 99b119f8e21e94f1dfd6d49fbeed29a70df1544896e76cd456f25e397b07d476 160826 
geoip_1.6.12.orig.tar.gz
 51d50d5b7b037d5b5d3a30c00da7c7a1db7dd762afd25c3382d66d2fb48dc058 38100 
geoip_1.6.12-12.debian.tar.xz
 3c288a04f398747b60ab3f6e037c56eceef614cf53a49c0187951b0a59e59aa9 6123 
geoip_1.6.12-12_source.buildinfo
Files:
 49d5d82acdee73e4a6bb15db8cd9b8c5 1869 net optional geoip_1.6.12-12.dsc
 727e5d6df9e9fc039dbc3a323cc56d2e 160826 net optional geoip_1.6.12.orig.tar.gz
 d5db01696fb350928eb11294ae092d16 38100 net optional 
geoip_1.6.12-12.debian.tar.xz
 08961d54a40c7a5fcd38ff698657e372 6123 net optional 
geoip_1.6.12-12_source.buildinfo

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEWKA9xYJCWk3IuQ4TEtmwSpDL2OQFAmpnaLAACgkQEtmwSpDL
2ORMeQ/9GO+4Hlm41nn3exeMxTtgsc5D8QlMLt29EXwwMPh2ghSyC6kHqC4DkcpS
nVZrW37jroJHUVASvQwsGiCbg+LgUnVmXtzU2Pj9aZwdX1JeboRWinbGZK0uyYxX
WVfawt85rRI+MczisPy0xdTIrxE13S7GCcMr169VP/tZsFeaklL93QRZ96pTy7b7
4StQ02N47BO1MVPgHzoniUeX+jAdFcUclG+Fl8udsuG44zDV5xDtV9ZzX31X4+gf
7TcT31lM65OqVe2iDtbOI05Hhr4oRF9FoqlA7G+ajxGPHy+h3DK3p4Ceqr8EizE+
6PnJGOsE3j32w/+6WdaI/F19zq3z0J8gjzE2zz3vvGvatQKDyKwfd5oZ/0+SjLcu
809ferPdnD5LZe3wb2T52VJ5MnGYInF+Wrf5HD2dYkaH6c7esWpPsPlE5tObK9cj
TbwRN2SWEQjuIxqMqlKsQCzB6CnhVKmgh2ldMsDWwSaqvsLL4PeB3iR74P95cdOw
SeuAwKPN9UTUtrIQa3v7Ne7Jqc8x7XHPi5wZINoHaCS2L1qSsT7zsSJxBHU9cTSt
NOo9UFZKctiHzjbqohC1Sg988ZD2qh/cTx0wkjwhQn25OonuzG8j2suGa7pglVkp
oMW8xS37D5AU8Y3d4cz8VBP7/xqGnaBZzH4KoemJX8ASR3mpyn8=
=4z6Q
-----END PGP SIGNATURE-----

Attachment: pgp7VB76Ou0qL.pgp
Description: PGP signature


--- End Message ---

Reply via email to