diff --git a/Dockerfile b/Dockerfile index aafbd47..9d2991b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM debian:bookworm-slim +# Start with debian:bookworm image with scot4 perl installed +FROM ghcr.io/sandialabs/scot4-perl-builder@sha256:6a92390d96baf3c1ad73fcdf9af5047a36e880c5ce026c91cff98d0064e2e67f WORKDIR /opt/flair @@ -11,6 +12,7 @@ COPY . /opt/flair RUN groupadd -g 7777 flair && \ useradd -c "Flair User" -g "flair" -u 7777 -d /opt/flair -M -s /bin/bash flair && \ chown -R flair:flair /opt/flair && \ - chown -R flair:flair /var/log/flair - + chown -R flair:flair /var/log/flair && \ + chmod +x /opt/flair/setup.pl + USER flair diff --git a/LICENSE b/LICENSE index 261eeb9..85bb47d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,23 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Copyright (2024) Sandia Corporation. Under the terms of Contract DE-AC04-94AL85000, there is a non-exclusive license for use of this work by or on behalf of the U.S. Government. Export of this program may require a license from the United States Government. + +NOTICE: - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +For five (5) years from 09/01/2024, the United States Government is granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable worldwide license in this data to reproduce, prepare derivative works, and perform publicly and display publicly, by or on behalf of the Government. There is provision for the possible extension of the term of this license. Subsequent to that period or any extension granted, the United States Government is granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable worldwide license in this data to reproduce, prepare derivative works, distribute copies to the public, perform publicly and display publicly, and to permit others to do so. The specific term of the license can be identified by inquiry made to Sandia Corporation or DOE. + +NEITHER THE UNITED STATES GOVERNMENT, NOR THE UNITED STATES DEPARTMENT OF ENERGY, NOR SANDIA CORPORATION, NOR ANY OF THEIR EMPLOYEES, MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY LEGAL RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR USEFULNESS OF ANY INFORMATION, APPARATUS, PRODUCT, OR PROCESS DISCLOSED, OR REPRESENTS THAT ITS USE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS. + +Any licensee of this software has the obligation and responsibility to abide by the applicable export control laws, regulations, and general prohibitions relating to the export of technical data. Failure to obtain an export control license or other authority from the Government may result in criminal liability under U.S. laws. - 1. Definitions. +Copyright [2024] Sandia Corporation. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. + http://www.apache.org/licenses/LICENSE-2.0 - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 4aefed7..c223d05 100644 --- a/README.md +++ b/README.md @@ -1 +1,13 @@ -# scot4-flair \ No newline at end of file +# Flair + +Entity extraction and highlighting Engine for SCOT + +## Documentation + +https://sandialabs.github.io/scot4-docs/index.html + +## User Interface + +Access the flair user interface through a working scot instance by the URL: https://{instance_fqdn}/flair-ui + + diff --git a/bin/start_minions.sh b/bin/start_minions.sh new file mode 100755 index 0000000..48eb4d9 --- /dev/null +++ b/bin/start_minions.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +FLAIRPATH=/opt/flair +SCRIPT=$FLAIRPATH/script/Flair + +# process 10 minion tasks in parallel +$SCRIPT minion worker -j 10 diff --git a/docs/DBIx.Setup.md b/docs/DBIx.Setup.md new file mode 100644 index 0000000..1b1f7f2 --- /dev/null +++ b/docs/DBIx.Setup.md @@ -0,0 +1,6 @@ +DBIx Class Migration Tutorial Links +----------------------------------- + +* https://metacpan.org/release/JJNAPIORK/DBIx-Class-Migration-0.075/view/lib/DBIx/Class/Migration/Tutorial/Setup.pod + + diff --git a/docs/required_pkgs.txt b/docs/required_pkgs.txt new file mode 100644 index 0000000..a0507e3 --- /dev/null +++ b/docs/required_pkgs.txt @@ -0,0 +1,3 @@ +apt install libz-dev +apt install libssl-dev +apt install librabbitmq4 librabbitmq-dev diff --git a/etc/core_regexes.pl b/etc/core_regexes.pl new file mode 100644 index 0000000..3b5cf08 --- /dev/null +++ b/etc/core_regexes.pl @@ -0,0 +1,421 @@ +{ regexes => [ + { + name => 'snumber', + description => 'Find Sandia SNumbers', + match => q{ + \b + ([sS][0-9]{6,7}) + \b + }, + entity_type => 'snumber', + regex_type => 'core', + re_order => 100, + multiword => 0, + active => 1, + }, + { + name => 'cve', + description => 'Find CVE-YYYYMMDD references', + match => q{ + \b + (CVE-(\d{4})-(\d{4,})) + \b + }, + entity_type => 'cve', + regex_type => 'core', + re_order => 10, + multiword => 0, + active => 1, + }, + { + name => 'cidr', + description => 'Find CIDR blocks', + match => q{ + \b # word boundary + (? 'cidr', + regex_type => 'core', + re_order => 20, + multiword => 0, + active => 1, + }, + { + name => 'ipv6', + description => 'Find IPv6 adresses', + match => q{ + # first look for a suricata/snort format (ip:port) + (?: + # look for aaaa:bbbb:cccc:dddd:eeee:ffff:gggg:hhhh + (?: + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + ) + # look for but dont capture a trailing :\d+ + (?=:[0-9]+) + ) + # next try the rest of the crazy that is ipv6 + # thanks to autors of + # https://learning.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s17.html + | + (?: + # Mixed + (?: + # Non-compressed + (?:[A-F0-9]{1,4}:){6} + # Compressed with at most 6 colons + |(?=(?:[A-F0-9]{0,4}:){0,6} + (?:[0-9]{1,3}\.){3}[0-9]{1,3} # and 4 bytes + (?![:.\w]) + ) + # and at most 1 double colon + (([0-9A-F]{1,4}:){0,5}|:)((:[0-9A-F]{1,4}){1,5}:|:) + # Compressed with 7 colons and 5 numbers + |::(?:[A-F0-9]{1,4}:){5} + ) + # 255.255.255. + (?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3} + # 255 + (?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]) + | + # Standard + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + | + # Compressed with at most 7 colons + (?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4} + (?![:\w]) + ) # and anchored + # and at most 1 double colon + (([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:) + | + # Compressed with 8 colons + (?:[A-F0-9]{1,4}:){7}:|:(:[A-F0-9]{1,4}){7} + ) (?![:\w]) # neg lookahead to "anchor" + }, + entity_type => 'ipv6', + regex_type => 'core', + re_order => 30, + multiword => 1, + active => 1, + }, + { + name => 'ipv6_suricata', + description => 'Find IPv6 adresses from Suricata data', + match => q{ + \b + (?: + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + )(?=:[0-9]+) + \b + }, + entity_type => 'ipv6', + regex_type => 'core', + re_order => 40, + multiword => 0, + active => 1, + }, + { + name => 'ipv4', + description => 'Find IPv4 addresses', + match => q{ + \b # word boundary + (? 'ipaddr', + regex_type => 'core', + re_order => 50, + multiword => 0, + active => 1, + }, + { + name => 'uuid1', + description => 'Find UUID1s', + match => q{ + [0-9a-f]{8} + \- + [0-9a-f]{4} + \- + 11[ef][0-9a-f] + \- + [89ab][0-9a-f]{3} + \- + [0-9a-f]{12} + }, + entity_type => 'uuid1', + regex_type => 'core', + re_order => 60, + multiword => 0, + active => 1, + }, + { + name => 'clsid', + description => 'Find CLSIDs', + match => q{ + [a-fA-F0-9]{8} + \- + [a-fA-F0-9]{4} + \- + [a-fA-F0-9]{4} + \- + [a-fA-F0-9]{4} + \- + [a-fA-F0-9]{12} + }, + entity_type => 'clsid', + regex_type => 'core', + re_order => 70, + multiword => 0, + active => 1, + }, + { + name => 'md5', + description => 'Find hex MD5 hashes', + match => q{ + \b + (?!.*\@\b)([0-9a-fA-F]{32}) + \b + }, + entity_type => 'md5', + regex_type => 'core', + re_order => 80, + multiword => 0, + active => 1, + }, + { + name => 'sha1', + description => 'Find hex SHA1 hashes', + match => q{ + \b + (?!.*\@\b)([0-9a-fA-F]{40}) + \b + }, + entity_type => 'sha1', + regex_type => 'core', + re_order => 90, + multiword => 0, + active => 1, + }, + { + name => 'sha256', + description => 'Find hex SHA256 hashes', + match => q{ + \b + (?!.*\@\b)([0-9a-fA-F]{64}) + \b + }, + entity_type => 'sha256', + regex_type => 'core', + re_order => 100, + multiword => 0, + active => 1, + }, + { + name => 'message_id', + description => 'Find Email addresses', + match => q{ + (<|<)? # optionally starts with < or < + (?:[^\s]*?) # some nonblank chars + @ # an @ seperator + (?:[^\s]*?) # some nonblank chars + (>|>)? # optionally ends with > or > + }, + entity_type => 'message_id', + regex_type => 'core', + re_order => 111, # must run after email + multiword => 1, + active => 1, + }, + { + name => 'email', + description => 'Find Email addresses', + match => q{ + \b # word boundary + ( + (?: + # one or more of these + [\=a-z0-9!#$%&'*+/?^_`{|}~-]+ + # zero or more of these + (?:\.[\=a-z0-9!#$%&'*+/?^_`{|}~-]+)* + ) + @ + (?: + (?!\d+\.\d+) + (?=.{4,255}) + (?: + (?:[a-zA-Z0-9-]{1,63}(? 'email', + regex_type => 'core', + re_order => 110, + multiword => 0, + active => 1, + }, + { + name => 'lbsig', + description => 'Find LaikaBoss Signatures', + match => q{ + \b # word boundary + (yr:[a-z\_]+_s[0-9]+)_[0-9]+ + \b + }, + entity_type => 'lbsig', + regex_type => 'core', + re_order => 120, + multiword => 0, + active => 1, + }, + { + name => 'winregistry', + description => 'Find Windows Registry Keys', + match => q{ + \b # word boundary + ( + (hklm|hkcu|hkey)[\\\w]+ + ) + \b + }, + entity_type => 'winregistry', + regex_type => 'core', + re_order => 130, + multiword => 0, + active => 1, + }, + { + name => 'common_file_extensions', + description => 'Find Filenames with common extensions', + match => q{ + \b( + [0-9a-zA-Z_\-\.]+ + \. + ( + 7z|arg|deb|pkg|rar|rpm|tar|tgz|gz|z|zip| # compressed + aif|mid|midi|mp3|ogg|wav|wma| # audio + bin|dmg|iso|exe|bat| # executables + csv|dat|log|mdb|sql|xml| # db/data + eml|ost|oft|pst|vcf| # email + apk|bat|bin|cgi|exe|jar| # executable + fnt|fon|otf|ttf| # fonts + ai|bmp|gif|ico|jpeg|jpg|ps|png|psd|svg|tif|tiff| # images + asp|aspx|cer|cfm|css|htm|html|js|jsp|part|php|rss|xhtml| # web serving + key|odp|pps|ppt|pptx| # presentation + c|class|cpp|h|vb|swift|py|rb| # source code + ods|xls|xlsm|xlsx| #spreadsheats + cab|cfg|cpl|dll|ini|lnk|msi|sys| # misc sys files + 3g2|3gp|avi|flv|h264|m4v|mkv|mov|mp4|mpg|mpeg|vob|wmv| # video + doc|docx|odt|pdf|rtf|tex|txt|wpd| # word processing + jse|jar| + ipt| + hta| + mht| + ps1| + sct| + scr| + vbe|vbs| + wsf|wsh|wsc + ) + )\b + }, + entity_type => 'file', + regex_type => 'core', + re_order => 150, + multiword => 0, + active => 1, + }, + { + name => 'Domain_name', + description => 'Find Domain Names', + match => q{ + \b + ( + ( + (?= [a-z0-9-]{1,63} [\(\{\[]* \. [\]\}\)]*) + (xn--)? + [a-z0-9]+ + (-[a-z0-9]+)* + [\(\{\[]* + \. + [\]\}\)]* + )+ + ( + [a-z0-9-]{2,63} + ) + (?<=[a-z]) # prevent foo.12 from being a match + ) + \b + }, + entity_type => 'domain', + regex_type => 'core', + re_order => 160, + multiword => 0, + active => 1, + }, + { + name => 'Appkey', + description => 'Find Appkeys', + match => q{ + \b + ( + AppKey=([0-9a-f]){28} + ) + \b + }, + entity_type => 'appkey', + regex_type => 'core', + re_order => 170, + multiword => 0, + active => 1, + }, + { + name => 'Angle Bracket MessageId', + description => 'Find MessageIds wrapped in <>', + match => q{ + ( + (<|<) # starts with < + (?:[^\s]*?) # has some non blank chars + @ # followed by an @ + (?:[^\s]*?) # followed by more not blank chars + (>|>) # ends with > + ) + }, + entity_type => '', + regex_type => 'core', + re_order => 180, + multiword => 0, + active => 1, + }, + { + name => 'jarm_hash', + description => 'Find JARM hashes', + match => q{ + \b + (?!.*\@\b) + ([0-9a-fA-F]{62}) + \b + }, + entity_type => 'jarm_hash', + regex_type => 'core', + re_order => 190, + multiword => 0, + active => 1, + }, +]}; diff --git a/etc/flair.service b/etc/flair.service new file mode 100644 index 0000000..6ec9120 --- /dev/null +++ b/etc/flair.service @@ -0,0 +1,16 @@ +[Unit] +Description=SCOT FLAIR Engine + +[Service] +Type=simple +RemainAfterExit=yes +SyslogIdentifier=FLAIR +PIDFile=/var/run/flair.pid +ExecStart=/opt/perl/bin/hypnotoad /opt/flair/script/Flair +ExecReload=/opt/perl/bin/hypnotoad /opt/flair/script/Flair +ExecStop=/opt/perl/bin/hypnotoad -s /opt/flair/script/Flair +StandardOutput=syslog +StandardError=syslog + +[Install] +WantedBy=multi-user.target diff --git a/etc/flair.sqlite.sql b/etc/flair.sqlite.sql new file mode 100644 index 0000000..07cffcd --- /dev/null +++ b/etc/flair.sqlite.sql @@ -0,0 +1,123 @@ +-- 1 up + +CREATE TABLE IF NOT EXISTS metrics ( + metric_id INTEGER PRIMARY KEY AUTOINCREMENT, + year INT, + month INT, + day INT, + hour INT, + metric TEXT NOT NULL, + value NUMERIC (6,2) +); + +-- +-- regex stores the regular expresion used by flair engine +-- + +CREATE TABLE IF NOT EXISTS regex ( + regex_id INTEGER PRIMARY KEY AUTOINCREMENT, + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + active BOOLEAN default true, + name TEXT NOT NULL, + description TEXT NOT NULL, + match TEXT NOT NULL, + entity_type TEXT NOT NULL, + regex_type TEXT CHECK(regex_type in ('core', 'udef')) NOT NULL, + re_order INT, + multiword BOOLEAN +); + +CREATE TRIGGER [update_regex_updated] + AFTER UPDATE ON regex FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE regex SET updated=CURRENT_TIMESTAMP WHERE regex_id=NEW.regex_id; +END; + + +-- +-- The Files table keeps track of the files created by imgmunger +-- + +CREATE TABLE IF NOT EXISTS files ( + file_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + filename TEXT NOT NULL, + dir TEXT NOT NULL +); + +CREATE TRIGGER [update_files_updated] + AFTER UPDATE ON files FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE files SET updated=CURRENT_TIMESTAMP WHERE file_id=NEW.file_id; +END; + + +-- +-- keep track of apikeys, how to access the api +-- +CREATE TABLE IF NOT EXISTS apikeys ( + apikey_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + username TEXT NOT NULL, + apikey TEXT NOT NULL, + lastaccess TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + flairjob BOOLEAN, -- create a flair job and query and read results + regex_ro BOOLEAN, -- read only regexes + regex_crud BOOLEAN, -- full control regexes + metrics BOOLEAN -- read metrics +); + +CREATE TRIGGER [update_apikeys_updated] + AFTER UPDATE ON apikeys FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE apikeys SET updated=CURRENT_TIMESTAMP WHERE apikey_id=NEW.apikey_id; +END; + + +-- +-- admins can access the web interface and api +-- +CREATE TABLE IF NOT EXISTS admins ( + admin_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + username TEXT NOT NULL, + who TEXT NOT NULL, + lastlogin TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + lastaccess TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + pwhash TEXT NOT NULL +); + +CREATE TRIGGER [update_admins_updated] + AFTER UPDATE ON admins FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE admins SET updated=CURRENT_TIMESTAMP WHERE admin_id=NEW.admin_id; +END; + +CREATE TABLE IF NOT EXISTS jobs ( + job_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + duration DECIMAL(6,4) DEFAULT 0, + imgduration DECIMAL(6,4) DEFAULT 0, + sourcelen INT DEFAULT 0, + images INT DEFAULT 0, + entities INT DEFAULT 0 +); + +CREATE TRIGGER [update_jobs_updated] + AFTER UPDATE ON jobs FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE jobs SET updated=CURRENT_TIMESTAMP WHERE job_id=NEW.job_id; +END; +--1 down +DROP TABLE IF EXISTS regex; +DROP TABLE IF EXISTS status; +DROP TABLE IF EXISTS metrics; +DROP TABLE IF EXISTS files; +DROP TABLE IF EXISTS apikeys; +DROP TABLE IF EXISTS admins; diff --git a/etc/minion.service b/etc/minion.service new file mode 100644 index 0000000..52e8dd9 --- /dev/null +++ b/etc/minion.service @@ -0,0 +1,15 @@ +[Unit] +Description=Flair Application Workers (minions) +After=mysql.service + +[Service] +Type=simple +ExecStart=/opt/flair/script/Flair minion worker -m production -j 8 +SyslogIdentifier=FlairMinion +StandardOutput=syslog +StandardError=syslog +KillMode=process + + +[Install] +WantedBy=multi-user.target diff --git a/etc/public_suffix_list.dat b/etc/public_suffix_list.dat new file mode 100644 index 0000000..73286ad --- /dev/null +++ b/etc/public_suffix_list.dat @@ -0,0 +1,15734 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Please pull this list from, and only from https://publicsuffix.org/list/public_suffix_list.dat, +// rather than any other VCS sites. Pulling from any other URL is not guaranteed to be supported. + +// Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. + +// ===BEGIN ICANN DOMAINS=== + +// ac : http://nic.ac/rules.htm +ac +com.ac +edu.ac +gov.ac +net.ac +mil.ac +org.ac + +// ad : https://www.iana.org/domains/root/db/ad.html +ad +nom.ad + +// ae : https://tdra.gov.ae/en/aeda/ae-policies +ae +co.ae +net.ae +org.ae +sch.ae +ac.ae +gov.ae +mil.ae + +// aero : https://information.aero/registration/policies/dmp +aero +// 2LDs +airline.aero +airport.aero +// 2LDs (currently not accepting registration, seemingly never have) +// As of 2024-07, these are marked as reserved for potential 3LD +// registrations (clause 11 "allocated subdomains" in the 2006 TLD +// policy), but the relevant industry partners have not opened them up +// for registration. Current status can be determined from the TLD's +// policy document: 2LDs that are open for registration must list +// their policy in the TLD's policy. Any 2LD without such a policy is +// not open for registrations. +accident-investigation.aero +accident-prevention.aero +aerobatic.aero +aeroclub.aero +aerodrome.aero +agents.aero +air-surveillance.aero +air-traffic-control.aero +aircraft.aero +airtraffic.aero +ambulance.aero +association.aero +author.aero +ballooning.aero +broker.aero +caa.aero +cargo.aero +catering.aero +certification.aero +championship.aero +charter.aero +civilaviation.aero +club.aero +conference.aero +consultant.aero +consulting.aero +control.aero +council.aero +crew.aero +design.aero +dgca.aero +educator.aero +emergency.aero +engine.aero +engineer.aero +entertainment.aero +equipment.aero +exchange.aero +express.aero +federation.aero +flight.aero +freight.aero +fuel.aero +gliding.aero +government.aero +groundhandling.aero +group.aero +hanggliding.aero +homebuilt.aero +insurance.aero +journal.aero +journalist.aero +leasing.aero +logistics.aero +magazine.aero +maintenance.aero +marketplace.aero +media.aero +microlight.aero +modelling.aero +navigation.aero +parachuting.aero +paragliding.aero +passenger-association.aero +pilot.aero +press.aero +production.aero +recreation.aero +repbody.aero +res.aero +research.aero +rotorcraft.aero +safety.aero +scientist.aero +services.aero +show.aero +skydiving.aero +software.aero +student.aero +taxi.aero +trader.aero +trading.aero +trainer.aero +union.aero +workinggroup.aero +works.aero + +// af : http://www.nic.af/help.jsp +af +gov.af +com.af +org.af +net.af +edu.af + +// ag : http://www.nic.ag/prices.htm +ag +com.ag +org.ag +net.ag +co.ag +nom.ag + +// ai : http://nic.com.ai/ +ai +off.ai +com.ai +net.ai +org.ai + +// al : http://www.ert.gov.al/ert_alb/faq_det.html?Id=31 +al +com.al +edu.al +gov.al +mil.al +net.al +org.al + +// am : https://www.amnic.net/policy/en/Policy_EN.pdf +am +co.am +com.am +commune.am +net.am +org.am + +// ao : https://www.iana.org/domains/root/db/ao.html +// http://www.dns.ao/REGISTR.DOC +ao +ed.ao +edu.ao +gov.ao +gv.ao +og.ao +org.ao +co.ao +pb.ao +it.ao + +// aq : https://www.iana.org/domains/root/db/aq.html +aq + +// ar : https://nic.ar/es/nic-argentina/normativa +ar +bet.ar +com.ar +coop.ar +edu.ar +gob.ar +gov.ar +int.ar +mil.ar +musica.ar +mutual.ar +net.ar +org.ar +senasa.ar +tur.ar + +// arpa : https://www.iana.org/domains/root/db/arpa.html +// Confirmed by registry 2008-06-18 +arpa +e164.arpa +in-addr.arpa +ip6.arpa +iris.arpa +uri.arpa +urn.arpa + +// as : https://www.iana.org/domains/root/db/as.html +as +gov.as + +// asia : https://www.iana.org/domains/root/db/asia.html +asia + +// at : https://www.iana.org/domains/root/db/at.html +// Confirmed by registry 2008-06-17 +at +ac.at +co.at +gv.at +or.at +sth.ac.at + +// au : https://www.iana.org/domains/root/db/au.html +// http://www.auda.org.au/ +au +// 2LDs +com.au +net.au +org.au +edu.au +gov.au +asn.au +id.au +// Historic 2LDs (closed to new registration, but sites still exist) +info.au +conf.au +oz.au +// CGDNs - http://www.cgdn.org.au/ +act.au +nsw.au +nt.au +qld.au +sa.au +tas.au +vic.au +wa.au +// 3LDs +act.edu.au +catholic.edu.au +// eq.edu.au - Removed at the request of the Queensland Department of Education +nsw.edu.au +nt.edu.au +qld.edu.au +sa.edu.au +tas.edu.au +vic.edu.au +wa.edu.au +// act.gov.au Bug 984824 - Removed at request of Greg Tankard +// nsw.gov.au Bug 547985 - Removed at request of +// nt.gov.au Bug 940478 - Removed at request of Greg Connors +qld.gov.au +sa.gov.au +tas.gov.au +vic.gov.au +wa.gov.au +// 4LDs +// education.tas.edu.au - Removed at the request of the Department of Education Tasmania +schools.nsw.edu.au + +// aw : https://www.iana.org/domains/root/db/aw.html +aw +com.aw + +// ax : https://www.iana.org/domains/root/db/ax.html +ax + +// az : https://www.iana.org/domains/root/db/az.html +az +com.az +net.az +int.az +gov.az +org.az +edu.az +info.az +pp.az +mil.az +name.az +pro.az +biz.az + +// ba : http://nic.ba/users_data/files/pravilnik_o_registraciji.pdf +ba +com.ba +edu.ba +gov.ba +mil.ba +net.ba +org.ba + +// bb : https://www.iana.org/domains/root/db/bb.html +bb +biz.bb +co.bb +com.bb +edu.bb +gov.bb +info.bb +net.bb +org.bb +store.bb +tv.bb + +// bd : https://www.iana.org/domains/root/db/bd.html +*.bd + +// be : https://www.iana.org/domains/root/db/be.html +// Confirmed by registry 2008-06-08 +be +ac.be + +// bf : https://www.iana.org/domains/root/db/bf.html +bf +gov.bf + +// bg : https://www.iana.org/domains/root/db/bg.html +// https://www.register.bg/user/static/rules/en/index.html +bg +a.bg +b.bg +c.bg +d.bg +e.bg +f.bg +g.bg +h.bg +i.bg +j.bg +k.bg +l.bg +m.bg +n.bg +o.bg +p.bg +q.bg +r.bg +s.bg +t.bg +u.bg +v.bg +w.bg +x.bg +y.bg +z.bg +0.bg +1.bg +2.bg +3.bg +4.bg +5.bg +6.bg +7.bg +8.bg +9.bg + +// bh : https://www.iana.org/domains/root/db/bh.html +bh +com.bh +edu.bh +net.bh +org.bh +gov.bh + +// bi : https://www.iana.org/domains/root/db/bi.html +// http://whois.nic.bi/ +bi +co.bi +com.bi +edu.bi +or.bi +org.bi + +// biz : https://www.iana.org/domains/root/db/biz.html +biz + +// bj : https://nic.bj/bj-suffixes.txt +// submitted by registry +bj +africa.bj +agro.bj +architectes.bj +assur.bj +avocats.bj +co.bj +com.bj +eco.bj +econo.bj +edu.bj +info.bj +loisirs.bj +money.bj +net.bj +org.bj +ote.bj +resto.bj +restaurant.bj +tourism.bj +univ.bj + +// bm : http://www.bermudanic.bm/dnr-text.txt +bm +com.bm +edu.bm +gov.bm +net.bm +org.bm + +// bn : http://www.bnnic.bn/faqs +bn +com.bn +edu.bn +gov.bn +net.bn +org.bn + +// bo : https://nic.bo/delegacion2015.php#h-1.10 +bo +com.bo +edu.bo +gob.bo +int.bo +org.bo +net.bo +mil.bo +tv.bo +web.bo +// Social Domains +academia.bo +agro.bo +arte.bo +blog.bo +bolivia.bo +ciencia.bo +cooperativa.bo +democracia.bo +deporte.bo +ecologia.bo +economia.bo +empresa.bo +indigena.bo +industria.bo +info.bo +medicina.bo +movimiento.bo +musica.bo +natural.bo +nombre.bo +noticias.bo +patria.bo +politica.bo +profesional.bo +plurinacional.bo +pueblo.bo +revista.bo +salud.bo +tecnologia.bo +tksat.bo +transporte.bo +wiki.bo + +// br : http://registro.br/dominio/categoria.html +// Submitted by registry +br +9guacu.br +abc.br +adm.br +adv.br +agr.br +aju.br +am.br +anani.br +aparecida.br +app.br +arq.br +art.br +ato.br +b.br +barueri.br +belem.br +bet.br +bhz.br +bib.br +bio.br +blog.br +bmd.br +boavista.br +bsb.br +campinagrande.br +campinas.br +caxias.br +cim.br +cng.br +cnt.br +com.br +contagem.br +coop.br +coz.br +cri.br +cuiaba.br +curitiba.br +def.br +des.br +det.br +dev.br +ecn.br +eco.br +edu.br +emp.br +enf.br +eng.br +esp.br +etc.br +eti.br +far.br +feira.br +flog.br +floripa.br +fm.br +fnd.br +fortal.br +fot.br +foz.br +fst.br +g12.br +geo.br +ggf.br +goiania.br +gov.br +// gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil +ac.gov.br +al.gov.br +am.gov.br +ap.gov.br +ba.gov.br +ce.gov.br +df.gov.br +es.gov.br +go.gov.br +ma.gov.br +mg.gov.br +ms.gov.br +mt.gov.br +pa.gov.br +pb.gov.br +pe.gov.br +pi.gov.br +pr.gov.br +rj.gov.br +rn.gov.br +ro.gov.br +rr.gov.br +rs.gov.br +sc.gov.br +se.gov.br +sp.gov.br +to.gov.br +gru.br +imb.br +ind.br +inf.br +jab.br +jampa.br +jdf.br +joinville.br +jor.br +jus.br +leg.br +leilao.br +lel.br +log.br +londrina.br +macapa.br +maceio.br +manaus.br +maringa.br +mat.br +med.br +mil.br +morena.br +mp.br +mus.br +natal.br +net.br +niteroi.br +*.nom.br +not.br +ntr.br +odo.br +ong.br +org.br +osasco.br +palmas.br +poa.br +ppg.br +pro.br +psc.br +psi.br +pvh.br +qsl.br +radio.br +rec.br +recife.br +rep.br +ribeirao.br +rio.br +riobranco.br +riopreto.br +salvador.br +sampa.br +santamaria.br +santoandre.br +saobernardo.br +saogonca.br +seg.br +sjc.br +slg.br +slz.br +sorocaba.br +srv.br +taxi.br +tc.br +tec.br +teo.br +the.br +tmp.br +trd.br +tur.br +tv.br +udi.br +vet.br +vix.br +vlog.br +wiki.br +zlg.br + +// bs : http://www.nic.bs/rules.html +bs +com.bs +net.bs +org.bs +edu.bs +gov.bs + +// bt : https://www.iana.org/domains/root/db/bt.html +bt +com.bt +edu.bt +gov.bt +net.bt +org.bt + +// bv : No registrations at this time. +// Submitted by registry +bv + +// bw : https://www.iana.org/domains/root/db/bw.html +// http://www.gobin.info/domainname/bw.doc +// list of other 2nd level tlds ? +bw +co.bw +org.bw + +// by : https://www.iana.org/domains/root/db/by.html +// http://tld.by/rules_2006_en.html +// list of other 2nd level tlds ? +by +gov.by +mil.by +// Official information does not indicate that com.by is a reserved +// second-level domain, but it's being used as one (see www.google.com.by and +// www.yahoo.com.by, for example), so we list it here for safety's sake. +com.by +// http://hoster.by/ +of.by + +// bz : https://www.iana.org/domains/root/db/bz.html +// http://www.belizenic.bz/ +bz +com.bz +net.bz +org.bz +edu.bz +gov.bz + +// ca : https://www.iana.org/domains/root/db/ca.html +ca +// ca geographical names +ab.ca +bc.ca +mb.ca +nb.ca +nf.ca +nl.ca +ns.ca +nt.ca +nu.ca +on.ca +pe.ca +qc.ca +sk.ca +yk.ca +// gc.ca: https://en.wikipedia.org/wiki/.gc.ca +// see also: http://registry.gc.ca/en/SubdomainFAQ +gc.ca + +// cat : https://www.iana.org/domains/root/db/cat.html +cat + +// cc : https://www.iana.org/domains/root/db/cc.html +cc + +// cd : https://www.iana.org/domains/root/db/cd.html +// see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1 +cd +gov.cd + +// cf : https://www.iana.org/domains/root/db/cf.html +cf + +// cg : https://www.iana.org/domains/root/db/cg.html +cg + +// ch : https://www.iana.org/domains/root/db/ch.html +ch + +// ci : https://www.iana.org/domains/root/db/ci.html +// http://www.nic.ci/index.php?page=charte +ci +org.ci +or.ci +com.ci +co.ci +edu.ci +ed.ci +ac.ci +net.ci +go.ci +asso.ci +aéroport.ci +int.ci +gouv.ci + +// ck : https://www.iana.org/domains/root/db/ck.html +*.ck +!www.ck + +// cl : https://www.nic.cl +// Confirmed by .CL registry +cl +co.cl +gob.cl +gov.cl +mil.cl + +// cm : https://www.iana.org/domains/root/db/cm.html plus bug 981927 +cm +co.cm +com.cm +gov.cm +net.cm + +// cn : https://www.iana.org/domains/root/db/cn.html +// Submitted by registry +cn +ac.cn +com.cn +edu.cn +gov.cn +net.cn +org.cn +mil.cn +公司.cn +网络.cn +網絡.cn +// cn geographic names +ah.cn +bj.cn +cq.cn +fj.cn +gd.cn +gs.cn +gz.cn +gx.cn +ha.cn +hb.cn +he.cn +hi.cn +hl.cn +hn.cn +jl.cn +js.cn +jx.cn +ln.cn +nm.cn +nx.cn +qh.cn +sc.cn +sd.cn +sh.cn +sn.cn +sx.cn +tj.cn +xj.cn +xz.cn +yn.cn +zj.cn +hk.cn +mo.cn +tw.cn + +// co : https://www.iana.org/domains/root/db/co.html +// Submitted by registry +co +arts.co +com.co +edu.co +firm.co +gov.co +info.co +int.co +mil.co +net.co +nom.co +org.co +rec.co +web.co + +// com : https://www.iana.org/domains/root/db/com.html +com + +// coop : https://www.iana.org/domains/root/db/coop.html +coop + +// cr : http://www.nic.cr/niccr_publico/showRegistroDominiosScreen.do +cr +ac.cr +co.cr +ed.cr +fi.cr +go.cr +or.cr +sa.cr + +// cu : https://www.iana.org/domains/root/db/cu.html +cu +com.cu +edu.cu +gob.cu +inf.cu +nat.cu +net.cu +org.cu + +// cv : https://www.iana.org/domains/root/db/cv.html +// cv : http://www.dns.cv/tldcv_portal/do?com=DS;5446457100;111;+PAGE(4000018)+K-CAT-CODIGO(RDOM)+RCNT(100); <- registration rules +cv +com.cv +edu.cv +int.cv +nome.cv +org.cv + +// cw : http://www.una.cw/cw_registry/ +// Confirmed by registry 2013-03-26 +cw +com.cw +edu.cw +net.cw +org.cw + +// cx : https://www.iana.org/domains/root/db/cx.html +// list of other 2nd level tlds ? +cx +gov.cx + +// cy : http://www.nic.cy/ +// Submitted by registry Panayiotou Fotia +// namespace policies URL https://www.nic.cy/portal//sites/default/files/symfonia_gia_eggrafi.pdf +cy +ac.cy +biz.cy +com.cy +ekloges.cy +gov.cy +ltd.cy +mil.cy +net.cy +org.cy +press.cy +pro.cy +tm.cy + +// cz : https://www.iana.org/domains/root/db/cz.html +cz + +// de : https://www.iana.org/domains/root/db/de.html +// Confirmed by registry (with technical +// reservations) 2008-07-01 +de + +// dj : https://www.iana.org/domains/root/db/dj.html +dj + +// dk : https://www.iana.org/domains/root/db/dk.html +// Confirmed by registry 2008-06-17 +dk + +// dm : https://www.iana.org/domains/root/db/dm.html +dm +com.dm +net.dm +org.dm +edu.dm +gov.dm + +// do : https://www.iana.org/domains/root/db/do.html +do +art.do +com.do +edu.do +gob.do +gov.do +mil.do +net.do +org.do +sld.do +web.do + +// dz : http://www.nic.dz/images/pdf_nic/charte.pdf +dz +art.dz +asso.dz +com.dz +edu.dz +gov.dz +org.dz +net.dz +pol.dz +soc.dz +tm.dz + +// ec : http://www.nic.ec/reg/paso1.asp +// Submitted by registry +ec +com.ec +info.ec +net.ec +fin.ec +k12.ec +med.ec +pro.ec +org.ec +edu.ec +gov.ec +gob.ec +mil.ec + +// edu : https://www.iana.org/domains/root/db/edu.html +edu + +// ee : http://www.eenet.ee/EENet/dom_reeglid.html#lisa_B +ee +edu.ee +gov.ee +riik.ee +lib.ee +med.ee +com.ee +pri.ee +aip.ee +org.ee +fie.ee + +// eg : https://www.iana.org/domains/root/db/eg.html +eg +com.eg +edu.eg +eun.eg +gov.eg +mil.eg +name.eg +net.eg +org.eg +sci.eg + +// er : https://www.iana.org/domains/root/db/er.html +*.er + +// es : https://www.nic.es/site_ingles/ingles/dominios/index.html +es +com.es +nom.es +org.es +gob.es +edu.es + +// et : https://www.iana.org/domains/root/db/et.html +et +com.et +gov.et +org.et +edu.et +biz.et +name.et +info.et +net.et + +// eu : https://www.iana.org/domains/root/db/eu.html +eu + +// fi : https://www.iana.org/domains/root/db/fi.html +fi +// aland.fi : https://www.iana.org/domains/root/db/ax.html +// This domain is being phased out in favor of .ax. As there are still many +// domains under aland.fi, we still keep it on the list until aland.fi is +// completely removed. +aland.fi + +// fj : http://domains.fj/ +// Submitted by registry 2020-02-11 +fj +ac.fj +biz.fj +com.fj +gov.fj +info.fj +mil.fj +name.fj +net.fj +org.fj +pro.fj + +// fk : https://www.iana.org/domains/root/db/fk.html +*.fk + +// fm : https://www.iana.org/domains/root/db/fm.html +com.fm +edu.fm +net.fm +org.fm +fm + +// fo : https://www.iana.org/domains/root/db/fo.html +fo + +// fr : https://www.afnic.fr/ https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +fr +asso.fr +com.fr +gouv.fr +nom.fr +prd.fr +tm.fr +// Other SLDs now selfmanaged out of AFNIC range. Former "domaines sectoriels", still registration suffixes +avoues.fr +cci.fr +greta.fr +huissier-justice.fr + +// ga : https://www.iana.org/domains/root/db/ga.html +ga + +// gb : This registry is effectively dormant +// Submitted by registry +gb + +// gd : https://www.iana.org/domains/root/db/gd.html +edu.gd +gov.gd +gd + +// ge : http://www.nic.net.ge/policy_en.pdf +ge +com.ge +edu.ge +gov.ge +org.ge +mil.ge +net.ge +pvt.ge + +// gf : https://www.iana.org/domains/root/db/gf.html +gf + +// gg : http://www.channelisles.net/register-domains/ +// Confirmed by registry 2013-11-28 +gg +co.gg +net.gg +org.gg + +// gh : https://www.iana.org/domains/root/db/gh.html +// see also: http://www.nic.gh/reg_now.php +// Although domains directly at second level are not possible at the moment, +// they have been possible for some time and may come back. +gh +com.gh +edu.gh +gov.gh +org.gh +mil.gh + +// gi : http://www.nic.gi/rules.html +gi +com.gi +ltd.gi +gov.gi +mod.gi +edu.gi +org.gi + +// gl : https://www.iana.org/domains/root/db/gl.html +// http://nic.gl +gl +co.gl +com.gl +edu.gl +net.gl +org.gl + +// gm : http://www.nic.gm/htmlpages%5Cgm-policy.htm +gm + +// gn : http://psg.com/dns/gn/gn.txt +// Submitted by registry +gn +ac.gn +com.gn +edu.gn +gov.gn +org.gn +net.gn + +// gov : https://www.iana.org/domains/root/db/gov.html +gov + +// gp : http://www.nic.gp/index.php?lang=en +gp +com.gp +net.gp +mobi.gp +edu.gp +org.gp +asso.gp + +// gq : https://www.iana.org/domains/root/db/gq.html +gq + +// gr : https://grweb.ics.forth.gr/english/1617-B-2005.html +// Submitted by registry +gr +com.gr +edu.gr +net.gr +org.gr +gov.gr + +// gs : https://www.iana.org/domains/root/db/gs.html +gs + +// gt : https://www.gt/sitio/registration_policy.php?lang=en +gt +com.gt +edu.gt +gob.gt +ind.gt +mil.gt +net.gt +org.gt + +// gu : http://gadao.gov.gu/register.html +// University of Guam : https://www.uog.edu +// Submitted by uognoc@triton.uog.edu +gu +com.gu +edu.gu +gov.gu +guam.gu +info.gu +net.gu +org.gu +web.gu + +// gw : https://www.iana.org/domains/root/db/gw.html +// gw : https://nic.gw/regras/ +gw + +// gy : https://www.iana.org/domains/root/db/gy.html +// http://registry.gy/ +gy +co.gy +com.gy +edu.gy +gov.gy +net.gy +org.gy + +// hk : https://www.hkirc.hk +// Submitted by registry +hk +com.hk +edu.hk +gov.hk +idv.hk +net.hk +org.hk +公司.hk +教育.hk +敎育.hk +政府.hk +個人.hk +个人.hk +箇人.hk +網络.hk +网络.hk +组織.hk +網絡.hk +网絡.hk +组织.hk +組織.hk +組织.hk + +// hm : https://www.iana.org/domains/root/db/hm.html +hm + +// hn : http://www.nic.hn/politicas/ps02,,05.html +hn +com.hn +edu.hn +org.hn +net.hn +mil.hn +gob.hn + +// hr : http://www.dns.hr/documents/pdf/HRTLD-regulations.pdf +hr +iz.hr +from.hr +name.hr +com.hr + +// ht : http://www.nic.ht/info/charte.cfm +ht +com.ht +shop.ht +firm.ht +info.ht +adult.ht +net.ht +pro.ht +org.ht +med.ht +art.ht +coop.ht +pol.ht +asso.ht +edu.ht +rel.ht +gouv.ht +perso.ht + +// hu : http://www.domain.hu/domain/English/sld.html +// Confirmed by registry 2008-06-12 +hu +co.hu +info.hu +org.hu +priv.hu +sport.hu +tm.hu +2000.hu +agrar.hu +bolt.hu +casino.hu +city.hu +erotica.hu +erotika.hu +film.hu +forum.hu +games.hu +hotel.hu +ingatlan.hu +jogasz.hu +konyvelo.hu +lakas.hu +media.hu +news.hu +reklam.hu +sex.hu +shop.hu +suli.hu +szex.hu +tozsde.hu +utazas.hu +video.hu + +// id : https://pandi.id/en/domain/registration-requirements/ +id +ac.id +biz.id +co.id +desa.id +go.id +mil.id +my.id +net.id +or.id +ponpes.id +sch.id +web.id + +// ie : https://www.iana.org/domains/root/db/ie.html +ie +gov.ie + +// il : http://www.isoc.org.il/domains/ +// see also: https://en.isoc.org.il/il-cctld/registration-rules +// ISOC-IL (operated by .il Registry) +il +ac.il +co.il +gov.il +idf.il +k12.il +muni.il +net.il +org.il +// xn--4dbrk0ce ("Israel", Hebrew) : IL +ישראל +// xn--4dbgdty6c.xn--4dbrk0ce. +אקדמיה.ישראל +// xn--5dbhl8d.xn--4dbrk0ce. +ישוב.ישראל +// xn--8dbq2a.xn--4dbrk0ce. +צהל.ישראל +// xn--hebda8b.xn--4dbrk0ce. +ממשל.ישראל + +// im : https://www.nic.im/ +// Submitted by registry +im +ac.im +co.im +com.im +ltd.co.im +net.im +org.im +plc.co.im +tt.im +tv.im + +// in : https://www.iana.org/domains/root/db/in.html +// see also: https://registry.in/policies +// Please note, that nic.in is not an official eTLD, but used by most +// government institutions. +in +5g.in +6g.in +ac.in +ai.in +am.in +bihar.in +biz.in +business.in +ca.in +cn.in +co.in +com.in +coop.in +cs.in +delhi.in +dr.in +edu.in +er.in +firm.in +gen.in +gov.in +gujarat.in +ind.in +info.in +int.in +internet.in +io.in +me.in +mil.in +net.in +nic.in +org.in +pg.in +post.in +pro.in +res.in +travel.in +tv.in +uk.in +up.in +us.in + +// info : https://www.iana.org/domains/root/db/info.html +info + +// int : https://www.iana.org/domains/root/db/int.html +// Confirmed by registry 2008-06-18 +int +eu.int + +// io : http://www.nic.io/rules.htm +io +co.io +com.io +edu.io +gov.io +mil.io +net.io +nom.io +org.io + +// iq : http://www.cmc.iq/english/iq/iqregister1.htm +iq +gov.iq +edu.iq +mil.iq +com.iq +org.iq +net.iq + +// ir : http://www.nic.ir/Terms_and_Conditions_ir,_Appendix_1_Domain_Rules +// Also see http://www.nic.ir/Internationalized_Domain_Names +// Two .ir entries added at request of , 2010-04-16 +ir +ac.ir +co.ir +gov.ir +id.ir +net.ir +org.ir +sch.ir +// xn--mgba3a4f16a.ir (.ir, Persian YEH) +ایران.ir +// xn--mgba3a4fra.ir (.ir, Arabic YEH) +ايران.ir + +// is : http://www.isnic.is/domain/rules.php +// Confirmed by registry 2008-12-06 +is +net.is +com.is +edu.is +gov.is +org.is +int.is + +// it : https://www.iana.org/domains/root/db/it.html +it +gov.it +edu.it +// Reserved geo-names (regions and provinces): +// https://www.nic.it/sites/default/files/archivio/docs/Regulation_assignation_v7.1.pdf +// Regions +abr.it +abruzzo.it +aosta-valley.it +aostavalley.it +bas.it +basilicata.it +cal.it +calabria.it +cam.it +campania.it +emilia-romagna.it +emiliaromagna.it +emr.it +friuli-v-giulia.it +friuli-ve-giulia.it +friuli-vegiulia.it +friuli-venezia-giulia.it +friuli-veneziagiulia.it +friuli-vgiulia.it +friuliv-giulia.it +friulive-giulia.it +friulivegiulia.it +friulivenezia-giulia.it +friuliveneziagiulia.it +friulivgiulia.it +fvg.it +laz.it +lazio.it +lig.it +liguria.it +lom.it +lombardia.it +lombardy.it +lucania.it +mar.it +marche.it +mol.it +molise.it +piedmont.it +piemonte.it +pmn.it +pug.it +puglia.it +sar.it +sardegna.it +sardinia.it +sic.it +sicilia.it +sicily.it +taa.it +tos.it +toscana.it +trentin-sud-tirol.it +trentin-süd-tirol.it +trentin-sudtirol.it +trentin-südtirol.it +trentin-sued-tirol.it +trentin-suedtirol.it +trentino-a-adige.it +trentino-aadige.it +trentino-alto-adige.it +trentino-altoadige.it +trentino-s-tirol.it +trentino-stirol.it +trentino-sud-tirol.it +trentino-süd-tirol.it +trentino-sudtirol.it +trentino-südtirol.it +trentino-sued-tirol.it +trentino-suedtirol.it +trentino.it +trentinoa-adige.it +trentinoaadige.it +trentinoalto-adige.it +trentinoaltoadige.it +trentinos-tirol.it +trentinostirol.it +trentinosud-tirol.it +trentinosüd-tirol.it +trentinosudtirol.it +trentinosüdtirol.it +trentinosued-tirol.it +trentinosuedtirol.it +trentinsud-tirol.it +trentinsüd-tirol.it +trentinsudtirol.it +trentinsüdtirol.it +trentinsued-tirol.it +trentinsuedtirol.it +tuscany.it +umb.it +umbria.it +val-d-aosta.it +val-daosta.it +vald-aosta.it +valdaosta.it +valle-aosta.it +valle-d-aosta.it +valle-daosta.it +valleaosta.it +valled-aosta.it +valledaosta.it +vallee-aoste.it +vallée-aoste.it +vallee-d-aoste.it +vallée-d-aoste.it +valleeaoste.it +valléeaoste.it +valleedaoste.it +valléedaoste.it +vao.it +vda.it +ven.it +veneto.it +// Provinces +ag.it +agrigento.it +al.it +alessandria.it +alto-adige.it +altoadige.it +an.it +ancona.it +andria-barletta-trani.it +andria-trani-barletta.it +andriabarlettatrani.it +andriatranibarletta.it +ao.it +aosta.it +aoste.it +ap.it +aq.it +aquila.it +ar.it +arezzo.it +ascoli-piceno.it +ascolipiceno.it +asti.it +at.it +av.it +avellino.it +ba.it +balsan-sudtirol.it +balsan-südtirol.it +balsan-suedtirol.it +balsan.it +bari.it +barletta-trani-andria.it +barlettatraniandria.it +belluno.it +benevento.it +bergamo.it +bg.it +bi.it +biella.it +bl.it +bn.it +bo.it +bologna.it +bolzano-altoadige.it +bolzano.it +bozen-sudtirol.it +bozen-südtirol.it +bozen-suedtirol.it +bozen.it +br.it +brescia.it +brindisi.it +bs.it +bt.it +bulsan-sudtirol.it +bulsan-südtirol.it +bulsan-suedtirol.it +bulsan.it +bz.it +ca.it +cagliari.it +caltanissetta.it +campidano-medio.it +campidanomedio.it +campobasso.it +carbonia-iglesias.it +carboniaiglesias.it +carrara-massa.it +carraramassa.it +caserta.it +catania.it +catanzaro.it +cb.it +ce.it +cesena-forli.it +cesena-forlì.it +cesenaforli.it +cesenaforlì.it +ch.it +chieti.it +ci.it +cl.it +cn.it +co.it +como.it +cosenza.it +cr.it +cremona.it +crotone.it +cs.it +ct.it +cuneo.it +cz.it +dell-ogliastra.it +dellogliastra.it +en.it +enna.it +fc.it +fe.it +fermo.it +ferrara.it +fg.it +fi.it +firenze.it +florence.it +fm.it +foggia.it +forli-cesena.it +forlì-cesena.it +forlicesena.it +forlìcesena.it +fr.it +frosinone.it +ge.it +genoa.it +genova.it +go.it +gorizia.it +gr.it +grosseto.it +iglesias-carbonia.it +iglesiascarbonia.it +im.it +imperia.it +is.it +isernia.it +kr.it +la-spezia.it +laquila.it +laspezia.it +latina.it +lc.it +le.it +lecce.it +lecco.it +li.it +livorno.it +lo.it +lodi.it +lt.it +lu.it +lucca.it +macerata.it +mantova.it +massa-carrara.it +massacarrara.it +matera.it +mb.it +mc.it +me.it +medio-campidano.it +mediocampidano.it +messina.it +mi.it +milan.it +milano.it +mn.it +mo.it +modena.it +monza-brianza.it +monza-e-della-brianza.it +monza.it +monzabrianza.it +monzaebrianza.it +monzaedellabrianza.it +ms.it +mt.it +na.it +naples.it +napoli.it +no.it +novara.it +nu.it +nuoro.it +og.it +ogliastra.it +olbia-tempio.it +olbiatempio.it +or.it +oristano.it +ot.it +pa.it +padova.it +padua.it +palermo.it +parma.it +pavia.it +pc.it +pd.it +pe.it +perugia.it +pesaro-urbino.it +pesarourbino.it +pescara.it +pg.it +pi.it +piacenza.it +pisa.it +pistoia.it +pn.it +po.it +pordenone.it +potenza.it +pr.it +prato.it +pt.it +pu.it +pv.it +pz.it +ra.it +ragusa.it +ravenna.it +rc.it +re.it +reggio-calabria.it +reggio-emilia.it +reggiocalabria.it +reggioemilia.it +rg.it +ri.it +rieti.it +rimini.it +rm.it +rn.it +ro.it +roma.it +rome.it +rovigo.it +sa.it +salerno.it +sassari.it +savona.it +si.it +siena.it +siracusa.it +so.it +sondrio.it +sp.it +sr.it +ss.it +suedtirol.it +südtirol.it +sv.it +ta.it +taranto.it +te.it +tempio-olbia.it +tempioolbia.it +teramo.it +terni.it +tn.it +to.it +torino.it +tp.it +tr.it +trani-andria-barletta.it +trani-barletta-andria.it +traniandriabarletta.it +tranibarlettaandria.it +trapani.it +trento.it +treviso.it +trieste.it +ts.it +turin.it +tv.it +ud.it +udine.it +urbino-pesaro.it +urbinopesaro.it +va.it +varese.it +vb.it +vc.it +ve.it +venezia.it +venice.it +verbania.it +vercelli.it +verona.it +vi.it +vibo-valentia.it +vibovalentia.it +vicenza.it +viterbo.it +vr.it +vs.it +vt.it +vv.it + +// je : http://www.channelisles.net/register-domains/ +// Confirmed by registry 2013-11-28 +je +co.je +net.je +org.je + +// jm : http://www.com.jm/register.html +*.jm + +// jo : http://www.dns.jo/Registration_policy.aspx +jo +com.jo +org.jo +net.jo +edu.jo +sch.jo +gov.jo +mil.jo +name.jo + +// jobs : https://www.iana.org/domains/root/db/jobs.html +jobs + +// jp : https://www.iana.org/domains/root/db/jp.html +// http://jprs.co.jp/en/jpdomain.html +// Submitted by registry +jp +// jp organizational type names +ac.jp +ad.jp +co.jp +ed.jp +go.jp +gr.jp +lg.jp +ne.jp +or.jp +// jp prefecture type names +aichi.jp +akita.jp +aomori.jp +chiba.jp +ehime.jp +fukui.jp +fukuoka.jp +fukushima.jp +gifu.jp +gunma.jp +hiroshima.jp +hokkaido.jp +hyogo.jp +ibaraki.jp +ishikawa.jp +iwate.jp +kagawa.jp +kagoshima.jp +kanagawa.jp +kochi.jp +kumamoto.jp +kyoto.jp +mie.jp +miyagi.jp +miyazaki.jp +nagano.jp +nagasaki.jp +nara.jp +niigata.jp +oita.jp +okayama.jp +okinawa.jp +osaka.jp +saga.jp +saitama.jp +shiga.jp +shimane.jp +shizuoka.jp +tochigi.jp +tokushima.jp +tokyo.jp +tottori.jp +toyama.jp +wakayama.jp +yamagata.jp +yamaguchi.jp +yamanashi.jp +栃木.jp +愛知.jp +愛媛.jp +兵庫.jp +熊本.jp +茨城.jp +北海道.jp +千葉.jp +和歌山.jp +長崎.jp +長野.jp +新潟.jp +青森.jp +静岡.jp +東京.jp +石川.jp +埼玉.jp +三重.jp +京都.jp +佐賀.jp +大分.jp +大阪.jp +奈良.jp +宮城.jp +宮崎.jp +富山.jp +山口.jp +山形.jp +山梨.jp +岩手.jp +岐阜.jp +岡山.jp +島根.jp +広島.jp +徳島.jp +沖縄.jp +滋賀.jp +神奈川.jp +福井.jp +福岡.jp +福島.jp +秋田.jp +群馬.jp +香川.jp +高知.jp +鳥取.jp +鹿児島.jp +// jp geographic type names +// http://jprs.jp/doc/rule/saisoku-1.html +*.kawasaki.jp +!city.kawasaki.jp +*.kitakyushu.jp +!city.kitakyushu.jp +*.kobe.jp +!city.kobe.jp +*.nagoya.jp +!city.nagoya.jp +*.sapporo.jp +!city.sapporo.jp +*.sendai.jp +!city.sendai.jp +*.yokohama.jp +!city.yokohama.jp +// 4th level registration +aisai.aichi.jp +ama.aichi.jp +anjo.aichi.jp +asuke.aichi.jp +chiryu.aichi.jp +chita.aichi.jp +fuso.aichi.jp +gamagori.aichi.jp +handa.aichi.jp +hazu.aichi.jp +hekinan.aichi.jp +higashiura.aichi.jp +ichinomiya.aichi.jp +inazawa.aichi.jp +inuyama.aichi.jp +isshiki.aichi.jp +iwakura.aichi.jp +kanie.aichi.jp +kariya.aichi.jp +kasugai.aichi.jp +kira.aichi.jp +kiyosu.aichi.jp +komaki.aichi.jp +konan.aichi.jp +kota.aichi.jp +mihama.aichi.jp +miyoshi.aichi.jp +nishio.aichi.jp +nisshin.aichi.jp +obu.aichi.jp +oguchi.aichi.jp +oharu.aichi.jp +okazaki.aichi.jp +owariasahi.aichi.jp +seto.aichi.jp +shikatsu.aichi.jp +shinshiro.aichi.jp +shitara.aichi.jp +tahara.aichi.jp +takahama.aichi.jp +tobishima.aichi.jp +toei.aichi.jp +togo.aichi.jp +tokai.aichi.jp +tokoname.aichi.jp +toyoake.aichi.jp +toyohashi.aichi.jp +toyokawa.aichi.jp +toyone.aichi.jp +toyota.aichi.jp +tsushima.aichi.jp +yatomi.aichi.jp +akita.akita.jp +daisen.akita.jp +fujisato.akita.jp +gojome.akita.jp +hachirogata.akita.jp +happou.akita.jp +higashinaruse.akita.jp +honjo.akita.jp +honjyo.akita.jp +ikawa.akita.jp +kamikoani.akita.jp +kamioka.akita.jp +katagami.akita.jp +kazuno.akita.jp +kitaakita.akita.jp +kosaka.akita.jp +kyowa.akita.jp +misato.akita.jp +mitane.akita.jp +moriyoshi.akita.jp +nikaho.akita.jp +noshiro.akita.jp +odate.akita.jp +oga.akita.jp +ogata.akita.jp +semboku.akita.jp +yokote.akita.jp +yurihonjo.akita.jp +aomori.aomori.jp +gonohe.aomori.jp +hachinohe.aomori.jp +hashikami.aomori.jp +hiranai.aomori.jp +hirosaki.aomori.jp +itayanagi.aomori.jp +kuroishi.aomori.jp +misawa.aomori.jp +mutsu.aomori.jp +nakadomari.aomori.jp +noheji.aomori.jp +oirase.aomori.jp +owani.aomori.jp +rokunohe.aomori.jp +sannohe.aomori.jp +shichinohe.aomori.jp +shingo.aomori.jp +takko.aomori.jp +towada.aomori.jp +tsugaru.aomori.jp +tsuruta.aomori.jp +abiko.chiba.jp +asahi.chiba.jp +chonan.chiba.jp +chosei.chiba.jp +choshi.chiba.jp +chuo.chiba.jp +funabashi.chiba.jp +futtsu.chiba.jp +hanamigawa.chiba.jp +ichihara.chiba.jp +ichikawa.chiba.jp +ichinomiya.chiba.jp +inzai.chiba.jp +isumi.chiba.jp +kamagaya.chiba.jp +kamogawa.chiba.jp +kashiwa.chiba.jp +katori.chiba.jp +katsuura.chiba.jp +kimitsu.chiba.jp +kisarazu.chiba.jp +kozaki.chiba.jp +kujukuri.chiba.jp +kyonan.chiba.jp +matsudo.chiba.jp +midori.chiba.jp +mihama.chiba.jp +minamiboso.chiba.jp +mobara.chiba.jp +mutsuzawa.chiba.jp +nagara.chiba.jp +nagareyama.chiba.jp +narashino.chiba.jp +narita.chiba.jp +noda.chiba.jp +oamishirasato.chiba.jp +omigawa.chiba.jp +onjuku.chiba.jp +otaki.chiba.jp +sakae.chiba.jp +sakura.chiba.jp +shimofusa.chiba.jp +shirako.chiba.jp +shiroi.chiba.jp +shisui.chiba.jp +sodegaura.chiba.jp +sosa.chiba.jp +tako.chiba.jp +tateyama.chiba.jp +togane.chiba.jp +tohnosho.chiba.jp +tomisato.chiba.jp +urayasu.chiba.jp +yachimata.chiba.jp +yachiyo.chiba.jp +yokaichiba.chiba.jp +yokoshibahikari.chiba.jp +yotsukaido.chiba.jp +ainan.ehime.jp +honai.ehime.jp +ikata.ehime.jp +imabari.ehime.jp +iyo.ehime.jp +kamijima.ehime.jp +kihoku.ehime.jp +kumakogen.ehime.jp +masaki.ehime.jp +matsuno.ehime.jp +matsuyama.ehime.jp +namikata.ehime.jp +niihama.ehime.jp +ozu.ehime.jp +saijo.ehime.jp +seiyo.ehime.jp +shikokuchuo.ehime.jp +tobe.ehime.jp +toon.ehime.jp +uchiko.ehime.jp +uwajima.ehime.jp +yawatahama.ehime.jp +echizen.fukui.jp +eiheiji.fukui.jp +fukui.fukui.jp +ikeda.fukui.jp +katsuyama.fukui.jp +mihama.fukui.jp +minamiechizen.fukui.jp +obama.fukui.jp +ohi.fukui.jp +ono.fukui.jp +sabae.fukui.jp +sakai.fukui.jp +takahama.fukui.jp +tsuruga.fukui.jp +wakasa.fukui.jp +ashiya.fukuoka.jp +buzen.fukuoka.jp +chikugo.fukuoka.jp +chikuho.fukuoka.jp +chikujo.fukuoka.jp +chikushino.fukuoka.jp +chikuzen.fukuoka.jp +chuo.fukuoka.jp +dazaifu.fukuoka.jp +fukuchi.fukuoka.jp +hakata.fukuoka.jp +higashi.fukuoka.jp +hirokawa.fukuoka.jp +hisayama.fukuoka.jp +iizuka.fukuoka.jp +inatsuki.fukuoka.jp +kaho.fukuoka.jp +kasuga.fukuoka.jp +kasuya.fukuoka.jp +kawara.fukuoka.jp +keisen.fukuoka.jp +koga.fukuoka.jp +kurate.fukuoka.jp +kurogi.fukuoka.jp +kurume.fukuoka.jp +minami.fukuoka.jp +miyako.fukuoka.jp +miyama.fukuoka.jp +miyawaka.fukuoka.jp +mizumaki.fukuoka.jp +munakata.fukuoka.jp +nakagawa.fukuoka.jp +nakama.fukuoka.jp +nishi.fukuoka.jp +nogata.fukuoka.jp +ogori.fukuoka.jp +okagaki.fukuoka.jp +okawa.fukuoka.jp +oki.fukuoka.jp +omuta.fukuoka.jp +onga.fukuoka.jp +onojo.fukuoka.jp +oto.fukuoka.jp +saigawa.fukuoka.jp +sasaguri.fukuoka.jp +shingu.fukuoka.jp +shinyoshitomi.fukuoka.jp +shonai.fukuoka.jp +soeda.fukuoka.jp +sue.fukuoka.jp +tachiarai.fukuoka.jp +tagawa.fukuoka.jp +takata.fukuoka.jp +toho.fukuoka.jp +toyotsu.fukuoka.jp +tsuiki.fukuoka.jp +ukiha.fukuoka.jp +umi.fukuoka.jp +usui.fukuoka.jp +yamada.fukuoka.jp +yame.fukuoka.jp +yanagawa.fukuoka.jp +yukuhashi.fukuoka.jp +aizubange.fukushima.jp +aizumisato.fukushima.jp +aizuwakamatsu.fukushima.jp +asakawa.fukushima.jp +bandai.fukushima.jp +date.fukushima.jp +fukushima.fukushima.jp +furudono.fukushima.jp +futaba.fukushima.jp +hanawa.fukushima.jp +higashi.fukushima.jp +hirata.fukushima.jp +hirono.fukushima.jp +iitate.fukushima.jp +inawashiro.fukushima.jp +ishikawa.fukushima.jp +iwaki.fukushima.jp +izumizaki.fukushima.jp +kagamiishi.fukushima.jp +kaneyama.fukushima.jp +kawamata.fukushima.jp +kitakata.fukushima.jp +kitashiobara.fukushima.jp +koori.fukushima.jp +koriyama.fukushima.jp +kunimi.fukushima.jp +miharu.fukushima.jp +mishima.fukushima.jp +namie.fukushima.jp +nango.fukushima.jp +nishiaizu.fukushima.jp +nishigo.fukushima.jp +okuma.fukushima.jp +omotego.fukushima.jp +ono.fukushima.jp +otama.fukushima.jp +samegawa.fukushima.jp +shimogo.fukushima.jp +shirakawa.fukushima.jp +showa.fukushima.jp +soma.fukushima.jp +sukagawa.fukushima.jp +taishin.fukushima.jp +tamakawa.fukushima.jp +tanagura.fukushima.jp +tenei.fukushima.jp +yabuki.fukushima.jp +yamato.fukushima.jp +yamatsuri.fukushima.jp +yanaizu.fukushima.jp +yugawa.fukushima.jp +anpachi.gifu.jp +ena.gifu.jp +gifu.gifu.jp +ginan.gifu.jp +godo.gifu.jp +gujo.gifu.jp +hashima.gifu.jp +hichiso.gifu.jp +hida.gifu.jp +higashishirakawa.gifu.jp +ibigawa.gifu.jp +ikeda.gifu.jp +kakamigahara.gifu.jp +kani.gifu.jp +kasahara.gifu.jp +kasamatsu.gifu.jp +kawaue.gifu.jp +kitagata.gifu.jp +mino.gifu.jp +minokamo.gifu.jp +mitake.gifu.jp +mizunami.gifu.jp +motosu.gifu.jp +nakatsugawa.gifu.jp +ogaki.gifu.jp +sakahogi.gifu.jp +seki.gifu.jp +sekigahara.gifu.jp +shirakawa.gifu.jp +tajimi.gifu.jp +takayama.gifu.jp +tarui.gifu.jp +toki.gifu.jp +tomika.gifu.jp +wanouchi.gifu.jp +yamagata.gifu.jp +yaotsu.gifu.jp +yoro.gifu.jp +annaka.gunma.jp +chiyoda.gunma.jp +fujioka.gunma.jp +higashiagatsuma.gunma.jp +isesaki.gunma.jp +itakura.gunma.jp +kanna.gunma.jp +kanra.gunma.jp +katashina.gunma.jp +kawaba.gunma.jp +kiryu.gunma.jp +kusatsu.gunma.jp +maebashi.gunma.jp +meiwa.gunma.jp +midori.gunma.jp +minakami.gunma.jp +naganohara.gunma.jp +nakanojo.gunma.jp +nanmoku.gunma.jp +numata.gunma.jp +oizumi.gunma.jp +ora.gunma.jp +ota.gunma.jp +shibukawa.gunma.jp +shimonita.gunma.jp +shinto.gunma.jp +showa.gunma.jp +takasaki.gunma.jp +takayama.gunma.jp +tamamura.gunma.jp +tatebayashi.gunma.jp +tomioka.gunma.jp +tsukiyono.gunma.jp +tsumagoi.gunma.jp +ueno.gunma.jp +yoshioka.gunma.jp +asaminami.hiroshima.jp +daiwa.hiroshima.jp +etajima.hiroshima.jp +fuchu.hiroshima.jp +fukuyama.hiroshima.jp +hatsukaichi.hiroshima.jp +higashihiroshima.hiroshima.jp +hongo.hiroshima.jp +jinsekikogen.hiroshima.jp +kaita.hiroshima.jp +kui.hiroshima.jp +kumano.hiroshima.jp +kure.hiroshima.jp +mihara.hiroshima.jp +miyoshi.hiroshima.jp +naka.hiroshima.jp +onomichi.hiroshima.jp +osakikamijima.hiroshima.jp +otake.hiroshima.jp +saka.hiroshima.jp +sera.hiroshima.jp +seranishi.hiroshima.jp +shinichi.hiroshima.jp +shobara.hiroshima.jp +takehara.hiroshima.jp +abashiri.hokkaido.jp +abira.hokkaido.jp +aibetsu.hokkaido.jp +akabira.hokkaido.jp +akkeshi.hokkaido.jp +asahikawa.hokkaido.jp +ashibetsu.hokkaido.jp +ashoro.hokkaido.jp +assabu.hokkaido.jp +atsuma.hokkaido.jp +bibai.hokkaido.jp +biei.hokkaido.jp +bifuka.hokkaido.jp +bihoro.hokkaido.jp +biratori.hokkaido.jp +chippubetsu.hokkaido.jp +chitose.hokkaido.jp +date.hokkaido.jp +ebetsu.hokkaido.jp +embetsu.hokkaido.jp +eniwa.hokkaido.jp +erimo.hokkaido.jp +esan.hokkaido.jp +esashi.hokkaido.jp +fukagawa.hokkaido.jp +fukushima.hokkaido.jp +furano.hokkaido.jp +furubira.hokkaido.jp +haboro.hokkaido.jp +hakodate.hokkaido.jp +hamatonbetsu.hokkaido.jp +hidaka.hokkaido.jp +higashikagura.hokkaido.jp +higashikawa.hokkaido.jp +hiroo.hokkaido.jp +hokuryu.hokkaido.jp +hokuto.hokkaido.jp +honbetsu.hokkaido.jp +horokanai.hokkaido.jp +horonobe.hokkaido.jp +ikeda.hokkaido.jp +imakane.hokkaido.jp +ishikari.hokkaido.jp +iwamizawa.hokkaido.jp +iwanai.hokkaido.jp +kamifurano.hokkaido.jp +kamikawa.hokkaido.jp +kamishihoro.hokkaido.jp +kamisunagawa.hokkaido.jp +kamoenai.hokkaido.jp +kayabe.hokkaido.jp +kembuchi.hokkaido.jp +kikonai.hokkaido.jp +kimobetsu.hokkaido.jp +kitahiroshima.hokkaido.jp +kitami.hokkaido.jp +kiyosato.hokkaido.jp +koshimizu.hokkaido.jp +kunneppu.hokkaido.jp +kuriyama.hokkaido.jp +kuromatsunai.hokkaido.jp +kushiro.hokkaido.jp +kutchan.hokkaido.jp +kyowa.hokkaido.jp +mashike.hokkaido.jp +matsumae.hokkaido.jp +mikasa.hokkaido.jp +minamifurano.hokkaido.jp +mombetsu.hokkaido.jp +moseushi.hokkaido.jp +mukawa.hokkaido.jp +muroran.hokkaido.jp +naie.hokkaido.jp +nakagawa.hokkaido.jp +nakasatsunai.hokkaido.jp +nakatombetsu.hokkaido.jp +nanae.hokkaido.jp +nanporo.hokkaido.jp +nayoro.hokkaido.jp +nemuro.hokkaido.jp +niikappu.hokkaido.jp +niki.hokkaido.jp +nishiokoppe.hokkaido.jp +noboribetsu.hokkaido.jp +numata.hokkaido.jp +obihiro.hokkaido.jp +obira.hokkaido.jp +oketo.hokkaido.jp +okoppe.hokkaido.jp +otaru.hokkaido.jp +otobe.hokkaido.jp +otofuke.hokkaido.jp +otoineppu.hokkaido.jp +oumu.hokkaido.jp +ozora.hokkaido.jp +pippu.hokkaido.jp +rankoshi.hokkaido.jp +rebun.hokkaido.jp +rikubetsu.hokkaido.jp +rishiri.hokkaido.jp +rishirifuji.hokkaido.jp +saroma.hokkaido.jp +sarufutsu.hokkaido.jp +shakotan.hokkaido.jp +shari.hokkaido.jp +shibecha.hokkaido.jp +shibetsu.hokkaido.jp +shikabe.hokkaido.jp +shikaoi.hokkaido.jp +shimamaki.hokkaido.jp +shimizu.hokkaido.jp +shimokawa.hokkaido.jp +shinshinotsu.hokkaido.jp +shintoku.hokkaido.jp +shiranuka.hokkaido.jp +shiraoi.hokkaido.jp +shiriuchi.hokkaido.jp +sobetsu.hokkaido.jp +sunagawa.hokkaido.jp +taiki.hokkaido.jp +takasu.hokkaido.jp +takikawa.hokkaido.jp +takinoue.hokkaido.jp +teshikaga.hokkaido.jp +tobetsu.hokkaido.jp +tohma.hokkaido.jp +tomakomai.hokkaido.jp +tomari.hokkaido.jp +toya.hokkaido.jp +toyako.hokkaido.jp +toyotomi.hokkaido.jp +toyoura.hokkaido.jp +tsubetsu.hokkaido.jp +tsukigata.hokkaido.jp +urakawa.hokkaido.jp +urausu.hokkaido.jp +uryu.hokkaido.jp +utashinai.hokkaido.jp +wakkanai.hokkaido.jp +wassamu.hokkaido.jp +yakumo.hokkaido.jp +yoichi.hokkaido.jp +aioi.hyogo.jp +akashi.hyogo.jp +ako.hyogo.jp +amagasaki.hyogo.jp +aogaki.hyogo.jp +asago.hyogo.jp +ashiya.hyogo.jp +awaji.hyogo.jp +fukusaki.hyogo.jp +goshiki.hyogo.jp +harima.hyogo.jp +himeji.hyogo.jp +ichikawa.hyogo.jp +inagawa.hyogo.jp +itami.hyogo.jp +kakogawa.hyogo.jp +kamigori.hyogo.jp +kamikawa.hyogo.jp +kasai.hyogo.jp +kasuga.hyogo.jp +kawanishi.hyogo.jp +miki.hyogo.jp +minamiawaji.hyogo.jp +nishinomiya.hyogo.jp +nishiwaki.hyogo.jp +ono.hyogo.jp +sanda.hyogo.jp +sannan.hyogo.jp +sasayama.hyogo.jp +sayo.hyogo.jp +shingu.hyogo.jp +shinonsen.hyogo.jp +shiso.hyogo.jp +sumoto.hyogo.jp +taishi.hyogo.jp +taka.hyogo.jp +takarazuka.hyogo.jp +takasago.hyogo.jp +takino.hyogo.jp +tamba.hyogo.jp +tatsuno.hyogo.jp +toyooka.hyogo.jp +yabu.hyogo.jp +yashiro.hyogo.jp +yoka.hyogo.jp +yokawa.hyogo.jp +ami.ibaraki.jp +asahi.ibaraki.jp +bando.ibaraki.jp +chikusei.ibaraki.jp +daigo.ibaraki.jp +fujishiro.ibaraki.jp +hitachi.ibaraki.jp +hitachinaka.ibaraki.jp +hitachiomiya.ibaraki.jp +hitachiota.ibaraki.jp +ibaraki.ibaraki.jp +ina.ibaraki.jp +inashiki.ibaraki.jp +itako.ibaraki.jp +iwama.ibaraki.jp +joso.ibaraki.jp +kamisu.ibaraki.jp +kasama.ibaraki.jp +kashima.ibaraki.jp +kasumigaura.ibaraki.jp +koga.ibaraki.jp +miho.ibaraki.jp +mito.ibaraki.jp +moriya.ibaraki.jp +naka.ibaraki.jp +namegata.ibaraki.jp +oarai.ibaraki.jp +ogawa.ibaraki.jp +omitama.ibaraki.jp +ryugasaki.ibaraki.jp +sakai.ibaraki.jp +sakuragawa.ibaraki.jp +shimodate.ibaraki.jp +shimotsuma.ibaraki.jp +shirosato.ibaraki.jp +sowa.ibaraki.jp +suifu.ibaraki.jp +takahagi.ibaraki.jp +tamatsukuri.ibaraki.jp +tokai.ibaraki.jp +tomobe.ibaraki.jp +tone.ibaraki.jp +toride.ibaraki.jp +tsuchiura.ibaraki.jp +tsukuba.ibaraki.jp +uchihara.ibaraki.jp +ushiku.ibaraki.jp +yachiyo.ibaraki.jp +yamagata.ibaraki.jp +yawara.ibaraki.jp +yuki.ibaraki.jp +anamizu.ishikawa.jp +hakui.ishikawa.jp +hakusan.ishikawa.jp +kaga.ishikawa.jp +kahoku.ishikawa.jp +kanazawa.ishikawa.jp +kawakita.ishikawa.jp +komatsu.ishikawa.jp +nakanoto.ishikawa.jp +nanao.ishikawa.jp +nomi.ishikawa.jp +nonoichi.ishikawa.jp +noto.ishikawa.jp +shika.ishikawa.jp +suzu.ishikawa.jp +tsubata.ishikawa.jp +tsurugi.ishikawa.jp +uchinada.ishikawa.jp +wajima.ishikawa.jp +fudai.iwate.jp +fujisawa.iwate.jp +hanamaki.iwate.jp +hiraizumi.iwate.jp +hirono.iwate.jp +ichinohe.iwate.jp +ichinoseki.iwate.jp +iwaizumi.iwate.jp +iwate.iwate.jp +joboji.iwate.jp +kamaishi.iwate.jp +kanegasaki.iwate.jp +karumai.iwate.jp +kawai.iwate.jp +kitakami.iwate.jp +kuji.iwate.jp +kunohe.iwate.jp +kuzumaki.iwate.jp +miyako.iwate.jp +mizusawa.iwate.jp +morioka.iwate.jp +ninohe.iwate.jp +noda.iwate.jp +ofunato.iwate.jp +oshu.iwate.jp +otsuchi.iwate.jp +rikuzentakata.iwate.jp +shiwa.iwate.jp +shizukuishi.iwate.jp +sumita.iwate.jp +tanohata.iwate.jp +tono.iwate.jp +yahaba.iwate.jp +yamada.iwate.jp +ayagawa.kagawa.jp +higashikagawa.kagawa.jp +kanonji.kagawa.jp +kotohira.kagawa.jp +manno.kagawa.jp +marugame.kagawa.jp +mitoyo.kagawa.jp +naoshima.kagawa.jp +sanuki.kagawa.jp +tadotsu.kagawa.jp +takamatsu.kagawa.jp +tonosho.kagawa.jp +uchinomi.kagawa.jp +utazu.kagawa.jp +zentsuji.kagawa.jp +akune.kagoshima.jp +amami.kagoshima.jp +hioki.kagoshima.jp +isa.kagoshima.jp +isen.kagoshima.jp +izumi.kagoshima.jp +kagoshima.kagoshima.jp +kanoya.kagoshima.jp +kawanabe.kagoshima.jp +kinko.kagoshima.jp +kouyama.kagoshima.jp +makurazaki.kagoshima.jp +matsumoto.kagoshima.jp +minamitane.kagoshima.jp +nakatane.kagoshima.jp +nishinoomote.kagoshima.jp +satsumasendai.kagoshima.jp +soo.kagoshima.jp +tarumizu.kagoshima.jp +yusui.kagoshima.jp +aikawa.kanagawa.jp +atsugi.kanagawa.jp +ayase.kanagawa.jp +chigasaki.kanagawa.jp +ebina.kanagawa.jp +fujisawa.kanagawa.jp +hadano.kanagawa.jp +hakone.kanagawa.jp +hiratsuka.kanagawa.jp +isehara.kanagawa.jp +kaisei.kanagawa.jp +kamakura.kanagawa.jp +kiyokawa.kanagawa.jp +matsuda.kanagawa.jp +minamiashigara.kanagawa.jp +miura.kanagawa.jp +nakai.kanagawa.jp +ninomiya.kanagawa.jp +odawara.kanagawa.jp +oi.kanagawa.jp +oiso.kanagawa.jp +sagamihara.kanagawa.jp +samukawa.kanagawa.jp +tsukui.kanagawa.jp +yamakita.kanagawa.jp +yamato.kanagawa.jp +yokosuka.kanagawa.jp +yugawara.kanagawa.jp +zama.kanagawa.jp +zushi.kanagawa.jp +aki.kochi.jp +geisei.kochi.jp +hidaka.kochi.jp +higashitsuno.kochi.jp +ino.kochi.jp +kagami.kochi.jp +kami.kochi.jp +kitagawa.kochi.jp +kochi.kochi.jp +mihara.kochi.jp +motoyama.kochi.jp +muroto.kochi.jp +nahari.kochi.jp +nakamura.kochi.jp +nankoku.kochi.jp +nishitosa.kochi.jp +niyodogawa.kochi.jp +ochi.kochi.jp +okawa.kochi.jp +otoyo.kochi.jp +otsuki.kochi.jp +sakawa.kochi.jp +sukumo.kochi.jp +susaki.kochi.jp +tosa.kochi.jp +tosashimizu.kochi.jp +toyo.kochi.jp +tsuno.kochi.jp +umaji.kochi.jp +yasuda.kochi.jp +yusuhara.kochi.jp +amakusa.kumamoto.jp +arao.kumamoto.jp +aso.kumamoto.jp +choyo.kumamoto.jp +gyokuto.kumamoto.jp +kamiamakusa.kumamoto.jp +kikuchi.kumamoto.jp +kumamoto.kumamoto.jp +mashiki.kumamoto.jp +mifune.kumamoto.jp +minamata.kumamoto.jp +minamioguni.kumamoto.jp +nagasu.kumamoto.jp +nishihara.kumamoto.jp +oguni.kumamoto.jp +ozu.kumamoto.jp +sumoto.kumamoto.jp +takamori.kumamoto.jp +uki.kumamoto.jp +uto.kumamoto.jp +yamaga.kumamoto.jp +yamato.kumamoto.jp +yatsushiro.kumamoto.jp +ayabe.kyoto.jp +fukuchiyama.kyoto.jp +higashiyama.kyoto.jp +ide.kyoto.jp +ine.kyoto.jp +joyo.kyoto.jp +kameoka.kyoto.jp +kamo.kyoto.jp +kita.kyoto.jp +kizu.kyoto.jp +kumiyama.kyoto.jp +kyotamba.kyoto.jp +kyotanabe.kyoto.jp +kyotango.kyoto.jp +maizuru.kyoto.jp +minami.kyoto.jp +minamiyamashiro.kyoto.jp +miyazu.kyoto.jp +muko.kyoto.jp +nagaokakyo.kyoto.jp +nakagyo.kyoto.jp +nantan.kyoto.jp +oyamazaki.kyoto.jp +sakyo.kyoto.jp +seika.kyoto.jp +tanabe.kyoto.jp +uji.kyoto.jp +ujitawara.kyoto.jp +wazuka.kyoto.jp +yamashina.kyoto.jp +yawata.kyoto.jp +asahi.mie.jp +inabe.mie.jp +ise.mie.jp +kameyama.mie.jp +kawagoe.mie.jp +kiho.mie.jp +kisosaki.mie.jp +kiwa.mie.jp +komono.mie.jp +kumano.mie.jp +kuwana.mie.jp +matsusaka.mie.jp +meiwa.mie.jp +mihama.mie.jp +minamiise.mie.jp +misugi.mie.jp +miyama.mie.jp +nabari.mie.jp +shima.mie.jp +suzuka.mie.jp +tado.mie.jp +taiki.mie.jp +taki.mie.jp +tamaki.mie.jp +toba.mie.jp +tsu.mie.jp +udono.mie.jp +ureshino.mie.jp +watarai.mie.jp +yokkaichi.mie.jp +furukawa.miyagi.jp +higashimatsushima.miyagi.jp +ishinomaki.miyagi.jp +iwanuma.miyagi.jp +kakuda.miyagi.jp +kami.miyagi.jp +kawasaki.miyagi.jp +marumori.miyagi.jp +matsushima.miyagi.jp +minamisanriku.miyagi.jp +misato.miyagi.jp +murata.miyagi.jp +natori.miyagi.jp +ogawara.miyagi.jp +ohira.miyagi.jp +onagawa.miyagi.jp +osaki.miyagi.jp +rifu.miyagi.jp +semine.miyagi.jp +shibata.miyagi.jp +shichikashuku.miyagi.jp +shikama.miyagi.jp +shiogama.miyagi.jp +shiroishi.miyagi.jp +tagajo.miyagi.jp +taiwa.miyagi.jp +tome.miyagi.jp +tomiya.miyagi.jp +wakuya.miyagi.jp +watari.miyagi.jp +yamamoto.miyagi.jp +zao.miyagi.jp +aya.miyazaki.jp +ebino.miyazaki.jp +gokase.miyazaki.jp +hyuga.miyazaki.jp +kadogawa.miyazaki.jp +kawaminami.miyazaki.jp +kijo.miyazaki.jp +kitagawa.miyazaki.jp +kitakata.miyazaki.jp +kitaura.miyazaki.jp +kobayashi.miyazaki.jp +kunitomi.miyazaki.jp +kushima.miyazaki.jp +mimata.miyazaki.jp +miyakonojo.miyazaki.jp +miyazaki.miyazaki.jp +morotsuka.miyazaki.jp +nichinan.miyazaki.jp +nishimera.miyazaki.jp +nobeoka.miyazaki.jp +saito.miyazaki.jp +shiiba.miyazaki.jp +shintomi.miyazaki.jp +takaharu.miyazaki.jp +takanabe.miyazaki.jp +takazaki.miyazaki.jp +tsuno.miyazaki.jp +achi.nagano.jp +agematsu.nagano.jp +anan.nagano.jp +aoki.nagano.jp +asahi.nagano.jp +azumino.nagano.jp +chikuhoku.nagano.jp +chikuma.nagano.jp +chino.nagano.jp +fujimi.nagano.jp +hakuba.nagano.jp +hara.nagano.jp +hiraya.nagano.jp +iida.nagano.jp +iijima.nagano.jp +iiyama.nagano.jp +iizuna.nagano.jp +ikeda.nagano.jp +ikusaka.nagano.jp +ina.nagano.jp +karuizawa.nagano.jp +kawakami.nagano.jp +kiso.nagano.jp +kisofukushima.nagano.jp +kitaaiki.nagano.jp +komagane.nagano.jp +komoro.nagano.jp +matsukawa.nagano.jp +matsumoto.nagano.jp +miasa.nagano.jp +minamiaiki.nagano.jp +minamimaki.nagano.jp +minamiminowa.nagano.jp +minowa.nagano.jp +miyada.nagano.jp +miyota.nagano.jp +mochizuki.nagano.jp +nagano.nagano.jp +nagawa.nagano.jp +nagiso.nagano.jp +nakagawa.nagano.jp +nakano.nagano.jp +nozawaonsen.nagano.jp +obuse.nagano.jp +ogawa.nagano.jp +okaya.nagano.jp +omachi.nagano.jp +omi.nagano.jp +ookuwa.nagano.jp +ooshika.nagano.jp +otaki.nagano.jp +otari.nagano.jp +sakae.nagano.jp +sakaki.nagano.jp +saku.nagano.jp +sakuho.nagano.jp +shimosuwa.nagano.jp +shinanomachi.nagano.jp +shiojiri.nagano.jp +suwa.nagano.jp +suzaka.nagano.jp +takagi.nagano.jp +takamori.nagano.jp +takayama.nagano.jp +tateshina.nagano.jp +tatsuno.nagano.jp +togakushi.nagano.jp +togura.nagano.jp +tomi.nagano.jp +ueda.nagano.jp +wada.nagano.jp +yamagata.nagano.jp +yamanouchi.nagano.jp +yasaka.nagano.jp +yasuoka.nagano.jp +chijiwa.nagasaki.jp +futsu.nagasaki.jp +goto.nagasaki.jp +hasami.nagasaki.jp +hirado.nagasaki.jp +iki.nagasaki.jp +isahaya.nagasaki.jp +kawatana.nagasaki.jp +kuchinotsu.nagasaki.jp +matsuura.nagasaki.jp +nagasaki.nagasaki.jp +obama.nagasaki.jp +omura.nagasaki.jp +oseto.nagasaki.jp +saikai.nagasaki.jp +sasebo.nagasaki.jp +seihi.nagasaki.jp +shimabara.nagasaki.jp +shinkamigoto.nagasaki.jp +togitsu.nagasaki.jp +tsushima.nagasaki.jp +unzen.nagasaki.jp +ando.nara.jp +gose.nara.jp +heguri.nara.jp +higashiyoshino.nara.jp +ikaruga.nara.jp +ikoma.nara.jp +kamikitayama.nara.jp +kanmaki.nara.jp +kashiba.nara.jp +kashihara.nara.jp +katsuragi.nara.jp +kawai.nara.jp +kawakami.nara.jp +kawanishi.nara.jp +koryo.nara.jp +kurotaki.nara.jp +mitsue.nara.jp +miyake.nara.jp +nara.nara.jp +nosegawa.nara.jp +oji.nara.jp +ouda.nara.jp +oyodo.nara.jp +sakurai.nara.jp +sango.nara.jp +shimoichi.nara.jp +shimokitayama.nara.jp +shinjo.nara.jp +soni.nara.jp +takatori.nara.jp +tawaramoto.nara.jp +tenkawa.nara.jp +tenri.nara.jp +uda.nara.jp +yamatokoriyama.nara.jp +yamatotakada.nara.jp +yamazoe.nara.jp +yoshino.nara.jp +aga.niigata.jp +agano.niigata.jp +gosen.niigata.jp +itoigawa.niigata.jp +izumozaki.niigata.jp +joetsu.niigata.jp +kamo.niigata.jp +kariwa.niigata.jp +kashiwazaki.niigata.jp +minamiuonuma.niigata.jp +mitsuke.niigata.jp +muika.niigata.jp +murakami.niigata.jp +myoko.niigata.jp +nagaoka.niigata.jp +niigata.niigata.jp +ojiya.niigata.jp +omi.niigata.jp +sado.niigata.jp +sanjo.niigata.jp +seiro.niigata.jp +seirou.niigata.jp +sekikawa.niigata.jp +shibata.niigata.jp +tagami.niigata.jp +tainai.niigata.jp +tochio.niigata.jp +tokamachi.niigata.jp +tsubame.niigata.jp +tsunan.niigata.jp +uonuma.niigata.jp +yahiko.niigata.jp +yoita.niigata.jp +yuzawa.niigata.jp +beppu.oita.jp +bungoono.oita.jp +bungotakada.oita.jp +hasama.oita.jp +hiji.oita.jp +himeshima.oita.jp +hita.oita.jp +kamitsue.oita.jp +kokonoe.oita.jp +kuju.oita.jp +kunisaki.oita.jp +kusu.oita.jp +oita.oita.jp +saiki.oita.jp +taketa.oita.jp +tsukumi.oita.jp +usa.oita.jp +usuki.oita.jp +yufu.oita.jp +akaiwa.okayama.jp +asakuchi.okayama.jp +bizen.okayama.jp +hayashima.okayama.jp +ibara.okayama.jp +kagamino.okayama.jp +kasaoka.okayama.jp +kibichuo.okayama.jp +kumenan.okayama.jp +kurashiki.okayama.jp +maniwa.okayama.jp +misaki.okayama.jp +nagi.okayama.jp +niimi.okayama.jp +nishiawakura.okayama.jp +okayama.okayama.jp +satosho.okayama.jp +setouchi.okayama.jp +shinjo.okayama.jp +shoo.okayama.jp +soja.okayama.jp +takahashi.okayama.jp +tamano.okayama.jp +tsuyama.okayama.jp +wake.okayama.jp +yakage.okayama.jp +aguni.okinawa.jp +ginowan.okinawa.jp +ginoza.okinawa.jp +gushikami.okinawa.jp +haebaru.okinawa.jp +higashi.okinawa.jp +hirara.okinawa.jp +iheya.okinawa.jp +ishigaki.okinawa.jp +ishikawa.okinawa.jp +itoman.okinawa.jp +izena.okinawa.jp +kadena.okinawa.jp +kin.okinawa.jp +kitadaito.okinawa.jp +kitanakagusuku.okinawa.jp +kumejima.okinawa.jp +kunigami.okinawa.jp +minamidaito.okinawa.jp +motobu.okinawa.jp +nago.okinawa.jp +naha.okinawa.jp +nakagusuku.okinawa.jp +nakijin.okinawa.jp +nanjo.okinawa.jp +nishihara.okinawa.jp +ogimi.okinawa.jp +okinawa.okinawa.jp +onna.okinawa.jp +shimoji.okinawa.jp +taketomi.okinawa.jp +tarama.okinawa.jp +tokashiki.okinawa.jp +tomigusuku.okinawa.jp +tonaki.okinawa.jp +urasoe.okinawa.jp +uruma.okinawa.jp +yaese.okinawa.jp +yomitan.okinawa.jp +yonabaru.okinawa.jp +yonaguni.okinawa.jp +zamami.okinawa.jp +abeno.osaka.jp +chihayaakasaka.osaka.jp +chuo.osaka.jp +daito.osaka.jp +fujiidera.osaka.jp +habikino.osaka.jp +hannan.osaka.jp +higashiosaka.osaka.jp +higashisumiyoshi.osaka.jp +higashiyodogawa.osaka.jp +hirakata.osaka.jp +ibaraki.osaka.jp +ikeda.osaka.jp +izumi.osaka.jp +izumiotsu.osaka.jp +izumisano.osaka.jp +kadoma.osaka.jp +kaizuka.osaka.jp +kanan.osaka.jp +kashiwara.osaka.jp +katano.osaka.jp +kawachinagano.osaka.jp +kishiwada.osaka.jp +kita.osaka.jp +kumatori.osaka.jp +matsubara.osaka.jp +minato.osaka.jp +minoh.osaka.jp +misaki.osaka.jp +moriguchi.osaka.jp +neyagawa.osaka.jp +nishi.osaka.jp +nose.osaka.jp +osakasayama.osaka.jp +sakai.osaka.jp +sayama.osaka.jp +sennan.osaka.jp +settsu.osaka.jp +shijonawate.osaka.jp +shimamoto.osaka.jp +suita.osaka.jp +tadaoka.osaka.jp +taishi.osaka.jp +tajiri.osaka.jp +takaishi.osaka.jp +takatsuki.osaka.jp +tondabayashi.osaka.jp +toyonaka.osaka.jp +toyono.osaka.jp +yao.osaka.jp +ariake.saga.jp +arita.saga.jp +fukudomi.saga.jp +genkai.saga.jp +hamatama.saga.jp +hizen.saga.jp +imari.saga.jp +kamimine.saga.jp +kanzaki.saga.jp +karatsu.saga.jp +kashima.saga.jp +kitagata.saga.jp +kitahata.saga.jp +kiyama.saga.jp +kouhoku.saga.jp +kyuragi.saga.jp +nishiarita.saga.jp +ogi.saga.jp +omachi.saga.jp +ouchi.saga.jp +saga.saga.jp +shiroishi.saga.jp +taku.saga.jp +tara.saga.jp +tosu.saga.jp +yoshinogari.saga.jp +arakawa.saitama.jp +asaka.saitama.jp +chichibu.saitama.jp +fujimi.saitama.jp +fujimino.saitama.jp +fukaya.saitama.jp +hanno.saitama.jp +hanyu.saitama.jp +hasuda.saitama.jp +hatogaya.saitama.jp +hatoyama.saitama.jp +hidaka.saitama.jp +higashichichibu.saitama.jp +higashimatsuyama.saitama.jp +honjo.saitama.jp +ina.saitama.jp +iruma.saitama.jp +iwatsuki.saitama.jp +kamiizumi.saitama.jp +kamikawa.saitama.jp +kamisato.saitama.jp +kasukabe.saitama.jp +kawagoe.saitama.jp +kawaguchi.saitama.jp +kawajima.saitama.jp +kazo.saitama.jp +kitamoto.saitama.jp +koshigaya.saitama.jp +kounosu.saitama.jp +kuki.saitama.jp +kumagaya.saitama.jp +matsubushi.saitama.jp +minano.saitama.jp +misato.saitama.jp +miyashiro.saitama.jp +miyoshi.saitama.jp +moroyama.saitama.jp +nagatoro.saitama.jp +namegawa.saitama.jp +niiza.saitama.jp +ogano.saitama.jp +ogawa.saitama.jp +ogose.saitama.jp +okegawa.saitama.jp +omiya.saitama.jp +otaki.saitama.jp +ranzan.saitama.jp +ryokami.saitama.jp +saitama.saitama.jp +sakado.saitama.jp +satte.saitama.jp +sayama.saitama.jp +shiki.saitama.jp +shiraoka.saitama.jp +soka.saitama.jp +sugito.saitama.jp +toda.saitama.jp +tokigawa.saitama.jp +tokorozawa.saitama.jp +tsurugashima.saitama.jp +urawa.saitama.jp +warabi.saitama.jp +yashio.saitama.jp +yokoze.saitama.jp +yono.saitama.jp +yorii.saitama.jp +yoshida.saitama.jp +yoshikawa.saitama.jp +yoshimi.saitama.jp +aisho.shiga.jp +gamo.shiga.jp +higashiomi.shiga.jp +hikone.shiga.jp +koka.shiga.jp +konan.shiga.jp +kosei.shiga.jp +koto.shiga.jp +kusatsu.shiga.jp +maibara.shiga.jp +moriyama.shiga.jp +nagahama.shiga.jp +nishiazai.shiga.jp +notogawa.shiga.jp +omihachiman.shiga.jp +otsu.shiga.jp +ritto.shiga.jp +ryuoh.shiga.jp +takashima.shiga.jp +takatsuki.shiga.jp +torahime.shiga.jp +toyosato.shiga.jp +yasu.shiga.jp +akagi.shimane.jp +ama.shimane.jp +gotsu.shimane.jp +hamada.shimane.jp +higashiizumo.shimane.jp +hikawa.shimane.jp +hikimi.shimane.jp +izumo.shimane.jp +kakinoki.shimane.jp +masuda.shimane.jp +matsue.shimane.jp +misato.shimane.jp +nishinoshima.shimane.jp +ohda.shimane.jp +okinoshima.shimane.jp +okuizumo.shimane.jp +shimane.shimane.jp +tamayu.shimane.jp +tsuwano.shimane.jp +unnan.shimane.jp +yakumo.shimane.jp +yasugi.shimane.jp +yatsuka.shimane.jp +arai.shizuoka.jp +atami.shizuoka.jp +fuji.shizuoka.jp +fujieda.shizuoka.jp +fujikawa.shizuoka.jp +fujinomiya.shizuoka.jp +fukuroi.shizuoka.jp +gotemba.shizuoka.jp +haibara.shizuoka.jp +hamamatsu.shizuoka.jp +higashiizu.shizuoka.jp +ito.shizuoka.jp +iwata.shizuoka.jp +izu.shizuoka.jp +izunokuni.shizuoka.jp +kakegawa.shizuoka.jp +kannami.shizuoka.jp +kawanehon.shizuoka.jp +kawazu.shizuoka.jp +kikugawa.shizuoka.jp +kosai.shizuoka.jp +makinohara.shizuoka.jp +matsuzaki.shizuoka.jp +minamiizu.shizuoka.jp +mishima.shizuoka.jp +morimachi.shizuoka.jp +nishiizu.shizuoka.jp +numazu.shizuoka.jp +omaezaki.shizuoka.jp +shimada.shizuoka.jp +shimizu.shizuoka.jp +shimoda.shizuoka.jp +shizuoka.shizuoka.jp +susono.shizuoka.jp +yaizu.shizuoka.jp +yoshida.shizuoka.jp +ashikaga.tochigi.jp +bato.tochigi.jp +haga.tochigi.jp +ichikai.tochigi.jp +iwafune.tochigi.jp +kaminokawa.tochigi.jp +kanuma.tochigi.jp +karasuyama.tochigi.jp +kuroiso.tochigi.jp +mashiko.tochigi.jp +mibu.tochigi.jp +moka.tochigi.jp +motegi.tochigi.jp +nasu.tochigi.jp +nasushiobara.tochigi.jp +nikko.tochigi.jp +nishikata.tochigi.jp +nogi.tochigi.jp +ohira.tochigi.jp +ohtawara.tochigi.jp +oyama.tochigi.jp +sakura.tochigi.jp +sano.tochigi.jp +shimotsuke.tochigi.jp +shioya.tochigi.jp +takanezawa.tochigi.jp +tochigi.tochigi.jp +tsuga.tochigi.jp +ujiie.tochigi.jp +utsunomiya.tochigi.jp +yaita.tochigi.jp +aizumi.tokushima.jp +anan.tokushima.jp +ichiba.tokushima.jp +itano.tokushima.jp +kainan.tokushima.jp +komatsushima.tokushima.jp +matsushige.tokushima.jp +mima.tokushima.jp +minami.tokushima.jp +miyoshi.tokushima.jp +mugi.tokushima.jp +nakagawa.tokushima.jp +naruto.tokushima.jp +sanagochi.tokushima.jp +shishikui.tokushima.jp +tokushima.tokushima.jp +wajiki.tokushima.jp +adachi.tokyo.jp +akiruno.tokyo.jp +akishima.tokyo.jp +aogashima.tokyo.jp +arakawa.tokyo.jp +bunkyo.tokyo.jp +chiyoda.tokyo.jp +chofu.tokyo.jp +chuo.tokyo.jp +edogawa.tokyo.jp +fuchu.tokyo.jp +fussa.tokyo.jp +hachijo.tokyo.jp +hachioji.tokyo.jp +hamura.tokyo.jp +higashikurume.tokyo.jp +higashimurayama.tokyo.jp +higashiyamato.tokyo.jp +hino.tokyo.jp +hinode.tokyo.jp +hinohara.tokyo.jp +inagi.tokyo.jp +itabashi.tokyo.jp +katsushika.tokyo.jp +kita.tokyo.jp +kiyose.tokyo.jp +kodaira.tokyo.jp +koganei.tokyo.jp +kokubunji.tokyo.jp +komae.tokyo.jp +koto.tokyo.jp +kouzushima.tokyo.jp +kunitachi.tokyo.jp +machida.tokyo.jp +meguro.tokyo.jp +minato.tokyo.jp +mitaka.tokyo.jp +mizuho.tokyo.jp +musashimurayama.tokyo.jp +musashino.tokyo.jp +nakano.tokyo.jp +nerima.tokyo.jp +ogasawara.tokyo.jp +okutama.tokyo.jp +ome.tokyo.jp +oshima.tokyo.jp +ota.tokyo.jp +setagaya.tokyo.jp +shibuya.tokyo.jp +shinagawa.tokyo.jp +shinjuku.tokyo.jp +suginami.tokyo.jp +sumida.tokyo.jp +tachikawa.tokyo.jp +taito.tokyo.jp +tama.tokyo.jp +toshima.tokyo.jp +chizu.tottori.jp +hino.tottori.jp +kawahara.tottori.jp +koge.tottori.jp +kotoura.tottori.jp +misasa.tottori.jp +nanbu.tottori.jp +nichinan.tottori.jp +sakaiminato.tottori.jp +tottori.tottori.jp +wakasa.tottori.jp +yazu.tottori.jp +yonago.tottori.jp +asahi.toyama.jp +fuchu.toyama.jp +fukumitsu.toyama.jp +funahashi.toyama.jp +himi.toyama.jp +imizu.toyama.jp +inami.toyama.jp +johana.toyama.jp +kamiichi.toyama.jp +kurobe.toyama.jp +nakaniikawa.toyama.jp +namerikawa.toyama.jp +nanto.toyama.jp +nyuzen.toyama.jp +oyabe.toyama.jp +taira.toyama.jp +takaoka.toyama.jp +tateyama.toyama.jp +toga.toyama.jp +tonami.toyama.jp +toyama.toyama.jp +unazuki.toyama.jp +uozu.toyama.jp +yamada.toyama.jp +arida.wakayama.jp +aridagawa.wakayama.jp +gobo.wakayama.jp +hashimoto.wakayama.jp +hidaka.wakayama.jp +hirogawa.wakayama.jp +inami.wakayama.jp +iwade.wakayama.jp +kainan.wakayama.jp +kamitonda.wakayama.jp +katsuragi.wakayama.jp +kimino.wakayama.jp +kinokawa.wakayama.jp +kitayama.wakayama.jp +koya.wakayama.jp +koza.wakayama.jp +kozagawa.wakayama.jp +kudoyama.wakayama.jp +kushimoto.wakayama.jp +mihama.wakayama.jp +misato.wakayama.jp +nachikatsuura.wakayama.jp +shingu.wakayama.jp +shirahama.wakayama.jp +taiji.wakayama.jp +tanabe.wakayama.jp +wakayama.wakayama.jp +yuasa.wakayama.jp +yura.wakayama.jp +asahi.yamagata.jp +funagata.yamagata.jp +higashine.yamagata.jp +iide.yamagata.jp +kahoku.yamagata.jp +kaminoyama.yamagata.jp +kaneyama.yamagata.jp +kawanishi.yamagata.jp +mamurogawa.yamagata.jp +mikawa.yamagata.jp +murayama.yamagata.jp +nagai.yamagata.jp +nakayama.yamagata.jp +nanyo.yamagata.jp +nishikawa.yamagata.jp +obanazawa.yamagata.jp +oe.yamagata.jp +oguni.yamagata.jp +ohkura.yamagata.jp +oishida.yamagata.jp +sagae.yamagata.jp +sakata.yamagata.jp +sakegawa.yamagata.jp +shinjo.yamagata.jp +shirataka.yamagata.jp +shonai.yamagata.jp +takahata.yamagata.jp +tendo.yamagata.jp +tozawa.yamagata.jp +tsuruoka.yamagata.jp +yamagata.yamagata.jp +yamanobe.yamagata.jp +yonezawa.yamagata.jp +yuza.yamagata.jp +abu.yamaguchi.jp +hagi.yamaguchi.jp +hikari.yamaguchi.jp +hofu.yamaguchi.jp +iwakuni.yamaguchi.jp +kudamatsu.yamaguchi.jp +mitou.yamaguchi.jp +nagato.yamaguchi.jp +oshima.yamaguchi.jp +shimonoseki.yamaguchi.jp +shunan.yamaguchi.jp +tabuse.yamaguchi.jp +tokuyama.yamaguchi.jp +toyota.yamaguchi.jp +ube.yamaguchi.jp +yuu.yamaguchi.jp +chuo.yamanashi.jp +doshi.yamanashi.jp +fuefuki.yamanashi.jp +fujikawa.yamanashi.jp +fujikawaguchiko.yamanashi.jp +fujiyoshida.yamanashi.jp +hayakawa.yamanashi.jp +hokuto.yamanashi.jp +ichikawamisato.yamanashi.jp +kai.yamanashi.jp +kofu.yamanashi.jp +koshu.yamanashi.jp +kosuge.yamanashi.jp +minami-alps.yamanashi.jp +minobu.yamanashi.jp +nakamichi.yamanashi.jp +nanbu.yamanashi.jp +narusawa.yamanashi.jp +nirasaki.yamanashi.jp +nishikatsura.yamanashi.jp +oshino.yamanashi.jp +otsuki.yamanashi.jp +showa.yamanashi.jp +tabayama.yamanashi.jp +tsuru.yamanashi.jp +uenohara.yamanashi.jp +yamanakako.yamanashi.jp +yamanashi.yamanashi.jp + +// ke : http://www.kenic.or.ke/index.php/en/ke-domains/ke-domains +ke +ac.ke +co.ke +go.ke +info.ke +me.ke +mobi.ke +ne.ke +or.ke +sc.ke + +// kg : http://www.domain.kg/dmn_n.html +kg +org.kg +net.kg +com.kg +edu.kg +gov.kg +mil.kg + +// kh : http://www.mptc.gov.kh/dns_registration.htm +*.kh + +// ki : http://www.ki/dns/index.html +ki +edu.ki +biz.ki +net.ki +org.ki +gov.ki +info.ki +com.ki + +// km : https://www.iana.org/domains/root/db/km.html +// http://www.domaine.km/documents/charte.doc +km +org.km +nom.km +gov.km +prd.km +tm.km +edu.km +mil.km +ass.km +com.km +// These are only mentioned as proposed suggestions at domaine.km, but +// https://www.iana.org/domains/root/db/km.html says they're available for registration: +coop.km +asso.km +presse.km +medecin.km +notaires.km +pharmaciens.km +veterinaire.km +gouv.km + +// kn : https://www.iana.org/domains/root/db/kn.html +// http://www.dot.kn/domainRules.html +kn +net.kn +org.kn +edu.kn +gov.kn + +// kp : http://www.kcce.kp/en_index.php +kp +com.kp +edu.kp +gov.kp +org.kp +rep.kp +tra.kp + +// kr : https://www.iana.org/domains/root/db/kr.html +// see also: http://domain.nida.or.kr/eng/registration.jsp +kr +ac.kr +co.kr +es.kr +go.kr +hs.kr +kg.kr +mil.kr +ms.kr +ne.kr +or.kr +pe.kr +re.kr +sc.kr +// kr geographical names +busan.kr +chungbuk.kr +chungnam.kr +daegu.kr +daejeon.kr +gangwon.kr +gwangju.kr +gyeongbuk.kr +gyeonggi.kr +gyeongnam.kr +incheon.kr +jeju.kr +jeonbuk.kr +jeonnam.kr +seoul.kr +ulsan.kr + +// kw : https://www.nic.kw/policies/ +// Confirmed by registry +kw +com.kw +edu.kw +emb.kw +gov.kw +ind.kw +net.kw +org.kw + +// ky : http://www.icta.ky/da_ky_reg_dom.php +// Confirmed by registry 2008-06-17 +ky +com.ky +edu.ky +net.ky +org.ky + +// kz : https://www.iana.org/domains/root/db/kz.html +// see also: http://www.nic.kz/rules/index.jsp +kz +org.kz +edu.kz +net.kz +gov.kz +mil.kz +com.kz + +// la : https://www.iana.org/domains/root/db/la.html +// Submitted by registry +la +int.la +net.la +info.la +edu.la +gov.la +per.la +com.la +org.la + +// lb : https://www.iana.org/domains/root/db/lb.html +// Submitted by registry +lb +com.lb +edu.lb +gov.lb +net.lb +org.lb + +// lc : https://www.iana.org/domains/root/db/lc.html +// see also: http://www.nic.lc/rules.htm +lc +com.lc +net.lc +co.lc +org.lc +edu.lc +gov.lc + +// li : https://www.iana.org/domains/root/db/li.html +li + +// lk : https://www.nic.lk/index.php/domain-registration/lk-domain-naming-structure +lk +gov.lk +sch.lk +net.lk +int.lk +com.lk +org.lk +edu.lk +ngo.lk +soc.lk +web.lk +ltd.lk +assn.lk +grp.lk +hotel.lk +ac.lk + +// lr : http://psg.com/dns/lr/lr.txt +// Submitted by registry +lr +com.lr +edu.lr +gov.lr +org.lr +net.lr + +// ls : http://www.nic.ls/ +// Confirmed by registry +ls +ac.ls +biz.ls +co.ls +edu.ls +gov.ls +info.ls +net.ls +org.ls +sc.ls + +// lt : https://www.iana.org/domains/root/db/lt.html +lt +// gov.lt : http://www.gov.lt/index_en.php +gov.lt + +// lu : http://www.dns.lu/en/ +lu + +// lv : http://www.nic.lv/DNS/En/generic.php +lv +com.lv +edu.lv +gov.lv +org.lv +mil.lv +id.lv +net.lv +asn.lv +conf.lv + +// ly : http://www.nic.ly/regulations.php +ly +com.ly +net.ly +gov.ly +plc.ly +edu.ly +sch.ly +med.ly +org.ly +id.ly + +// ma : https://www.iana.org/domains/root/db/ma.html +// http://www.anrt.ma/fr/admin/download/upload/file_fr782.pdf +ma +co.ma +net.ma +gov.ma +org.ma +ac.ma +press.ma + +// mc : http://www.nic.mc/ +mc +tm.mc +asso.mc + +// md : https://www.iana.org/domains/root/db/md.html +md + +// me : https://www.iana.org/domains/root/db/me.html +me +co.me +net.me +org.me +edu.me +ac.me +gov.me +its.me +priv.me + +// mg : http://nic.mg/nicmg/?page_id=39 +mg +org.mg +nom.mg +gov.mg +prd.mg +tm.mg +edu.mg +mil.mg +com.mg +co.mg + +// mh : https://www.iana.org/domains/root/db/mh.html +mh + +// mil : https://www.iana.org/domains/root/db/mil.html +mil + +// mk : https://www.iana.org/domains/root/db/mk.html +// see also: http://dns.marnet.net.mk/postapka.php +mk +com.mk +org.mk +net.mk +edu.mk +gov.mk +inf.mk +name.mk + +// ml : http://www.gobin.info/domainname/ml-template.doc +// see also: https://www.iana.org/domains/root/db/ml.html +ml +com.ml +edu.ml +gouv.ml +gov.ml +net.ml +org.ml +presse.ml + +// mm : https://www.iana.org/domains/root/db/mm.html +*.mm + +// mn : https://www.iana.org/domains/root/db/mn.html +mn +gov.mn +edu.mn +org.mn + +// mo : http://www.monic.net.mo/ +mo +com.mo +net.mo +org.mo +edu.mo +gov.mo + +// mobi : https://www.iana.org/domains/root/db/mobi.html +mobi + +// mp : http://www.dot.mp/ +// Confirmed by registry 2008-06-17 +mp + +// mq : https://www.iana.org/domains/root/db/mq.html +mq + +// mr : https://www.iana.org/domains/root/db/mr.html +mr +gov.mr + +// ms : http://www.nic.ms/pdf/MS_Domain_Name_Rules.pdf +ms +com.ms +edu.ms +gov.ms +net.ms +org.ms + +// mt : https://www.nic.org.mt/go/policy +// Submitted by registry +mt +com.mt +edu.mt +net.mt +org.mt + +// mu : https://www.iana.org/domains/root/db/mu.html +mu +com.mu +net.mu +org.mu +gov.mu +ac.mu +co.mu +or.mu + +// museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/ +museum + +// mv : https://www.iana.org/domains/root/db/mv.html +// "mv" included because, contra Wikipedia, google.mv exists. +mv +aero.mv +biz.mv +com.mv +coop.mv +edu.mv +gov.mv +info.mv +int.mv +mil.mv +museum.mv +name.mv +net.mv +org.mv +pro.mv + +// mw : http://www.registrar.mw/ +mw +ac.mw +biz.mw +co.mw +com.mw +coop.mw +edu.mw +gov.mw +int.mw +net.mw +org.mw + +// mx : http://www.nic.mx/ +// Submitted by registry +mx +com.mx +org.mx +gob.mx +edu.mx +net.mx + +// my : http://www.mynic.my/ +// Available strings: https://mynic.my/resources/domains/buying-a-domain/ +my +biz.my +com.my +edu.my +gov.my +mil.my +name.my +net.my +org.my + +// mz : http://www.uem.mz/ +// Submitted by registry +mz +ac.mz +adv.mz +co.mz +edu.mz +gov.mz +mil.mz +net.mz +org.mz + +// na : http://www.na-nic.com.na/ +na +alt.na +co.na +com.na +gov.na +net.na +org.na + +// name : has 2nd-level tlds, but there's no list of them +name + +// nc : http://www.cctld.nc/ +nc +asso.nc +nom.nc + +// ne : https://www.iana.org/domains/root/db/ne.html +ne + +// net : https://www.iana.org/domains/root/db/net.html +net + +// nf : https://www.iana.org/domains/root/db/nf.html +nf +com.nf +net.nf +per.nf +rec.nf +web.nf +arts.nf +firm.nf +info.nf +other.nf +store.nf + +// ng : http://www.nira.org.ng/index.php/join-us/register-ng-domain/189-nira-slds +ng +com.ng +edu.ng +gov.ng +i.ng +mil.ng +mobi.ng +name.ng +net.ng +org.ng +sch.ng + +// ni : http://www.nic.ni/ +ni +ac.ni +biz.ni +co.ni +com.ni +edu.ni +gob.ni +in.ni +info.ni +int.ni +mil.ni +net.ni +nom.ni +org.ni +web.ni + +// nl : https://www.iana.org/domains/root/db/nl.html +// https://www.sidn.nl/ +// ccTLD for the Netherlands +nl + +// no : https://www.norid.no/en/om-domenenavn/regelverk-for-no/ +// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ +// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ +// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ +// RSS feed: https://teknisk.norid.no/en/feed/ +no +// Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ +fhs.no +vgs.no +fylkesbibl.no +folkebibl.no +museum.no +idrett.no +priv.no +// Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ +mil.no +stat.no +dep.no +kommune.no +herad.no +// Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ +// counties +aa.no +ah.no +bu.no +fm.no +hl.no +hm.no +jan-mayen.no +mr.no +nl.no +nt.no +of.no +ol.no +oslo.no +rl.no +sf.no +st.no +svalbard.no +tm.no +tr.no +va.no +vf.no +// primary and lower secondary schools per county +gs.aa.no +gs.ah.no +gs.bu.no +gs.fm.no +gs.hl.no +gs.hm.no +gs.jan-mayen.no +gs.mr.no +gs.nl.no +gs.nt.no +gs.of.no +gs.ol.no +gs.oslo.no +gs.rl.no +gs.sf.no +gs.st.no +gs.svalbard.no +gs.tm.no +gs.tr.no +gs.va.no +gs.vf.no +// cities +akrehamn.no +åkrehamn.no +algard.no +ålgård.no +arna.no +brumunddal.no +bryne.no +bronnoysund.no +brønnøysund.no +drobak.no +drøbak.no +egersund.no +fetsund.no +floro.no +florø.no +fredrikstad.no +hokksund.no +honefoss.no +hønefoss.no +jessheim.no +jorpeland.no +jørpeland.no +kirkenes.no +kopervik.no +krokstadelva.no +langevag.no +langevåg.no +leirvik.no +mjondalen.no +mjøndalen.no +mo-i-rana.no +mosjoen.no +mosjøen.no +nesoddtangen.no +orkanger.no +osoyro.no +osøyro.no +raholt.no +råholt.no +sandnessjoen.no +sandnessjøen.no +skedsmokorset.no +slattum.no +spjelkavik.no +stathelle.no +stavern.no +stjordalshalsen.no +stjørdalshalsen.no +tananger.no +tranby.no +vossevangen.no +// communities +afjord.no +åfjord.no +agdenes.no +al.no +ål.no +alesund.no +ålesund.no +alstahaug.no +alta.no +áltá.no +alaheadju.no +álaheadju.no +alvdal.no +amli.no +åmli.no +amot.no +åmot.no +andebu.no +andoy.no +andøy.no +andasuolo.no +ardal.no +årdal.no +aremark.no +arendal.no +ås.no +aseral.no +åseral.no +asker.no +askim.no +askvoll.no +askoy.no +askøy.no +asnes.no +åsnes.no +audnedaln.no +aukra.no +aure.no +aurland.no +aurskog-holand.no +aurskog-høland.no +austevoll.no +austrheim.no +averoy.no +averøy.no +balestrand.no +ballangen.no +balat.no +bálát.no +balsfjord.no +bahccavuotna.no +báhccavuotna.no +bamble.no +bardu.no +beardu.no +beiarn.no +bajddar.no +bájddar.no +baidar.no +báidár.no +berg.no +bergen.no +berlevag.no +berlevåg.no +bearalvahki.no +bearalváhki.no +bindal.no +birkenes.no +bjarkoy.no +bjarkøy.no +bjerkreim.no +bjugn.no +bodo.no +bodø.no +badaddja.no +bådåddjå.no +budejju.no +bokn.no +bremanger.no +bronnoy.no +brønnøy.no +bygland.no +bykle.no +barum.no +bærum.no +bo.telemark.no +bø.telemark.no +bo.nordland.no +bø.nordland.no +bievat.no +bievát.no +bomlo.no +bømlo.no +batsfjord.no +båtsfjord.no +bahcavuotna.no +báhcavuotna.no +dovre.no +drammen.no +drangedal.no +dyroy.no +dyrøy.no +donna.no +dønna.no +eid.no +eidfjord.no +eidsberg.no +eidskog.no +eidsvoll.no +eigersund.no +elverum.no +enebakk.no +engerdal.no +etne.no +etnedal.no +evenes.no +evenassi.no +evenášši.no +evje-og-hornnes.no +farsund.no +fauske.no +fuossko.no +fuoisku.no +fedje.no +fet.no +finnoy.no +finnøy.no +fitjar.no +fjaler.no +fjell.no +flakstad.no +flatanger.no +flekkefjord.no +flesberg.no +flora.no +fla.no +flå.no +folldal.no +forsand.no +fosnes.no +frei.no +frogn.no +froland.no +frosta.no +frana.no +fræna.no +froya.no +frøya.no +fusa.no +fyresdal.no +forde.no +førde.no +gamvik.no +gangaviika.no +gáŋgaviika.no +gaular.no +gausdal.no +gildeskal.no +gildeskål.no +giske.no +gjemnes.no +gjerdrum.no +gjerstad.no +gjesdal.no +gjovik.no +gjøvik.no +gloppen.no +gol.no +gran.no +grane.no +granvin.no +gratangen.no +grimstad.no +grong.no +kraanghke.no +kråanghke.no +grue.no +gulen.no +hadsel.no +halden.no +halsa.no +hamar.no +hamaroy.no +habmer.no +hábmer.no +hapmir.no +hápmir.no +hammerfest.no +hammarfeasta.no +hámmárfeasta.no +haram.no +hareid.no +harstad.no +hasvik.no +aknoluokta.no +ákŋoluokta.no +hattfjelldal.no +aarborte.no +haugesund.no +hemne.no +hemnes.no +hemsedal.no +heroy.more-og-romsdal.no +herøy.møre-og-romsdal.no +heroy.nordland.no +herøy.nordland.no +hitra.no +hjartdal.no +hjelmeland.no +hobol.no +hobøl.no +hof.no +hol.no +hole.no +holmestrand.no +holtalen.no +holtålen.no +hornindal.no +horten.no +hurdal.no +hurum.no +hvaler.no +hyllestad.no +hagebostad.no +hægebostad.no +hoyanger.no +høyanger.no +hoylandet.no +høylandet.no +ha.no +hå.no +ibestad.no +inderoy.no +inderøy.no +iveland.no +jevnaker.no +jondal.no +jolster.no +jølster.no +karasjok.no +karasjohka.no +kárášjohka.no +karlsoy.no +galsa.no +gálsá.no +karmoy.no +karmøy.no +kautokeino.no +guovdageaidnu.no +klepp.no +klabu.no +klæbu.no +kongsberg.no +kongsvinger.no +kragero.no +kragerø.no +kristiansand.no +kristiansund.no +krodsherad.no +krødsherad.no +kvalsund.no +rahkkeravju.no +ráhkkerávju.no +kvam.no +kvinesdal.no +kvinnherad.no +kviteseid.no +kvitsoy.no +kvitsøy.no +kvafjord.no +kvæfjord.no +giehtavuoatna.no +kvanangen.no +kvænangen.no +navuotna.no +návuotna.no +kafjord.no +kåfjord.no +gaivuotna.no +gáivuotna.no +larvik.no +lavangen.no +lavagis.no +loabat.no +loabát.no +lebesby.no +davvesiida.no +leikanger.no +leirfjord.no +leka.no +leksvik.no +lenvik.no +leangaviika.no +leaŋgaviika.no +lesja.no +levanger.no +lier.no +lierne.no +lillehammer.no +lillesand.no +lindesnes.no +lindas.no +lindås.no +lom.no +loppa.no +lahppi.no +láhppi.no +lund.no +lunner.no +luroy.no +lurøy.no +luster.no +lyngdal.no +lyngen.no +ivgu.no +lardal.no +lerdal.no +lærdal.no +lodingen.no +lødingen.no +lorenskog.no +lørenskog.no +loten.no +løten.no +malvik.no +masoy.no +måsøy.no +muosat.no +muosát.no +mandal.no +marker.no +marnardal.no +masfjorden.no +meland.no +meldal.no +melhus.no +meloy.no +meløy.no +meraker.no +meråker.no +moareke.no +moåreke.no +midsund.no +midtre-gauldal.no +modalen.no +modum.no +molde.no +moskenes.no +moss.no +mosvik.no +malselv.no +målselv.no +malatvuopmi.no +málatvuopmi.no +namdalseid.no +aejrie.no +namsos.no +namsskogan.no +naamesjevuemie.no +nååmesjevuemie.no +laakesvuemie.no +nannestad.no +narvik.no +narviika.no +naustdal.no +nedre-eiker.no +nes.akershus.no +nes.buskerud.no +nesna.no +nesodden.no +nesseby.no +unjarga.no +unjárga.no +nesset.no +nissedal.no +nittedal.no +nord-aurdal.no +nord-fron.no +nord-odal.no +norddal.no +nordkapp.no +davvenjarga.no +davvenjárga.no +nordre-land.no +nordreisa.no +raisa.no +ráisa.no +nore-og-uvdal.no +notodden.no +naroy.no +nærøy.no +notteroy.no +nøtterøy.no +odda.no +oksnes.no +øksnes.no +oppdal.no +oppegard.no +oppegård.no +orkdal.no +orland.no +ørland.no +orskog.no +ørskog.no +orsta.no +ørsta.no +os.hedmark.no +os.hordaland.no +osen.no +osteroy.no +osterøy.no +ostre-toten.no +østre-toten.no +overhalla.no +ovre-eiker.no +øvre-eiker.no +oyer.no +øyer.no +oygarden.no +øygarden.no +oystre-slidre.no +øystre-slidre.no +porsanger.no +porsangu.no +porsáŋgu.no +porsgrunn.no +radoy.no +radøy.no +rakkestad.no +rana.no +ruovat.no +randaberg.no +rauma.no +rendalen.no +rennebu.no +rennesoy.no +rennesøy.no +rindal.no +ringebu.no +ringerike.no +ringsaker.no +rissa.no +risor.no +risør.no +roan.no +rollag.no +rygge.no +ralingen.no +rælingen.no +rodoy.no +rødøy.no +romskog.no +rømskog.no +roros.no +røros.no +rost.no +røst.no +royken.no +røyken.no +royrvik.no +røyrvik.no +rade.no +råde.no +salangen.no +siellak.no +saltdal.no +salat.no +sálát.no +sálat.no +samnanger.no +sande.more-og-romsdal.no +sande.møre-og-romsdal.no +sande.vestfold.no +sandefjord.no +sandnes.no +sandoy.no +sandøy.no +sarpsborg.no +sauda.no +sauherad.no +sel.no +selbu.no +selje.no +seljord.no +sigdal.no +siljan.no +sirdal.no +skaun.no +skedsmo.no +ski.no +skien.no +skiptvet.no +skjervoy.no +skjervøy.no +skierva.no +skiervá.no +skjak.no +skjåk.no +skodje.no +skanland.no +skånland.no +skanit.no +skánit.no +smola.no +smøla.no +snillfjord.no +snasa.no +snåsa.no +snoasa.no +snaase.no +snåase.no +sogndal.no +sokndal.no +sola.no +solund.no +songdalen.no +sortland.no +spydeberg.no +stange.no +stavanger.no +steigen.no +steinkjer.no +stjordal.no +stjørdal.no +stokke.no +stor-elvdal.no +stord.no +stordal.no +storfjord.no +omasvuotna.no +strand.no +stranda.no +stryn.no +sula.no +suldal.no +sund.no +sunndal.no +surnadal.no +sveio.no +svelvik.no +sykkylven.no +sogne.no +søgne.no +somna.no +sømna.no +sondre-land.no +søndre-land.no +sor-aurdal.no +sør-aurdal.no +sor-fron.no +sør-fron.no +sor-odal.no +sør-odal.no +sor-varanger.no +sør-varanger.no +matta-varjjat.no +mátta-várjjat.no +sorfold.no +sørfold.no +sorreisa.no +sørreisa.no +sorum.no +sørum.no +tana.no +deatnu.no +time.no +tingvoll.no +tinn.no +tjeldsund.no +dielddanuorri.no +tjome.no +tjøme.no +tokke.no +tolga.no +torsken.no +tranoy.no +tranøy.no +tromso.no +tromsø.no +tromsa.no +romsa.no +trondheim.no +troandin.no +trysil.no +trana.no +træna.no +trogstad.no +trøgstad.no +tvedestrand.no +tydal.no +tynset.no +tysfjord.no +divtasvuodna.no +divttasvuotna.no +tysnes.no +tysvar.no +tysvær.no +tonsberg.no +tønsberg.no +ullensaker.no +ullensvang.no +ulvik.no +utsira.no +vadso.no +vadsø.no +cahcesuolo.no +čáhcesuolo.no +vaksdal.no +valle.no +vang.no +vanylven.no +vardo.no +vardø.no +varggat.no +várggát.no +vefsn.no +vaapste.no +vega.no +vegarshei.no +vegårshei.no +vennesla.no +verdal.no +verran.no +vestby.no +vestnes.no +vestre-slidre.no +vestre-toten.no +vestvagoy.no +vestvågøy.no +vevelstad.no +vik.no +vikna.no +vindafjord.no +volda.no +voss.no +varoy.no +værøy.no +vagan.no +vågan.no +voagat.no +vagsoy.no +vågsøy.no +vaga.no +vågå.no +valer.ostfold.no +våler.østfold.no +valer.hedmark.no +våler.hedmark.no + +// np : http://www.mos.com.np/register.html +*.np + +// nr : http://cenpac.net.nr/dns/index.html +// Submitted by registry +nr +biz.nr +info.nr +gov.nr +edu.nr +org.nr +net.nr +com.nr + +// nu : https://www.iana.org/domains/root/db/nu.html +nu + +// nz : https://www.iana.org/domains/root/db/nz.html +// Submitted by registry +nz +ac.nz +co.nz +cri.nz +geek.nz +gen.nz +govt.nz +health.nz +iwi.nz +kiwi.nz +maori.nz +mil.nz +māori.nz +net.nz +org.nz +parliament.nz +school.nz + +// om : https://www.iana.org/domains/root/db/om.html +om +co.om +com.om +edu.om +gov.om +med.om +museum.om +net.om +org.om +pro.om + +// onion : https://tools.ietf.org/html/rfc7686 +onion + +// org : https://www.iana.org/domains/root/db/org.html +org + +// pa : http://www.nic.pa/ +// Some additional second level "domains" resolve directly as hostnames, such as +// pannet.pa, so we add a rule for "pa". +pa +ac.pa +gob.pa +com.pa +org.pa +sld.pa +edu.pa +net.pa +ing.pa +abo.pa +med.pa +nom.pa + +// pe : https://www.nic.pe/InformeFinalComision.pdf +pe +edu.pe +gob.pe +nom.pe +mil.pe +org.pe +com.pe +net.pe + +// pf : http://www.gobin.info/domainname/formulaire-pf.pdf +pf +com.pf +org.pf +edu.pf + +// pg : https://www.iana.org/domains/root/db/pg.html +*.pg + +// ph : http://www.domains.ph/FAQ2.asp +// Submitted by registry +ph +com.ph +net.ph +org.ph +gov.ph +edu.ph +ngo.ph +mil.ph +i.ph + +// pk : https://pknic.net.pk +// pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK + grandfathered old gon.pk +// Contact Email: staff@pknic.net.pk PKNIC .PK Registry + +pk +ac.pk +biz.pk +com.pk +edu.pk +fam.pk +gkp.pk +gob.pk +gog.pk +gok.pk +gon.pk +gop.pk +gos.pk +gov.pk +net.pk +org.pk +web.pk + +// pl http://www.dns.pl/english/index.html +// Submitted by registry +pl +com.pl +net.pl +org.pl +// pl functional domains (http://www.dns.pl/english/index.html) +aid.pl +agro.pl +atm.pl +auto.pl +biz.pl +edu.pl +gmina.pl +gsm.pl +info.pl +mail.pl +miasta.pl +media.pl +mil.pl +nieruchomosci.pl +nom.pl +pc.pl +powiat.pl +priv.pl +realestate.pl +rel.pl +sex.pl +shop.pl +sklep.pl +sos.pl +szkola.pl +targi.pl +tm.pl +tourism.pl +travel.pl +turystyka.pl +// Government domains +gov.pl +ap.gov.pl +griw.gov.pl +ic.gov.pl +is.gov.pl +kmpsp.gov.pl +konsulat.gov.pl +kppsp.gov.pl +kwp.gov.pl +kwpsp.gov.pl +mup.gov.pl +mw.gov.pl +oia.gov.pl +oirm.gov.pl +oke.gov.pl +oow.gov.pl +oschr.gov.pl +oum.gov.pl +pa.gov.pl +pinb.gov.pl +piw.gov.pl +po.gov.pl +pr.gov.pl +psp.gov.pl +psse.gov.pl +pup.gov.pl +rzgw.gov.pl +sa.gov.pl +sdn.gov.pl +sko.gov.pl +so.gov.pl +sr.gov.pl +starostwo.gov.pl +ug.gov.pl +ugim.gov.pl +um.gov.pl +umig.gov.pl +upow.gov.pl +uppo.gov.pl +us.gov.pl +uw.gov.pl +uzs.gov.pl +wif.gov.pl +wiih.gov.pl +winb.gov.pl +wios.gov.pl +witd.gov.pl +wiw.gov.pl +wkz.gov.pl +wsa.gov.pl +wskr.gov.pl +wsse.gov.pl +wuoz.gov.pl +wzmiuw.gov.pl +zp.gov.pl +zpisdn.gov.pl +// pl regional domains (http://www.dns.pl/english/index.html) +augustow.pl +babia-gora.pl +bedzin.pl +beskidy.pl +bialowieza.pl +bialystok.pl +bielawa.pl +bieszczady.pl +boleslawiec.pl +bydgoszcz.pl +bytom.pl +cieszyn.pl +czeladz.pl +czest.pl +dlugoleka.pl +elblag.pl +elk.pl +glogow.pl +gniezno.pl +gorlice.pl +grajewo.pl +ilawa.pl +jaworzno.pl +jelenia-gora.pl +jgora.pl +kalisz.pl +kazimierz-dolny.pl +karpacz.pl +kartuzy.pl +kaszuby.pl +katowice.pl +kepno.pl +ketrzyn.pl +klodzko.pl +kobierzyce.pl +kolobrzeg.pl +konin.pl +konskowola.pl +kutno.pl +lapy.pl +lebork.pl +legnica.pl +lezajsk.pl +limanowa.pl +lomza.pl +lowicz.pl +lubin.pl +lukow.pl +malbork.pl +malopolska.pl +mazowsze.pl +mazury.pl +mielec.pl +mielno.pl +mragowo.pl +naklo.pl +nowaruda.pl +nysa.pl +olawa.pl +olecko.pl +olkusz.pl +olsztyn.pl +opoczno.pl +opole.pl +ostroda.pl +ostroleka.pl +ostrowiec.pl +ostrowwlkp.pl +pila.pl +pisz.pl +podhale.pl +podlasie.pl +polkowice.pl +pomorze.pl +pomorskie.pl +prochowice.pl +pruszkow.pl +przeworsk.pl +pulawy.pl +radom.pl +rawa-maz.pl +rybnik.pl +rzeszow.pl +sanok.pl +sejny.pl +slask.pl +slupsk.pl +sosnowiec.pl +stalowa-wola.pl +skoczow.pl +starachowice.pl +stargard.pl +suwalki.pl +swidnica.pl +swiebodzin.pl +swinoujscie.pl +szczecin.pl +szczytno.pl +tarnobrzeg.pl +tgory.pl +turek.pl +tychy.pl +ustka.pl +walbrzych.pl +warmia.pl +warszawa.pl +waw.pl +wegrow.pl +wielun.pl +wlocl.pl +wloclawek.pl +wodzislaw.pl +wolomin.pl +wroclaw.pl +zachpomor.pl +zagan.pl +zarow.pl +zgora.pl +zgorzelec.pl + +// pm : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +pm + +// pn : http://www.government.pn/PnRegistry/policies.htm +pn +gov.pn +co.pn +org.pn +edu.pn +net.pn + +// post : https://www.iana.org/domains/root/db/post.html +post + +// pr : http://www.nic.pr/index.asp?f=1 +pr +com.pr +net.pr +org.pr +gov.pr +edu.pr +isla.pr +pro.pr +biz.pr +info.pr +name.pr +// these aren't mentioned on nic.pr, but on https://www.iana.org/domains/root/db/pr.html +est.pr +prof.pr +ac.pr + +// pro : http://registry.pro/get-pro +pro +aaa.pro +aca.pro +acct.pro +avocat.pro +bar.pro +cpa.pro +eng.pro +jur.pro +law.pro +med.pro +recht.pro + +// ps : https://www.iana.org/domains/root/db/ps.html +// http://www.nic.ps/registration/policy.html#reg +ps +edu.ps +gov.ps +sec.ps +plo.ps +com.ps +org.ps +net.ps + +// pt : https://www.dns.pt/en/domain/pt-terms-and-conditions-registration-rules/ +pt +net.pt +gov.pt +org.pt +edu.pt +int.pt +publ.pt +com.pt +nome.pt + +// pw : https://www.iana.org/domains/root/db/pw.html +pw +co.pw +or.pw +ed.pw +go.pw +belau.pw + +// py : http://www.nic.py/pautas.html#seccion_9 +// Submitted by registry +py +com.py +coop.py +edu.py +gov.py +mil.py +net.py +org.py + +// qa : http://domains.qa/en/ +qa +com.qa +edu.qa +gov.qa +mil.qa +name.qa +net.qa +org.qa +sch.qa + +// re : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +re +asso.re +com.re +nom.re + +// ro : http://www.rotld.ro/ +ro +arts.ro +com.ro +firm.ro +info.ro +nom.ro +nt.ro +org.ro +rec.ro +store.ro +tm.ro +www.ro + +// rs : https://www.rnids.rs/en/domains/national-domains +rs +ac.rs +co.rs +edu.rs +gov.rs +in.rs +org.rs + +// ru : https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf +// Submitted by George Georgievsky +ru + +// rw : https://www.ricta.org.rw/sites/default/files/resources/registry_registrar_contract_0.pdf +rw +ac.rw +co.rw +coop.rw +gov.rw +mil.rw +net.rw +org.rw + +// sa : http://www.nic.net.sa/ +sa +com.sa +net.sa +org.sa +gov.sa +med.sa +pub.sa +edu.sa +sch.sa + +// sb : http://www.sbnic.net.sb/ +// Submitted by registry +sb +com.sb +edu.sb +gov.sb +net.sb +org.sb + +// sc : http://www.nic.sc/ +sc +com.sc +gov.sc +net.sc +org.sc +edu.sc + +// sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm +// Submitted by registry +sd +com.sd +net.sd +org.sd +edu.sd +med.sd +tv.sd +gov.sd +info.sd + +// se : https://www.iana.org/domains/root/db/se.html +// Submitted by registry +se +a.se +ac.se +b.se +bd.se +brand.se +c.se +d.se +e.se +f.se +fh.se +fhsk.se +fhv.se +g.se +h.se +i.se +k.se +komforb.se +kommunalforbund.se +komvux.se +l.se +lanbib.se +m.se +n.se +naturbruksgymn.se +o.se +org.se +p.se +parti.se +pp.se +press.se +r.se +s.se +t.se +tm.se +u.se +w.se +x.se +y.se +z.se + +// sg : http://www.nic.net.sg/page/registration-policies-procedures-and-guidelines +sg +com.sg +net.sg +org.sg +gov.sg +edu.sg +per.sg + +// sh : http://nic.sh/rules.htm +sh +com.sh +net.sh +gov.sh +org.sh +mil.sh + +// si : https://www.iana.org/domains/root/db/si.html +si + +// sj : No registrations at this time. +// Submitted by registry +sj + +// sk : https://www.iana.org/domains/root/db/sk.html +sk + +// sl : http://www.nic.sl +// Submitted by registry +sl +com.sl +net.sl +edu.sl +gov.sl +org.sl + +// sm : https://www.iana.org/domains/root/db/sm.html +sm + +// sn : https://www.iana.org/domains/root/db/sn.html +sn +art.sn +com.sn +edu.sn +gouv.sn +org.sn +perso.sn +univ.sn + +// so : http://sonic.so/policies/ +so +com.so +edu.so +gov.so +me.so +net.so +org.so + +// sr : https://www.iana.org/domains/root/db/sr.html +sr + +// ss : https://registry.nic.ss/ +// Submitted by registry +ss +biz.ss +co.ss +com.ss +edu.ss +gov.ss +me.ss +net.ss +org.ss +sch.ss + +// st : http://www.nic.st/html/policyrules/ +st +co.st +com.st +consulado.st +edu.st +embaixada.st +mil.st +net.st +org.st +principe.st +saotome.st +store.st + +// su : https://www.iana.org/domains/root/db/su.html +su + +// sv : http://www.svnet.org.sv/niveldos.pdf +sv +com.sv +edu.sv +gob.sv +org.sv +red.sv + +// sx : https://www.iana.org/domains/root/db/sx.html +// Submitted by registry +sx +gov.sx + +// sy : https://www.iana.org/domains/root/db/sy.html +// see also: http://www.gobin.info/domainname/sy.doc +sy +edu.sy +gov.sy +net.sy +mil.sy +com.sy +org.sy + +// sz : https://www.iana.org/domains/root/db/sz.html +// http://www.sispa.org.sz/ +sz +co.sz +ac.sz +org.sz + +// tc : https://www.iana.org/domains/root/db/tc.html +tc + +// td : https://www.iana.org/domains/root/db/td.html +td + +// tel: https://www.iana.org/domains/root/db/tel.html +// http://www.telnic.org/ +tel + +// tf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +tf + +// tg : https://www.iana.org/domains/root/db/tg.html +// http://www.nic.tg/ +tg + +// th : https://www.iana.org/domains/root/db/th.html +// Submitted by registry +th +ac.th +co.th +go.th +in.th +mi.th +net.th +or.th + +// tj : http://www.nic.tj/policy.html +tj +ac.tj +biz.tj +co.tj +com.tj +edu.tj +go.tj +gov.tj +int.tj +mil.tj +name.tj +net.tj +nic.tj +org.tj +test.tj +web.tj + +// tk : https://www.iana.org/domains/root/db/tk.html +tk + +// tl : https://www.iana.org/domains/root/db/tl.html +tl +gov.tl + +// tm : http://www.nic.tm/local.html +tm +com.tm +co.tm +org.tm +net.tm +nom.tm +gov.tm +mil.tm +edu.tm + +// tn : http://www.registre.tn/fr/ +// https://whois.ati.tn/ +tn +com.tn +ens.tn +fin.tn +gov.tn +ind.tn +info.tn +intl.tn +mincom.tn +nat.tn +net.tn +org.tn +perso.tn +tourism.tn + +// to : https://www.iana.org/domains/root/db/to.html +// Submitted by registry +to +com.to +gov.to +net.to +org.to +edu.to +mil.to + +// tr : https://nic.tr/ +// https://nic.tr/forms/eng/policies.pdf +// https://nic.tr/index.php?USRACTN=PRICELST +tr +av.tr +bbs.tr +bel.tr +biz.tr +com.tr +dr.tr +edu.tr +gen.tr +gov.tr +info.tr +mil.tr +k12.tr +kep.tr +name.tr +net.tr +org.tr +pol.tr +tel.tr +tsk.tr +tv.tr +web.tr +// Used by Northern Cyprus +nc.tr +// Used by government agencies of Northern Cyprus +gov.nc.tr + +// tt : http://www.nic.tt/ +tt +co.tt +com.tt +org.tt +net.tt +biz.tt +info.tt +pro.tt +int.tt +coop.tt +jobs.tt +mobi.tt +travel.tt +museum.tt +aero.tt +name.tt +gov.tt +edu.tt + +// tv : https://www.iana.org/domains/root/db/tv.html +// Not listing any 2LDs as reserved since none seem to exist in practice, +// Wikipedia notwithstanding. +tv + +// tw : https://www.iana.org/domains/root/db/tw.html +tw +edu.tw +gov.tw +mil.tw +com.tw +net.tw +org.tw +idv.tw +game.tw +ebiz.tw +club.tw +網路.tw +組織.tw +商業.tw + +// tz : http://www.tznic.or.tz/index.php/domains +// Submitted by registry +tz +ac.tz +co.tz +go.tz +hotel.tz +info.tz +me.tz +mil.tz +mobi.tz +ne.tz +or.tz +sc.tz +tv.tz + +// ua : https://hostmaster.ua/policy/?ua +// Submitted by registry +ua +// ua 2LD +com.ua +edu.ua +gov.ua +in.ua +net.ua +org.ua +// ua geographic names +// https://hostmaster.ua/2ld/ +cherkassy.ua +cherkasy.ua +chernigov.ua +chernihiv.ua +chernivtsi.ua +chernovtsy.ua +ck.ua +cn.ua +cr.ua +crimea.ua +cv.ua +dn.ua +dnepropetrovsk.ua +dnipropetrovsk.ua +donetsk.ua +dp.ua +if.ua +ivano-frankivsk.ua +kh.ua +kharkiv.ua +kharkov.ua +kherson.ua +khmelnitskiy.ua +khmelnytskyi.ua +kiev.ua +kirovograd.ua +km.ua +kr.ua +kropyvnytskyi.ua +krym.ua +ks.ua +kv.ua +kyiv.ua +lg.ua +lt.ua +lugansk.ua +luhansk.ua +lutsk.ua +lv.ua +lviv.ua +mk.ua +mykolaiv.ua +nikolaev.ua +od.ua +odesa.ua +odessa.ua +pl.ua +poltava.ua +rivne.ua +rovno.ua +rv.ua +sb.ua +sebastopol.ua +sevastopol.ua +sm.ua +sumy.ua +te.ua +ternopil.ua +uz.ua +uzhgorod.ua +uzhhorod.ua +vinnica.ua +vinnytsia.ua +vn.ua +volyn.ua +yalta.ua +zakarpattia.ua +zaporizhzhe.ua +zaporizhzhia.ua +zhitomir.ua +zhytomyr.ua +zp.ua +zt.ua + +// ug : https://www.registry.co.ug/ +ug +co.ug +or.ug +ac.ug +sc.ug +go.ug +ne.ug +com.ug +org.ug + +// uk : https://www.iana.org/domains/root/db/uk.html +// Submitted by registry +uk +ac.uk +co.uk +gov.uk +ltd.uk +me.uk +net.uk +nhs.uk +org.uk +plc.uk +police.uk +*.sch.uk + +// us : https://www.iana.org/domains/root/db/us.html +us +dni.us +fed.us +isa.us +kids.us +nsn.us +// us geographic names +ak.us +al.us +ar.us +as.us +az.us +ca.us +co.us +ct.us +dc.us +de.us +fl.us +ga.us +gu.us +hi.us +ia.us +id.us +il.us +in.us +ks.us +ky.us +la.us +ma.us +md.us +me.us +mi.us +mn.us +mo.us +ms.us +mt.us +nc.us +nd.us +ne.us +nh.us +nj.us +nm.us +nv.us +ny.us +oh.us +ok.us +or.us +pa.us +pr.us +ri.us +sc.us +sd.us +tn.us +tx.us +ut.us +vi.us +vt.us +va.us +wa.us +wi.us +wv.us +wy.us +// The registrar notes several more specific domains available in each state, +// such as state.*.us, dst.*.us, etc., but resolution of these is somewhat +// haphazard; in some states these domains resolve as addresses, while in others +// only subdomains are available, or even nothing at all. We include the +// most common ones where it's clear that different sites are different +// entities. +k12.ak.us +k12.al.us +k12.ar.us +k12.as.us +k12.az.us +k12.ca.us +k12.co.us +k12.ct.us +k12.dc.us +k12.fl.us +k12.ga.us +k12.gu.us +// k12.hi.us Bug 614565 - Hawaii has a state-wide DOE login +k12.ia.us +k12.id.us +k12.il.us +k12.in.us +k12.ks.us +k12.ky.us +k12.la.us +k12.ma.us +k12.md.us +k12.me.us +k12.mi.us +k12.mn.us +k12.mo.us +k12.ms.us +k12.mt.us +k12.nc.us +// k12.nd.us Bug 1028347 - Removed at request of Travis Rosso +k12.ne.us +k12.nh.us +k12.nj.us +k12.nm.us +k12.nv.us +k12.ny.us +k12.oh.us +k12.ok.us +k12.or.us +k12.pa.us +k12.pr.us +// k12.ri.us Removed at request of Kim Cournoyer +k12.sc.us +// k12.sd.us Bug 934131 - Removed at request of James Booze +k12.tn.us +k12.tx.us +k12.ut.us +k12.vi.us +k12.vt.us +k12.va.us +k12.wa.us +k12.wi.us +// k12.wv.us Bug 947705 - Removed at request of Verne Britton +k12.wy.us +cc.ak.us +cc.al.us +cc.ar.us +cc.as.us +cc.az.us +cc.ca.us +cc.co.us +cc.ct.us +cc.dc.us +cc.de.us +cc.fl.us +cc.ga.us +cc.gu.us +cc.hi.us +cc.ia.us +cc.id.us +cc.il.us +cc.in.us +cc.ks.us +cc.ky.us +cc.la.us +cc.ma.us +cc.md.us +cc.me.us +cc.mi.us +cc.mn.us +cc.mo.us +cc.ms.us +cc.mt.us +cc.nc.us +cc.nd.us +cc.ne.us +cc.nh.us +cc.nj.us +cc.nm.us +cc.nv.us +cc.ny.us +cc.oh.us +cc.ok.us +cc.or.us +cc.pa.us +cc.pr.us +cc.ri.us +cc.sc.us +cc.sd.us +cc.tn.us +cc.tx.us +cc.ut.us +cc.vi.us +cc.vt.us +cc.va.us +cc.wa.us +cc.wi.us +cc.wv.us +cc.wy.us +lib.ak.us +lib.al.us +lib.ar.us +lib.as.us +lib.az.us +lib.ca.us +lib.co.us +lib.ct.us +lib.dc.us +// lib.de.us Issue #243 - Moved to Private section at request of Ed Moore +lib.fl.us +lib.ga.us +lib.gu.us +lib.hi.us +lib.ia.us +lib.id.us +lib.il.us +lib.in.us +lib.ks.us +lib.ky.us +lib.la.us +lib.ma.us +lib.md.us +lib.me.us +lib.mi.us +lib.mn.us +lib.mo.us +lib.ms.us +lib.mt.us +lib.nc.us +lib.nd.us +lib.ne.us +lib.nh.us +lib.nj.us +lib.nm.us +lib.nv.us +lib.ny.us +lib.oh.us +lib.ok.us +lib.or.us +lib.pa.us +lib.pr.us +lib.ri.us +lib.sc.us +lib.sd.us +lib.tn.us +lib.tx.us +lib.ut.us +lib.vi.us +lib.vt.us +lib.va.us +lib.wa.us +lib.wi.us +// lib.wv.us Bug 941670 - Removed at request of Larry W Arnold +lib.wy.us +// k12.ma.us contains school districts in Massachusetts. The 4LDs are +// managed independently except for private (PVT), charter (CHTR) and +// parochial (PAROCH) schools. Those are delegated directly to the +// 5LD operators. +pvt.k12.ma.us +chtr.k12.ma.us +paroch.k12.ma.us +// Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following +// see also: http://domreg.merit.edu +// see also: whois -h whois.domreg.merit.edu help +ann-arbor.mi.us +cog.mi.us +dst.mi.us +eaton.mi.us +gen.mi.us +mus.mi.us +tec.mi.us +washtenaw.mi.us + +// uy : http://www.nic.org.uy/ +uy +com.uy +edu.uy +gub.uy +mil.uy +net.uy +org.uy + +// uz : http://www.reg.uz/ +uz +co.uz +com.uz +net.uz +org.uz + +// va : https://www.iana.org/domains/root/db/va.html +va + +// vc : https://www.iana.org/domains/root/db/vc.html +// Submitted by registry +vc +com.vc +net.vc +org.vc +gov.vc +mil.vc +edu.vc + +// ve : https://registro.nic.ve/ +// Submitted by registry nic@nic.ve and nicve@conatel.gob.ve +ve +arts.ve +bib.ve +co.ve +com.ve +e12.ve +edu.ve +firm.ve +gob.ve +gov.ve +info.ve +int.ve +mil.ve +net.ve +nom.ve +org.ve +rar.ve +rec.ve +store.ve +tec.ve +web.ve + +// vg : https://www.iana.org/domains/root/db/vg.html +vg + +// vi : http://www.nic.vi/newdomainform.htm +// http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other +// TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they +// are available for registration (which they do not seem to be). +vi +co.vi +com.vi +k12.vi +net.vi +org.vi + +// vn : https://www.vnnic.vn/en/domain/cctld-vn +// https://vnnic.vn/sites/default/files/tailieu/vn.cctld.domains.txt +vn +ac.vn +ai.vn +biz.vn +com.vn +edu.vn +gov.vn +health.vn +id.vn +info.vn +int.vn +io.vn +name.vn +net.vn +org.vn +pro.vn + +// vn geographical names +angiang.vn +bacgiang.vn +backan.vn +baclieu.vn +bacninh.vn +baria-vungtau.vn +bentre.vn +binhdinh.vn +binhduong.vn +binhphuoc.vn +binhthuan.vn +camau.vn +cantho.vn +caobang.vn +daklak.vn +daknong.vn +danang.vn +dienbien.vn +dongnai.vn +dongthap.vn +gialai.vn +hagiang.vn +haiduong.vn +haiphong.vn +hanam.vn +hanoi.vn +hatinh.vn +haugiang.vn +hoabinh.vn +hungyen.vn +khanhhoa.vn +kiengiang.vn +kontum.vn +laichau.vn +lamdong.vn +langson.vn +laocai.vn +longan.vn +namdinh.vn +nghean.vn +ninhbinh.vn +ninhthuan.vn +phutho.vn +phuyen.vn +quangbinh.vn +quangnam.vn +quangngai.vn +quangninh.vn +quangtri.vn +soctrang.vn +sonla.vn +tayninh.vn +thaibinh.vn +thainguyen.vn +thanhhoa.vn +thanhphohochiminh.vn +thuathienhue.vn +tiengiang.vn +travinh.vn +tuyenquang.vn +vinhlong.vn +vinhphuc.vn +yenbai.vn + +// vu : https://www.iana.org/domains/root/db/vu.html +// http://www.vunic.vu/ +vu +com.vu +edu.vu +net.vu +org.vu + +// wf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +wf + +// ws : https://www.iana.org/domains/root/db/ws.html +// http://samoanic.ws/index.dhtml +ws +com.ws +net.ws +org.ws +gov.ws +edu.ws + +// yt : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf +yt + +// IDN ccTLDs +// When submitting patches, please maintain a sort by ISO 3166 ccTLD, then +// U-label, and follow this format: +// // A-Label ("", [, variant info]) : +// // [sponsoring org] +// U-Label + +// xn--mgbaam7a8h ("Emerat", Arabic) : AE +// http://nic.ae/english/arabicdomain/rules.jsp +امارات + +// xn--y9a3aq ("hye", Armenian) : AM +// ISOC AM (operated by .am Registry) +հայ + +// xn--54b7fta0cc ("Bangla", Bangla) : BD +বাংলা + +// xn--90ae ("bg", Bulgarian) : BG +бг + +// xn--mgbcpq6gpa1a ("albahrain", Arabic) : BH +البحرين + +// xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY +// Operated by .by registry +бел + +// xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN +// CNNIC +// http://cnnic.cn/html/Dir/2005/10/11/3218.htm +中国 + +// xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN +// CNNIC +// http://cnnic.cn/html/Dir/2005/10/11/3218.htm +中國 + +// xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ +الجزائر + +// xn--wgbh1c ("Egypt/Masr", Arabic) : EG +// http://www.dotmasr.eg/ +مصر + +// xn--e1a4c ("eu", Cyrillic) : EU +// https://eurid.eu +ею + +// xn--qxa6a ("eu", Greek) : EU +// https://eurid.eu +ευ + +// xn--mgbah1a3hjkrd ("Mauritania", Arabic) : MR +موريتانيا + +// xn--node ("ge", Georgian Mkhedruli) : GE +გე + +// xn--qxam ("el", Greek) : GR +// Hellenic Ministry of Infrastructure, Transport, and Networks +ελ + +// xn--j6w193g ("Hong Kong", Chinese) : HK +// https://www.hkirc.hk +// Submitted by registry +// https://www.hkirc.hk/content.jsp?id=30#!/34 +香港 +公司.香港 +教育.香港 +政府.香港 +個人.香港 +網絡.香港 +組織.香港 + +// xn--2scrj9c ("Bharat", Kannada) : IN +// India +ಭಾರತ + +// xn--3hcrj9c ("Bharat", Oriya) : IN +// India +ଭାରତ + +// xn--45br5cyl ("Bharatam", Assamese) : IN +// India +ভাৰত + +// xn--h2breg3eve ("Bharatam", Sanskrit) : IN +// India +भारतम् + +// xn--h2brj9c8c ("Bharot", Santali) : IN +// India +भारोत + +// xn--mgbgu82a ("Bharat", Sindhi) : IN +// India +ڀارت + +// xn--rvc1e0am3e ("Bharatam", Malayalam) : IN +// India +ഭാരതം + +// xn--h2brj9c ("Bharat", Devanagari) : IN +// India +भारत + +// xn--mgbbh1a ("Bharat", Kashmiri) : IN +// India +بارت + +// xn--mgbbh1a71e ("Bharat", Arabic) : IN +// India +بھارت + +// xn--fpcrj9c3d ("Bharat", Telugu) : IN +// India +భారత్ + +// xn--gecrj9c ("Bharat", Gujarati) : IN +// India +ભારત + +// xn--s9brj9c ("Bharat", Gurmukhi) : IN +// India +ਭਾਰਤ + +// xn--45brj9c ("Bharat", Bengali) : IN +// India +ভারত + +// xn--xkc2dl3a5ee0h ("India", Tamil) : IN +// India +இந்தியா + +// xn--mgba3a4f16a ("Iran", Persian) : IR +ایران + +// xn--mgba3a4fra ("Iran", Arabic) : IR +ايران + +// xn--mgbtx2b ("Iraq", Arabic) : IQ +// Communications and Media Commission +عراق + +// xn--mgbayh7gpa ("al-Ordon", Arabic) : JO +// National Information Technology Center (NITC) +// Royal Scientific Society, Al-Jubeiha +الاردن + +// xn--3e0b707e ("Republic of Korea", Hangul) : KR +한국 + +// xn--80ao21a ("Kaz", Kazakh) : KZ +қаз + +// xn--q7ce6a ("Lao", Lao) : LA +ລາວ + +// xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK +// https://nic.lk +ලංකා + +// xn--xkc2al3hye2a ("Ilangai", Tamil) : LK +// https://nic.lk +இலங்கை + +// xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA +المغرب + +// xn--d1alf ("mkd", Macedonian) : MK +// MARnet +мкд + +// xn--l1acc ("mon", Mongolian) : MN +мон + +// xn--mix891f ("Macao", Chinese, Traditional) : MO +// MONIC / HNET Asia (Registry Operator for .mo) +澳門 + +// xn--mix082f ("Macao", Chinese, Simplified) : MO +澳门 + +// xn--mgbx4cd0ab ("Malaysia", Malay) : MY +مليسيا + +// xn--mgb9awbf ("Oman", Arabic) : OM +عمان + +// xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK +پاکستان + +// xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK +پاكستان + +// xn--ygbi2ammx ("Falasteen", Arabic) : PS +// The Palestinian National Internet Naming Authority (PNINA) +// http://www.pnina.ps +فلسطين + +// xn--90a3ac ("srb", Cyrillic) : RS +// https://www.rnids.rs/en/domains/national-domains +срб +пр.срб +орг.срб +обр.срб +од.срб +упр.срб +ак.срб + +// xn--p1ai ("rf", Russian-Cyrillic) : RU +// https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf +// Submitted by George Georgievsky +рф + +// xn--wgbl6a ("Qatar", Arabic) : QA +// http://www.ict.gov.qa/ +قطر + +// xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA +// http://www.nic.net.sa/ +السعودية + +// xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant) : SA +السعودیة + +// xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA +السعودیۃ + +// xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA +السعوديه + +// xn--mgbpl2fh ("sudan", Arabic) : SD +// Operated by .sd registry +سودان + +// xn--yfro4i67o Singapore ("Singapore", Chinese) : SG +新加坡 + +// xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG +சிங்கப்பூர் + +// xn--ogbpf8fl ("Syria", Arabic) : SY +سورية + +// xn--mgbtf8fl ("Syria", Arabic, variant) : SY +سوريا + +// xn--o3cw4h ("Thai", Thai) : TH +// http://www.thnic.co.th +ไทย +ศึกษา.ไทย +ธุรกิจ.ไทย +รัฐบาล.ไทย +ทหาร.ไทย +เน็ต.ไทย +องค์กร.ไทย + +// xn--pgbs0dh ("Tunisia", Arabic) : TN +// http://nic.tn +تونس + +// xn--kpry57d ("Taiwan", Chinese, Traditional) : TW +// http://www.twnic.net/english/dn/dn_07a.htm +台灣 + +// xn--kprw13d ("Taiwan", Chinese, Simplified) : TW +// http://www.twnic.net/english/dn/dn_07a.htm +台湾 + +// xn--nnx388a ("Taiwan", Chinese, variant) : TW +臺灣 + +// xn--j1amh ("ukr", Cyrillic) : UA +укр + +// xn--mgb2ddes ("AlYemen", Arabic) : YE +اليمن + +// xxx : http://icmregistry.com +xxx + +// ye : http://www.y.net.ye/services/domain_name.htm +ye +com.ye +edu.ye +gov.ye +net.ye +mil.ye +org.ye + +// za : https://www.zadna.org.za/content/page/domain-information/ +ac.za +agric.za +alt.za +co.za +edu.za +gov.za +grondar.za +law.za +mil.za +net.za +ngo.za +nic.za +nis.za +nom.za +org.za +school.za +tm.za +web.za + +// zm : https://zicta.zm/ +// Submitted by registry +zm +ac.zm +biz.zm +co.zm +com.zm +edu.zm +gov.zm +info.zm +mil.zm +net.zm +org.zm +sch.zm + +// zw : https://www.potraz.gov.zw/ +// Confirmed by registry 2017-01-25 +zw +ac.zw +co.zw +gov.zw +mil.zw +org.zw + +// newGTLDs + +// List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2024-10-17T15:16:23Z +// This list is auto-generated, don't edit it manually. +// aaa : American Automobile Association, Inc. +// https://www.iana.org/domains/root/db/aaa.html +aaa + +// aarp : AARP +// https://www.iana.org/domains/root/db/aarp.html +aarp + +// abb : ABB Ltd +// https://www.iana.org/domains/root/db/abb.html +abb + +// abbott : Abbott Laboratories, Inc. +// https://www.iana.org/domains/root/db/abbott.html +abbott + +// abbvie : AbbVie Inc. +// https://www.iana.org/domains/root/db/abbvie.html +abbvie + +// abc : Disney Enterprises, Inc. +// https://www.iana.org/domains/root/db/abc.html +abc + +// able : Able Inc. +// https://www.iana.org/domains/root/db/able.html +able + +// abogado : Registry Services, LLC +// https://www.iana.org/domains/root/db/abogado.html +abogado + +// abudhabi : Abu Dhabi Systems and Information Centre +// https://www.iana.org/domains/root/db/abudhabi.html +abudhabi + +// academy : Binky Moon, LLC +// https://www.iana.org/domains/root/db/academy.html +academy + +// accenture : Accenture plc +// https://www.iana.org/domains/root/db/accenture.html +accenture + +// accountant : dot Accountant Limited +// https://www.iana.org/domains/root/db/accountant.html +accountant + +// accountants : Binky Moon, LLC +// https://www.iana.org/domains/root/db/accountants.html +accountants + +// aco : ACO Severin Ahlmann GmbH & Co. KG +// https://www.iana.org/domains/root/db/aco.html +aco + +// actor : Dog Beach, LLC +// https://www.iana.org/domains/root/db/actor.html +actor + +// ads : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/ads.html +ads + +// adult : ICM Registry AD LLC +// https://www.iana.org/domains/root/db/adult.html +adult + +// aeg : Aktiebolaget Electrolux +// https://www.iana.org/domains/root/db/aeg.html +aeg + +// aetna : Aetna Life Insurance Company +// https://www.iana.org/domains/root/db/aetna.html +aetna + +// afl : Australian Football League +// https://www.iana.org/domains/root/db/afl.html +afl + +// africa : ZA Central Registry NPC trading as Registry.Africa +// https://www.iana.org/domains/root/db/africa.html +africa + +// agakhan : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/agakhan.html +agakhan + +// agency : Binky Moon, LLC +// https://www.iana.org/domains/root/db/agency.html +agency + +// aig : American International Group, Inc. +// https://www.iana.org/domains/root/db/aig.html +aig + +// airbus : Airbus S.A.S. +// https://www.iana.org/domains/root/db/airbus.html +airbus + +// airforce : Dog Beach, LLC +// https://www.iana.org/domains/root/db/airforce.html +airforce + +// airtel : Bharti Airtel Limited +// https://www.iana.org/domains/root/db/airtel.html +airtel + +// akdn : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/akdn.html +akdn + +// alibaba : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/alibaba.html +alibaba + +// alipay : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/alipay.html +alipay + +// allfinanz : Allfinanz Deutsche Vermögensberatung Aktiengesellschaft +// https://www.iana.org/domains/root/db/allfinanz.html +allfinanz + +// allstate : Allstate Fire and Casualty Insurance Company +// https://www.iana.org/domains/root/db/allstate.html +allstate + +// ally : Ally Financial Inc. +// https://www.iana.org/domains/root/db/ally.html +ally + +// alsace : Region Grand Est +// https://www.iana.org/domains/root/db/alsace.html +alsace + +// alstom : ALSTOM +// https://www.iana.org/domains/root/db/alstom.html +alstom + +// amazon : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/amazon.html +amazon + +// americanexpress : American Express Travel Related Services Company, Inc. +// https://www.iana.org/domains/root/db/americanexpress.html +americanexpress + +// americanfamily : AmFam, Inc. +// https://www.iana.org/domains/root/db/americanfamily.html +americanfamily + +// amex : American Express Travel Related Services Company, Inc. +// https://www.iana.org/domains/root/db/amex.html +amex + +// amfam : AmFam, Inc. +// https://www.iana.org/domains/root/db/amfam.html +amfam + +// amica : Amica Mutual Insurance Company +// https://www.iana.org/domains/root/db/amica.html +amica + +// amsterdam : Gemeente Amsterdam +// https://www.iana.org/domains/root/db/amsterdam.html +amsterdam + +// analytics : Campus IP LLC +// https://www.iana.org/domains/root/db/analytics.html +analytics + +// android : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/android.html +android + +// anquan : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/anquan.html +anquan + +// anz : Australia and New Zealand Banking Group Limited +// https://www.iana.org/domains/root/db/anz.html +anz + +// aol : Yahoo Inc. +// https://www.iana.org/domains/root/db/aol.html +aol + +// apartments : Binky Moon, LLC +// https://www.iana.org/domains/root/db/apartments.html +apartments + +// app : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/app.html +app + +// apple : Apple Inc. +// https://www.iana.org/domains/root/db/apple.html +apple + +// aquarelle : Aquarelle.com +// https://www.iana.org/domains/root/db/aquarelle.html +aquarelle + +// arab : League of Arab States +// https://www.iana.org/domains/root/db/arab.html +arab + +// aramco : Aramco Services Company +// https://www.iana.org/domains/root/db/aramco.html +aramco + +// archi : Identity Digital Limited +// https://www.iana.org/domains/root/db/archi.html +archi + +// army : Dog Beach, LLC +// https://www.iana.org/domains/root/db/army.html +army + +// art : UK Creative Ideas Limited +// https://www.iana.org/domains/root/db/art.html +art + +// arte : Association Relative à la Télévision Européenne G.E.I.E. +// https://www.iana.org/domains/root/db/arte.html +arte + +// asda : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/asda.html +asda + +// associates : Binky Moon, LLC +// https://www.iana.org/domains/root/db/associates.html +associates + +// athleta : The Gap, Inc. +// https://www.iana.org/domains/root/db/athleta.html +athleta + +// attorney : Dog Beach, LLC +// https://www.iana.org/domains/root/db/attorney.html +attorney + +// auction : Dog Beach, LLC +// https://www.iana.org/domains/root/db/auction.html +auction + +// audi : AUDI Aktiengesellschaft +// https://www.iana.org/domains/root/db/audi.html +audi + +// audible : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/audible.html +audible + +// audio : XYZ.COM LLC +// https://www.iana.org/domains/root/db/audio.html +audio + +// auspost : Australian Postal Corporation +// https://www.iana.org/domains/root/db/auspost.html +auspost + +// author : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/author.html +author + +// auto : XYZ.COM LLC +// https://www.iana.org/domains/root/db/auto.html +auto + +// autos : XYZ.COM LLC +// https://www.iana.org/domains/root/db/autos.html +autos + +// aws : AWS Registry LLC +// https://www.iana.org/domains/root/db/aws.html +aws + +// axa : AXA Group Operations SAS +// https://www.iana.org/domains/root/db/axa.html +axa + +// azure : Microsoft Corporation +// https://www.iana.org/domains/root/db/azure.html +azure + +// baby : XYZ.COM LLC +// https://www.iana.org/domains/root/db/baby.html +baby + +// baidu : Baidu, Inc. +// https://www.iana.org/domains/root/db/baidu.html +baidu + +// banamex : Citigroup Inc. +// https://www.iana.org/domains/root/db/banamex.html +banamex + +// band : Dog Beach, LLC +// https://www.iana.org/domains/root/db/band.html +band + +// bank : fTLD Registry Services LLC +// https://www.iana.org/domains/root/db/bank.html +bank + +// bar : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable +// https://www.iana.org/domains/root/db/bar.html +bar + +// barcelona : Municipi de Barcelona +// https://www.iana.org/domains/root/db/barcelona.html +barcelona + +// barclaycard : Barclays Bank PLC +// https://www.iana.org/domains/root/db/barclaycard.html +barclaycard + +// barclays : Barclays Bank PLC +// https://www.iana.org/domains/root/db/barclays.html +barclays + +// barefoot : Gallo Vineyards, Inc. +// https://www.iana.org/domains/root/db/barefoot.html +barefoot + +// bargains : Binky Moon, LLC +// https://www.iana.org/domains/root/db/bargains.html +bargains + +// baseball : MLB Advanced Media DH, LLC +// https://www.iana.org/domains/root/db/baseball.html +baseball + +// basketball : Fédération Internationale de Basketball (FIBA) +// https://www.iana.org/domains/root/db/basketball.html +basketball + +// bauhaus : Werkhaus GmbH +// https://www.iana.org/domains/root/db/bauhaus.html +bauhaus + +// bayern : Bayern Connect GmbH +// https://www.iana.org/domains/root/db/bayern.html +bayern + +// bbc : British Broadcasting Corporation +// https://www.iana.org/domains/root/db/bbc.html +bbc + +// bbt : BB&T Corporation +// https://www.iana.org/domains/root/db/bbt.html +bbt + +// bbva : BANCO BILBAO VIZCAYA ARGENTARIA, S.A. +// https://www.iana.org/domains/root/db/bbva.html +bbva + +// bcg : The Boston Consulting Group, Inc. +// https://www.iana.org/domains/root/db/bcg.html +bcg + +// bcn : Municipi de Barcelona +// https://www.iana.org/domains/root/db/bcn.html +bcn + +// beats : Beats Electronics, LLC +// https://www.iana.org/domains/root/db/beats.html +beats + +// beauty : XYZ.COM LLC +// https://www.iana.org/domains/root/db/beauty.html +beauty + +// beer : Registry Services, LLC +// https://www.iana.org/domains/root/db/beer.html +beer + +// bentley : Bentley Motors Limited +// https://www.iana.org/domains/root/db/bentley.html +bentley + +// berlin : dotBERLIN GmbH & Co. KG +// https://www.iana.org/domains/root/db/berlin.html +berlin + +// best : BestTLD Pty Ltd +// https://www.iana.org/domains/root/db/best.html +best + +// bestbuy : BBY Solutions, Inc. +// https://www.iana.org/domains/root/db/bestbuy.html +bestbuy + +// bet : Identity Digital Limited +// https://www.iana.org/domains/root/db/bet.html +bet + +// bharti : Bharti Enterprises (Holding) Private Limited +// https://www.iana.org/domains/root/db/bharti.html +bharti + +// bible : American Bible Society +// https://www.iana.org/domains/root/db/bible.html +bible + +// bid : dot Bid Limited +// https://www.iana.org/domains/root/db/bid.html +bid + +// bike : Binky Moon, LLC +// https://www.iana.org/domains/root/db/bike.html +bike + +// bing : Microsoft Corporation +// https://www.iana.org/domains/root/db/bing.html +bing + +// bingo : Binky Moon, LLC +// https://www.iana.org/domains/root/db/bingo.html +bingo + +// bio : Identity Digital Limited +// https://www.iana.org/domains/root/db/bio.html +bio + +// black : Identity Digital Limited +// https://www.iana.org/domains/root/db/black.html +black + +// blackfriday : Registry Services, LLC +// https://www.iana.org/domains/root/db/blackfriday.html +blackfriday + +// blockbuster : Dish DBS Corporation +// https://www.iana.org/domains/root/db/blockbuster.html +blockbuster + +// blog : Knock Knock WHOIS There, LLC +// https://www.iana.org/domains/root/db/blog.html +blog + +// bloomberg : Bloomberg IP Holdings LLC +// https://www.iana.org/domains/root/db/bloomberg.html +bloomberg + +// blue : Identity Digital Limited +// https://www.iana.org/domains/root/db/blue.html +blue + +// bms : Bristol-Myers Squibb Company +// https://www.iana.org/domains/root/db/bms.html +bms + +// bmw : Bayerische Motoren Werke Aktiengesellschaft +// https://www.iana.org/domains/root/db/bmw.html +bmw + +// bnpparibas : BNP Paribas +// https://www.iana.org/domains/root/db/bnpparibas.html +bnpparibas + +// boats : XYZ.COM LLC +// https://www.iana.org/domains/root/db/boats.html +boats + +// boehringer : Boehringer Ingelheim International GmbH +// https://www.iana.org/domains/root/db/boehringer.html +boehringer + +// bofa : Bank of America Corporation +// https://www.iana.org/domains/root/db/bofa.html +bofa + +// bom : Núcleo de Informação e Coordenação do Ponto BR - NIC.br +// https://www.iana.org/domains/root/db/bom.html +bom + +// bond : ShortDot SA +// https://www.iana.org/domains/root/db/bond.html +bond + +// boo : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/boo.html +boo + +// book : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/book.html +book + +// booking : Booking.com B.V. +// https://www.iana.org/domains/root/db/booking.html +booking + +// bosch : Robert Bosch GMBH +// https://www.iana.org/domains/root/db/bosch.html +bosch + +// bostik : Bostik SA +// https://www.iana.org/domains/root/db/bostik.html +bostik + +// boston : Registry Services, LLC +// https://www.iana.org/domains/root/db/boston.html +boston + +// bot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/bot.html +bot + +// boutique : Binky Moon, LLC +// https://www.iana.org/domains/root/db/boutique.html +boutique + +// box : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/box.html +box + +// bradesco : Banco Bradesco S.A. +// https://www.iana.org/domains/root/db/bradesco.html +bradesco + +// bridgestone : Bridgestone Corporation +// https://www.iana.org/domains/root/db/bridgestone.html +bridgestone + +// broadway : Celebrate Broadway, Inc. +// https://www.iana.org/domains/root/db/broadway.html +broadway + +// broker : Dog Beach, LLC +// https://www.iana.org/domains/root/db/broker.html +broker + +// brother : Brother Industries, Ltd. +// https://www.iana.org/domains/root/db/brother.html +brother + +// brussels : DNS.be vzw +// https://www.iana.org/domains/root/db/brussels.html +brussels + +// build : Plan Bee LLC +// https://www.iana.org/domains/root/db/build.html +build + +// builders : Binky Moon, LLC +// https://www.iana.org/domains/root/db/builders.html +builders + +// business : Binky Moon, LLC +// https://www.iana.org/domains/root/db/business.html +business + +// buy : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/buy.html +buy + +// buzz : DOTSTRATEGY CO. +// https://www.iana.org/domains/root/db/buzz.html +buzz + +// bzh : Association www.bzh +// https://www.iana.org/domains/root/db/bzh.html +bzh + +// cab : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cab.html +cab + +// cafe : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cafe.html +cafe + +// cal : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/cal.html +cal + +// call : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/call.html +call + +// calvinklein : PVH gTLD Holdings LLC +// https://www.iana.org/domains/root/db/calvinklein.html +calvinklein + +// cam : Cam Connecting SARL +// https://www.iana.org/domains/root/db/cam.html +cam + +// camera : Binky Moon, LLC +// https://www.iana.org/domains/root/db/camera.html +camera + +// camp : Binky Moon, LLC +// https://www.iana.org/domains/root/db/camp.html +camp + +// canon : Canon Inc. +// https://www.iana.org/domains/root/db/canon.html +canon + +// capetown : ZA Central Registry NPC trading as ZA Central Registry +// https://www.iana.org/domains/root/db/capetown.html +capetown + +// capital : Binky Moon, LLC +// https://www.iana.org/domains/root/db/capital.html +capital + +// capitalone : Capital One Financial Corporation +// https://www.iana.org/domains/root/db/capitalone.html +capitalone + +// car : XYZ.COM LLC +// https://www.iana.org/domains/root/db/car.html +car + +// caravan : Caravan International, Inc. +// https://www.iana.org/domains/root/db/caravan.html +caravan + +// cards : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cards.html +cards + +// care : Binky Moon, LLC +// https://www.iana.org/domains/root/db/care.html +care + +// career : dotCareer LLC +// https://www.iana.org/domains/root/db/career.html +career + +// careers : Binky Moon, LLC +// https://www.iana.org/domains/root/db/careers.html +careers + +// cars : XYZ.COM LLC +// https://www.iana.org/domains/root/db/cars.html +cars + +// casa : Registry Services, LLC +// https://www.iana.org/domains/root/db/casa.html +casa + +// case : Digity, LLC +// https://www.iana.org/domains/root/db/case.html +case + +// cash : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cash.html +cash + +// casino : Binky Moon, LLC +// https://www.iana.org/domains/root/db/casino.html +casino + +// catering : Binky Moon, LLC +// https://www.iana.org/domains/root/db/catering.html +catering + +// catholic : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/catholic.html +catholic + +// cba : COMMONWEALTH BANK OF AUSTRALIA +// https://www.iana.org/domains/root/db/cba.html +cba + +// cbn : The Christian Broadcasting Network, Inc. +// https://www.iana.org/domains/root/db/cbn.html +cbn + +// cbre : CBRE, Inc. +// https://www.iana.org/domains/root/db/cbre.html +cbre + +// center : Binky Moon, LLC +// https://www.iana.org/domains/root/db/center.html +center + +// ceo : XYZ.COM LLC +// https://www.iana.org/domains/root/db/ceo.html +ceo + +// cern : European Organization for Nuclear Research ("CERN") +// https://www.iana.org/domains/root/db/cern.html +cern + +// cfa : CFA Institute +// https://www.iana.org/domains/root/db/cfa.html +cfa + +// cfd : ShortDot SA +// https://www.iana.org/domains/root/db/cfd.html +cfd + +// chanel : Chanel International B.V. +// https://www.iana.org/domains/root/db/chanel.html +chanel + +// channel : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/channel.html +channel + +// charity : Public Interest Registry +// https://www.iana.org/domains/root/db/charity.html +charity + +// chase : JPMorgan Chase Bank, National Association +// https://www.iana.org/domains/root/db/chase.html +chase + +// chat : Binky Moon, LLC +// https://www.iana.org/domains/root/db/chat.html +chat + +// cheap : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cheap.html +cheap + +// chintai : CHINTAI Corporation +// https://www.iana.org/domains/root/db/chintai.html +chintai + +// christmas : XYZ.COM LLC +// https://www.iana.org/domains/root/db/christmas.html +christmas + +// chrome : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/chrome.html +chrome + +// church : Binky Moon, LLC +// https://www.iana.org/domains/root/db/church.html +church + +// cipriani : Hotel Cipriani Srl +// https://www.iana.org/domains/root/db/cipriani.html +cipriani + +// circle : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/circle.html +circle + +// cisco : Cisco Technology, Inc. +// https://www.iana.org/domains/root/db/cisco.html +cisco + +// citadel : Citadel Domain LLC +// https://www.iana.org/domains/root/db/citadel.html +citadel + +// citi : Citigroup Inc. +// https://www.iana.org/domains/root/db/citi.html +citi + +// citic : CITIC Group Corporation +// https://www.iana.org/domains/root/db/citic.html +citic + +// city : Binky Moon, LLC +// https://www.iana.org/domains/root/db/city.html +city + +// claims : Binky Moon, LLC +// https://www.iana.org/domains/root/db/claims.html +claims + +// cleaning : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cleaning.html +cleaning + +// click : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/click.html +click + +// clinic : Binky Moon, LLC +// https://www.iana.org/domains/root/db/clinic.html +clinic + +// clinique : The Estée Lauder Companies Inc. +// https://www.iana.org/domains/root/db/clinique.html +clinique + +// clothing : Binky Moon, LLC +// https://www.iana.org/domains/root/db/clothing.html +clothing + +// cloud : Aruba PEC S.p.A. +// https://www.iana.org/domains/root/db/cloud.html +cloud + +// club : Registry Services, LLC +// https://www.iana.org/domains/root/db/club.html +club + +// clubmed : Club Méditerranée S.A. +// https://www.iana.org/domains/root/db/clubmed.html +clubmed + +// coach : Binky Moon, LLC +// https://www.iana.org/domains/root/db/coach.html +coach + +// codes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/codes.html +codes + +// coffee : Binky Moon, LLC +// https://www.iana.org/domains/root/db/coffee.html +coffee + +// college : XYZ.COM LLC +// https://www.iana.org/domains/root/db/college.html +college + +// cologne : dotKoeln GmbH +// https://www.iana.org/domains/root/db/cologne.html +cologne + +// commbank : COMMONWEALTH BANK OF AUSTRALIA +// https://www.iana.org/domains/root/db/commbank.html +commbank + +// community : Binky Moon, LLC +// https://www.iana.org/domains/root/db/community.html +community + +// company : Binky Moon, LLC +// https://www.iana.org/domains/root/db/company.html +company + +// compare : Registry Services, LLC +// https://www.iana.org/domains/root/db/compare.html +compare + +// computer : Binky Moon, LLC +// https://www.iana.org/domains/root/db/computer.html +computer + +// comsec : VeriSign, Inc. +// https://www.iana.org/domains/root/db/comsec.html +comsec + +// condos : Binky Moon, LLC +// https://www.iana.org/domains/root/db/condos.html +condos + +// construction : Binky Moon, LLC +// https://www.iana.org/domains/root/db/construction.html +construction + +// consulting : Dog Beach, LLC +// https://www.iana.org/domains/root/db/consulting.html +consulting + +// contact : Dog Beach, LLC +// https://www.iana.org/domains/root/db/contact.html +contact + +// contractors : Binky Moon, LLC +// https://www.iana.org/domains/root/db/contractors.html +contractors + +// cooking : Registry Services, LLC +// https://www.iana.org/domains/root/db/cooking.html +cooking + +// cool : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cool.html +cool + +// corsica : Collectivité de Corse +// https://www.iana.org/domains/root/db/corsica.html +corsica + +// country : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/country.html +country + +// coupon : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/coupon.html +coupon + +// coupons : Binky Moon, LLC +// https://www.iana.org/domains/root/db/coupons.html +coupons + +// courses : Registry Services, LLC +// https://www.iana.org/domains/root/db/courses.html +courses + +// cpa : American Institute of Certified Public Accountants +// https://www.iana.org/domains/root/db/cpa.html +cpa + +// credit : Binky Moon, LLC +// https://www.iana.org/domains/root/db/credit.html +credit + +// creditcard : Binky Moon, LLC +// https://www.iana.org/domains/root/db/creditcard.html +creditcard + +// creditunion : DotCooperation LLC +// https://www.iana.org/domains/root/db/creditunion.html +creditunion + +// cricket : dot Cricket Limited +// https://www.iana.org/domains/root/db/cricket.html +cricket + +// crown : Crown Equipment Corporation +// https://www.iana.org/domains/root/db/crown.html +crown + +// crs : Federated Co-operatives Limited +// https://www.iana.org/domains/root/db/crs.html +crs + +// cruise : Viking River Cruises (Bermuda) Ltd. +// https://www.iana.org/domains/root/db/cruise.html +cruise + +// cruises : Binky Moon, LLC +// https://www.iana.org/domains/root/db/cruises.html +cruises + +// cuisinella : SCHMIDT GROUPE S.A.S. +// https://www.iana.org/domains/root/db/cuisinella.html +cuisinella + +// cymru : Nominet UK +// https://www.iana.org/domains/root/db/cymru.html +cymru + +// cyou : ShortDot SA +// https://www.iana.org/domains/root/db/cyou.html +cyou + +// dad : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/dad.html +dad + +// dance : Dog Beach, LLC +// https://www.iana.org/domains/root/db/dance.html +dance + +// data : Dish DBS Corporation +// https://www.iana.org/domains/root/db/data.html +data + +// date : dot Date Limited +// https://www.iana.org/domains/root/db/date.html +date + +// dating : Binky Moon, LLC +// https://www.iana.org/domains/root/db/dating.html +dating + +// datsun : NISSAN MOTOR CO., LTD. +// https://www.iana.org/domains/root/db/datsun.html +datsun + +// day : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/day.html +day + +// dclk : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/dclk.html +dclk + +// dds : Registry Services, LLC +// https://www.iana.org/domains/root/db/dds.html +dds + +// deal : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/deal.html +deal + +// dealer : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/dealer.html +dealer + +// deals : Binky Moon, LLC +// https://www.iana.org/domains/root/db/deals.html +deals + +// degree : Dog Beach, LLC +// https://www.iana.org/domains/root/db/degree.html +degree + +// delivery : Binky Moon, LLC +// https://www.iana.org/domains/root/db/delivery.html +delivery + +// dell : Dell Inc. +// https://www.iana.org/domains/root/db/dell.html +dell + +// deloitte : Deloitte Touche Tohmatsu +// https://www.iana.org/domains/root/db/deloitte.html +deloitte + +// delta : Delta Air Lines, Inc. +// https://www.iana.org/domains/root/db/delta.html +delta + +// democrat : Dog Beach, LLC +// https://www.iana.org/domains/root/db/democrat.html +democrat + +// dental : Binky Moon, LLC +// https://www.iana.org/domains/root/db/dental.html +dental + +// dentist : Dog Beach, LLC +// https://www.iana.org/domains/root/db/dentist.html +dentist + +// desi +// https://www.iana.org/domains/root/db/desi.html +desi + +// design : Registry Services, LLC +// https://www.iana.org/domains/root/db/design.html +design + +// dev : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/dev.html +dev + +// dhl : Deutsche Post AG +// https://www.iana.org/domains/root/db/dhl.html +dhl + +// diamonds : Binky Moon, LLC +// https://www.iana.org/domains/root/db/diamonds.html +diamonds + +// diet : XYZ.COM LLC +// https://www.iana.org/domains/root/db/diet.html +diet + +// digital : Binky Moon, LLC +// https://www.iana.org/domains/root/db/digital.html +digital + +// direct : Binky Moon, LLC +// https://www.iana.org/domains/root/db/direct.html +direct + +// directory : Binky Moon, LLC +// https://www.iana.org/domains/root/db/directory.html +directory + +// discount : Binky Moon, LLC +// https://www.iana.org/domains/root/db/discount.html +discount + +// discover : Discover Financial Services +// https://www.iana.org/domains/root/db/discover.html +discover + +// dish : Dish DBS Corporation +// https://www.iana.org/domains/root/db/dish.html +dish + +// diy : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/diy.html +diy + +// dnp : Dai Nippon Printing Co., Ltd. +// https://www.iana.org/domains/root/db/dnp.html +dnp + +// docs : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/docs.html +docs + +// doctor : Binky Moon, LLC +// https://www.iana.org/domains/root/db/doctor.html +doctor + +// dog : Binky Moon, LLC +// https://www.iana.org/domains/root/db/dog.html +dog + +// domains : Binky Moon, LLC +// https://www.iana.org/domains/root/db/domains.html +domains + +// dot : Dish DBS Corporation +// https://www.iana.org/domains/root/db/dot.html +dot + +// download : dot Support Limited +// https://www.iana.org/domains/root/db/download.html +download + +// drive : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/drive.html +drive + +// dtv : Dish DBS Corporation +// https://www.iana.org/domains/root/db/dtv.html +dtv + +// dubai : Dubai Smart Government Department +// https://www.iana.org/domains/root/db/dubai.html +dubai + +// dunlop : The Goodyear Tire & Rubber Company +// https://www.iana.org/domains/root/db/dunlop.html +dunlop + +// dupont : DuPont Specialty Products USA, LLC +// https://www.iana.org/domains/root/db/dupont.html +dupont + +// durban : ZA Central Registry NPC trading as ZA Central Registry +// https://www.iana.org/domains/root/db/durban.html +durban + +// dvag : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/dvag.html +dvag + +// dvr : DISH Technologies L.L.C. +// https://www.iana.org/domains/root/db/dvr.html +dvr + +// earth : Interlink Systems Innovation Institute K.K. +// https://www.iana.org/domains/root/db/earth.html +earth + +// eat : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/eat.html +eat + +// eco : Big Room Inc. +// https://www.iana.org/domains/root/db/eco.html +eco + +// edeka : EDEKA Verband kaufmännischer Genossenschaften e.V. +// https://www.iana.org/domains/root/db/edeka.html +edeka + +// education : Binky Moon, LLC +// https://www.iana.org/domains/root/db/education.html +education + +// email : Binky Moon, LLC +// https://www.iana.org/domains/root/db/email.html +email + +// emerck : Merck KGaA +// https://www.iana.org/domains/root/db/emerck.html +emerck + +// energy : Binky Moon, LLC +// https://www.iana.org/domains/root/db/energy.html +energy + +// engineer : Dog Beach, LLC +// https://www.iana.org/domains/root/db/engineer.html +engineer + +// engineering : Binky Moon, LLC +// https://www.iana.org/domains/root/db/engineering.html +engineering + +// enterprises : Binky Moon, LLC +// https://www.iana.org/domains/root/db/enterprises.html +enterprises + +// epson : Seiko Epson Corporation +// https://www.iana.org/domains/root/db/epson.html +epson + +// equipment : Binky Moon, LLC +// https://www.iana.org/domains/root/db/equipment.html +equipment + +// ericsson : Telefonaktiebolaget L M Ericsson +// https://www.iana.org/domains/root/db/ericsson.html +ericsson + +// erni : ERNI Group Holding AG +// https://www.iana.org/domains/root/db/erni.html +erni + +// esq : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/esq.html +esq + +// estate : Binky Moon, LLC +// https://www.iana.org/domains/root/db/estate.html +estate + +// eurovision : European Broadcasting Union (EBU) +// https://www.iana.org/domains/root/db/eurovision.html +eurovision + +// eus : Puntueus Fundazioa +// https://www.iana.org/domains/root/db/eus.html +eus + +// events : Binky Moon, LLC +// https://www.iana.org/domains/root/db/events.html +events + +// exchange : Binky Moon, LLC +// https://www.iana.org/domains/root/db/exchange.html +exchange + +// expert : Binky Moon, LLC +// https://www.iana.org/domains/root/db/expert.html +expert + +// exposed : Binky Moon, LLC +// https://www.iana.org/domains/root/db/exposed.html +exposed + +// express : Binky Moon, LLC +// https://www.iana.org/domains/root/db/express.html +express + +// extraspace : Extra Space Storage LLC +// https://www.iana.org/domains/root/db/extraspace.html +extraspace + +// fage : Fage International S.A. +// https://www.iana.org/domains/root/db/fage.html +fage + +// fail : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fail.html +fail + +// fairwinds : FairWinds Partners, LLC +// https://www.iana.org/domains/root/db/fairwinds.html +fairwinds + +// faith : dot Faith Limited +// https://www.iana.org/domains/root/db/faith.html +faith + +// family : Dog Beach, LLC +// https://www.iana.org/domains/root/db/family.html +family + +// fan : Dog Beach, LLC +// https://www.iana.org/domains/root/db/fan.html +fan + +// fans : ZDNS International Limited +// https://www.iana.org/domains/root/db/fans.html +fans + +// farm : Binky Moon, LLC +// https://www.iana.org/domains/root/db/farm.html +farm + +// farmers : Farmers Insurance Exchange +// https://www.iana.org/domains/root/db/farmers.html +farmers + +// fashion : Registry Services, LLC +// https://www.iana.org/domains/root/db/fashion.html +fashion + +// fast : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/fast.html +fast + +// fedex : Federal Express Corporation +// https://www.iana.org/domains/root/db/fedex.html +fedex + +// feedback : Top Level Spectrum, Inc. +// https://www.iana.org/domains/root/db/feedback.html +feedback + +// ferrari : Fiat Chrysler Automobiles N.V. +// https://www.iana.org/domains/root/db/ferrari.html +ferrari + +// ferrero : Ferrero Trading Lux S.A. +// https://www.iana.org/domains/root/db/ferrero.html +ferrero + +// fidelity : Fidelity Brokerage Services LLC +// https://www.iana.org/domains/root/db/fidelity.html +fidelity + +// fido : Rogers Communications Canada Inc. +// https://www.iana.org/domains/root/db/fido.html +fido + +// film : Motion Picture Domain Registry Pty Ltd +// https://www.iana.org/domains/root/db/film.html +film + +// final : Núcleo de Informação e Coordenação do Ponto BR - NIC.br +// https://www.iana.org/domains/root/db/final.html +final + +// finance : Binky Moon, LLC +// https://www.iana.org/domains/root/db/finance.html +finance + +// financial : Binky Moon, LLC +// https://www.iana.org/domains/root/db/financial.html +financial + +// fire : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/fire.html +fire + +// firestone : Bridgestone Licensing Services, Inc +// https://www.iana.org/domains/root/db/firestone.html +firestone + +// firmdale : Firmdale Holdings Limited +// https://www.iana.org/domains/root/db/firmdale.html +firmdale + +// fish : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fish.html +fish + +// fishing : Registry Services, LLC +// https://www.iana.org/domains/root/db/fishing.html +fishing + +// fit : Registry Services, LLC +// https://www.iana.org/domains/root/db/fit.html +fit + +// fitness : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fitness.html +fitness + +// flickr : Flickr, Inc. +// https://www.iana.org/domains/root/db/flickr.html +flickr + +// flights : Binky Moon, LLC +// https://www.iana.org/domains/root/db/flights.html +flights + +// flir : FLIR Systems, Inc. +// https://www.iana.org/domains/root/db/flir.html +flir + +// florist : Binky Moon, LLC +// https://www.iana.org/domains/root/db/florist.html +florist + +// flowers : XYZ.COM LLC +// https://www.iana.org/domains/root/db/flowers.html +flowers + +// fly : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/fly.html +fly + +// foo : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/foo.html +foo + +// food : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/food.html +food + +// football : Binky Moon, LLC +// https://www.iana.org/domains/root/db/football.html +football + +// ford : Ford Motor Company +// https://www.iana.org/domains/root/db/ford.html +ford + +// forex : Dog Beach, LLC +// https://www.iana.org/domains/root/db/forex.html +forex + +// forsale : Dog Beach, LLC +// https://www.iana.org/domains/root/db/forsale.html +forsale + +// forum : Waterford Limited +// https://www.iana.org/domains/root/db/forum.html +forum + +// foundation : Public Interest Registry +// https://www.iana.org/domains/root/db/foundation.html +foundation + +// fox : FOX Registry, LLC +// https://www.iana.org/domains/root/db/fox.html +fox + +// free : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/free.html +free + +// fresenius : Fresenius Immobilien-Verwaltungs-GmbH +// https://www.iana.org/domains/root/db/fresenius.html +fresenius + +// frl : FRLregistry B.V. +// https://www.iana.org/domains/root/db/frl.html +frl + +// frogans : OP3FT +// https://www.iana.org/domains/root/db/frogans.html +frogans + +// frontier : Frontier Communications Corporation +// https://www.iana.org/domains/root/db/frontier.html +frontier + +// ftr : Frontier Communications Corporation +// https://www.iana.org/domains/root/db/ftr.html +ftr + +// fujitsu : Fujitsu Limited +// https://www.iana.org/domains/root/db/fujitsu.html +fujitsu + +// fun : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/fun.html +fun + +// fund : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fund.html +fund + +// furniture : Binky Moon, LLC +// https://www.iana.org/domains/root/db/furniture.html +furniture + +// futbol : Dog Beach, LLC +// https://www.iana.org/domains/root/db/futbol.html +futbol + +// fyi : Binky Moon, LLC +// https://www.iana.org/domains/root/db/fyi.html +fyi + +// gal : Asociación puntoGAL +// https://www.iana.org/domains/root/db/gal.html +gal + +// gallery : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gallery.html +gallery + +// gallo : Gallo Vineyards, Inc. +// https://www.iana.org/domains/root/db/gallo.html +gallo + +// gallup : Gallup, Inc. +// https://www.iana.org/domains/root/db/gallup.html +gallup + +// game : XYZ.COM LLC +// https://www.iana.org/domains/root/db/game.html +game + +// games : Dog Beach, LLC +// https://www.iana.org/domains/root/db/games.html +games + +// gap : The Gap, Inc. +// https://www.iana.org/domains/root/db/gap.html +gap + +// garden : Registry Services, LLC +// https://www.iana.org/domains/root/db/garden.html +garden + +// gay : Registry Services, LLC +// https://www.iana.org/domains/root/db/gay.html +gay + +// gbiz : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/gbiz.html +gbiz + +// gdn : Joint Stock Company "Navigation-information systems" +// https://www.iana.org/domains/root/db/gdn.html +gdn + +// gea : GEA Group Aktiengesellschaft +// https://www.iana.org/domains/root/db/gea.html +gea + +// gent : Easyhost BV +// https://www.iana.org/domains/root/db/gent.html +gent + +// genting : Resorts World Inc Pte. Ltd. +// https://www.iana.org/domains/root/db/genting.html +genting + +// george : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/george.html +george + +// ggee : GMO Internet, Inc. +// https://www.iana.org/domains/root/db/ggee.html +ggee + +// gift : DotGift, LLC +// https://www.iana.org/domains/root/db/gift.html +gift + +// gifts : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gifts.html +gifts + +// gives : Public Interest Registry +// https://www.iana.org/domains/root/db/gives.html +gives + +// giving : Public Interest Registry +// https://www.iana.org/domains/root/db/giving.html +giving + +// glass : Binky Moon, LLC +// https://www.iana.org/domains/root/db/glass.html +glass + +// gle : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/gle.html +gle + +// global : Identity Digital Limited +// https://www.iana.org/domains/root/db/global.html +global + +// globo : Globo Comunicação e Participações S.A +// https://www.iana.org/domains/root/db/globo.html +globo + +// gmail : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/gmail.html +gmail + +// gmbh : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gmbh.html +gmbh + +// gmo : GMO Internet, Inc. +// https://www.iana.org/domains/root/db/gmo.html +gmo + +// gmx : 1&1 Mail & Media GmbH +// https://www.iana.org/domains/root/db/gmx.html +gmx + +// godaddy : Go Daddy East, LLC +// https://www.iana.org/domains/root/db/godaddy.html +godaddy + +// gold : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gold.html +gold + +// goldpoint : YODOBASHI CAMERA CO.,LTD. +// https://www.iana.org/domains/root/db/goldpoint.html +goldpoint + +// golf : Binky Moon, LLC +// https://www.iana.org/domains/root/db/golf.html +golf + +// goo : NTT DOCOMO, INC. +// https://www.iana.org/domains/root/db/goo.html +goo + +// goodyear : The Goodyear Tire & Rubber Company +// https://www.iana.org/domains/root/db/goodyear.html +goodyear + +// goog : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/goog.html +goog + +// google : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/google.html +google + +// gop : Republican State Leadership Committee, Inc. +// https://www.iana.org/domains/root/db/gop.html +gop + +// got : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/got.html +got + +// grainger : Grainger Registry Services, LLC +// https://www.iana.org/domains/root/db/grainger.html +grainger + +// graphics : Binky Moon, LLC +// https://www.iana.org/domains/root/db/graphics.html +graphics + +// gratis : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gratis.html +gratis + +// green : Identity Digital Limited +// https://www.iana.org/domains/root/db/green.html +green + +// gripe : Binky Moon, LLC +// https://www.iana.org/domains/root/db/gripe.html +gripe + +// grocery : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/grocery.html +grocery + +// group : Binky Moon, LLC +// https://www.iana.org/domains/root/db/group.html +group + +// gucci : Guccio Gucci S.p.a. +// https://www.iana.org/domains/root/db/gucci.html +gucci + +// guge : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/guge.html +guge + +// guide : Binky Moon, LLC +// https://www.iana.org/domains/root/db/guide.html +guide + +// guitars : XYZ.COM LLC +// https://www.iana.org/domains/root/db/guitars.html +guitars + +// guru : Binky Moon, LLC +// https://www.iana.org/domains/root/db/guru.html +guru + +// hair : XYZ.COM LLC +// https://www.iana.org/domains/root/db/hair.html +hair + +// hamburg : Hamburg Top-Level-Domain GmbH +// https://www.iana.org/domains/root/db/hamburg.html +hamburg + +// hangout : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/hangout.html +hangout + +// haus : Dog Beach, LLC +// https://www.iana.org/domains/root/db/haus.html +haus + +// hbo : HBO Registry Services, Inc. +// https://www.iana.org/domains/root/db/hbo.html +hbo + +// hdfc : HDFC BANK LIMITED +// https://www.iana.org/domains/root/db/hdfc.html +hdfc + +// hdfcbank : HDFC BANK LIMITED +// https://www.iana.org/domains/root/db/hdfcbank.html +hdfcbank + +// health : Registry Services, LLC +// https://www.iana.org/domains/root/db/health.html +health + +// healthcare : Binky Moon, LLC +// https://www.iana.org/domains/root/db/healthcare.html +healthcare + +// help : Innovation service Limited +// https://www.iana.org/domains/root/db/help.html +help + +// helsinki : City of Helsinki +// https://www.iana.org/domains/root/db/helsinki.html +helsinki + +// here : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/here.html +here + +// hermes : HERMES INTERNATIONAL +// https://www.iana.org/domains/root/db/hermes.html +hermes + +// hiphop : Dot Hip Hop, LLC +// https://www.iana.org/domains/root/db/hiphop.html +hiphop + +// hisamitsu : Hisamitsu Pharmaceutical Co.,Inc. +// https://www.iana.org/domains/root/db/hisamitsu.html +hisamitsu + +// hitachi : Hitachi, Ltd. +// https://www.iana.org/domains/root/db/hitachi.html +hitachi + +// hiv : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/hiv.html +hiv + +// hkt : PCCW-HKT DataCom Services Limited +// https://www.iana.org/domains/root/db/hkt.html +hkt + +// hockey : Binky Moon, LLC +// https://www.iana.org/domains/root/db/hockey.html +hockey + +// holdings : Binky Moon, LLC +// https://www.iana.org/domains/root/db/holdings.html +holdings + +// holiday : Binky Moon, LLC +// https://www.iana.org/domains/root/db/holiday.html +holiday + +// homedepot : Home Depot Product Authority, LLC +// https://www.iana.org/domains/root/db/homedepot.html +homedepot + +// homegoods : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/homegoods.html +homegoods + +// homes : XYZ.COM LLC +// https://www.iana.org/domains/root/db/homes.html +homes + +// homesense : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/homesense.html +homesense + +// honda : Honda Motor Co., Ltd. +// https://www.iana.org/domains/root/db/honda.html +honda + +// horse : Registry Services, LLC +// https://www.iana.org/domains/root/db/horse.html +horse + +// hospital : Binky Moon, LLC +// https://www.iana.org/domains/root/db/hospital.html +hospital + +// host : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/host.html +host + +// hosting : XYZ.COM LLC +// https://www.iana.org/domains/root/db/hosting.html +hosting + +// hot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/hot.html +hot + +// hotels : Booking.com B.V. +// https://www.iana.org/domains/root/db/hotels.html +hotels + +// hotmail : Microsoft Corporation +// https://www.iana.org/domains/root/db/hotmail.html +hotmail + +// house : Binky Moon, LLC +// https://www.iana.org/domains/root/db/house.html +house + +// how : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/how.html +how + +// hsbc : HSBC Global Services (UK) Limited +// https://www.iana.org/domains/root/db/hsbc.html +hsbc + +// hughes : Hughes Satellite Systems Corporation +// https://www.iana.org/domains/root/db/hughes.html +hughes + +// hyatt : Hyatt GTLD, L.L.C. +// https://www.iana.org/domains/root/db/hyatt.html +hyatt + +// hyundai : Hyundai Motor Company +// https://www.iana.org/domains/root/db/hyundai.html +hyundai + +// ibm : International Business Machines Corporation +// https://www.iana.org/domains/root/db/ibm.html +ibm + +// icbc : Industrial and Commercial Bank of China Limited +// https://www.iana.org/domains/root/db/icbc.html +icbc + +// ice : IntercontinentalExchange, Inc. +// https://www.iana.org/domains/root/db/ice.html +ice + +// icu : ShortDot SA +// https://www.iana.org/domains/root/db/icu.html +icu + +// ieee : IEEE Global LLC +// https://www.iana.org/domains/root/db/ieee.html +ieee + +// ifm : ifm electronic gmbh +// https://www.iana.org/domains/root/db/ifm.html +ifm + +// ikano : Ikano S.A. +// https://www.iana.org/domains/root/db/ikano.html +ikano + +// imamat : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/imamat.html +imamat + +// imdb : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/imdb.html +imdb + +// immo : Binky Moon, LLC +// https://www.iana.org/domains/root/db/immo.html +immo + +// immobilien : Dog Beach, LLC +// https://www.iana.org/domains/root/db/immobilien.html +immobilien + +// inc : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/inc.html +inc + +// industries : Binky Moon, LLC +// https://www.iana.org/domains/root/db/industries.html +industries + +// infiniti : NISSAN MOTOR CO., LTD. +// https://www.iana.org/domains/root/db/infiniti.html +infiniti + +// ing : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/ing.html +ing + +// ink : Registry Services, LLC +// https://www.iana.org/domains/root/db/ink.html +ink + +// institute : Binky Moon, LLC +// https://www.iana.org/domains/root/db/institute.html +institute + +// insurance : fTLD Registry Services LLC +// https://www.iana.org/domains/root/db/insurance.html +insurance + +// insure : Binky Moon, LLC +// https://www.iana.org/domains/root/db/insure.html +insure + +// international : Binky Moon, LLC +// https://www.iana.org/domains/root/db/international.html +international + +// intuit : Intuit Administrative Services, Inc. +// https://www.iana.org/domains/root/db/intuit.html +intuit + +// investments : Binky Moon, LLC +// https://www.iana.org/domains/root/db/investments.html +investments + +// ipiranga : Ipiranga Produtos de Petroleo S.A. +// https://www.iana.org/domains/root/db/ipiranga.html +ipiranga + +// irish : Binky Moon, LLC +// https://www.iana.org/domains/root/db/irish.html +irish + +// ismaili : Fondation Aga Khan (Aga Khan Foundation) +// https://www.iana.org/domains/root/db/ismaili.html +ismaili + +// ist : Istanbul Metropolitan Municipality +// https://www.iana.org/domains/root/db/ist.html +ist + +// istanbul : Istanbul Metropolitan Municipality +// https://www.iana.org/domains/root/db/istanbul.html +istanbul + +// itau : Itau Unibanco Holding S.A. +// https://www.iana.org/domains/root/db/itau.html +itau + +// itv : ITV Services Limited +// https://www.iana.org/domains/root/db/itv.html +itv + +// jaguar : Jaguar Land Rover Ltd +// https://www.iana.org/domains/root/db/jaguar.html +jaguar + +// java : Oracle Corporation +// https://www.iana.org/domains/root/db/java.html +java + +// jcb : JCB Co., Ltd. +// https://www.iana.org/domains/root/db/jcb.html +jcb + +// jeep : FCA US LLC. +// https://www.iana.org/domains/root/db/jeep.html +jeep + +// jetzt : Binky Moon, LLC +// https://www.iana.org/domains/root/db/jetzt.html +jetzt + +// jewelry : Binky Moon, LLC +// https://www.iana.org/domains/root/db/jewelry.html +jewelry + +// jio : Reliance Industries Limited +// https://www.iana.org/domains/root/db/jio.html +jio + +// jll : Jones Lang LaSalle Incorporated +// https://www.iana.org/domains/root/db/jll.html +jll + +// jmp : Matrix IP LLC +// https://www.iana.org/domains/root/db/jmp.html +jmp + +// jnj : Johnson & Johnson Services, Inc. +// https://www.iana.org/domains/root/db/jnj.html +jnj + +// joburg : ZA Central Registry NPC trading as ZA Central Registry +// https://www.iana.org/domains/root/db/joburg.html +joburg + +// jot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/jot.html +jot + +// joy : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/joy.html +joy + +// jpmorgan : JPMorgan Chase Bank, National Association +// https://www.iana.org/domains/root/db/jpmorgan.html +jpmorgan + +// jprs : Japan Registry Services Co., Ltd. +// https://www.iana.org/domains/root/db/jprs.html +jprs + +// juegos : Dog Beach, LLC +// https://www.iana.org/domains/root/db/juegos.html +juegos + +// juniper : JUNIPER NETWORKS, INC. +// https://www.iana.org/domains/root/db/juniper.html +juniper + +// kaufen : Dog Beach, LLC +// https://www.iana.org/domains/root/db/kaufen.html +kaufen + +// kddi : KDDI CORPORATION +// https://www.iana.org/domains/root/db/kddi.html +kddi + +// kerryhotels : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/kerryhotels.html +kerryhotels + +// kerrylogistics : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/kerrylogistics.html +kerrylogistics + +// kerryproperties : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/kerryproperties.html +kerryproperties + +// kfh : Kuwait Finance House +// https://www.iana.org/domains/root/db/kfh.html +kfh + +// kia : KIA MOTORS CORPORATION +// https://www.iana.org/domains/root/db/kia.html +kia + +// kids : DotKids Foundation Limited +// https://www.iana.org/domains/root/db/kids.html +kids + +// kim : Identity Digital Limited +// https://www.iana.org/domains/root/db/kim.html +kim + +// kindle : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/kindle.html +kindle + +// kitchen : Binky Moon, LLC +// https://www.iana.org/domains/root/db/kitchen.html +kitchen + +// kiwi : DOT KIWI LIMITED +// https://www.iana.org/domains/root/db/kiwi.html +kiwi + +// koeln : dotKoeln GmbH +// https://www.iana.org/domains/root/db/koeln.html +koeln + +// komatsu : Komatsu Ltd. +// https://www.iana.org/domains/root/db/komatsu.html +komatsu + +// kosher : Kosher Marketing Assets LLC +// https://www.iana.org/domains/root/db/kosher.html +kosher + +// kpmg : KPMG International Cooperative (KPMG International Genossenschaft) +// https://www.iana.org/domains/root/db/kpmg.html +kpmg + +// kpn : Koninklijke KPN N.V. +// https://www.iana.org/domains/root/db/kpn.html +kpn + +// krd : KRG Department of Information Technology +// https://www.iana.org/domains/root/db/krd.html +krd + +// kred : KredTLD Pty Ltd +// https://www.iana.org/domains/root/db/kred.html +kred + +// kuokgroup : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/kuokgroup.html +kuokgroup + +// kyoto : Academic Institution: Kyoto Jyoho Gakuen +// https://www.iana.org/domains/root/db/kyoto.html +kyoto + +// lacaixa : Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa” +// https://www.iana.org/domains/root/db/lacaixa.html +lacaixa + +// lamborghini : Automobili Lamborghini S.p.A. +// https://www.iana.org/domains/root/db/lamborghini.html +lamborghini + +// lamer : The Estée Lauder Companies Inc. +// https://www.iana.org/domains/root/db/lamer.html +lamer + +// lancaster : LANCASTER +// https://www.iana.org/domains/root/db/lancaster.html +lancaster + +// land : Binky Moon, LLC +// https://www.iana.org/domains/root/db/land.html +land + +// landrover : Jaguar Land Rover Ltd +// https://www.iana.org/domains/root/db/landrover.html +landrover + +// lanxess : LANXESS Corporation +// https://www.iana.org/domains/root/db/lanxess.html +lanxess + +// lasalle : Jones Lang LaSalle Incorporated +// https://www.iana.org/domains/root/db/lasalle.html +lasalle + +// lat : XYZ.COM LLC +// https://www.iana.org/domains/root/db/lat.html +lat + +// latino : Dish DBS Corporation +// https://www.iana.org/domains/root/db/latino.html +latino + +// latrobe : La Trobe University +// https://www.iana.org/domains/root/db/latrobe.html +latrobe + +// law : Registry Services, LLC +// https://www.iana.org/domains/root/db/law.html +law + +// lawyer : Dog Beach, LLC +// https://www.iana.org/domains/root/db/lawyer.html +lawyer + +// lds : IRI Domain Management, LLC +// https://www.iana.org/domains/root/db/lds.html +lds + +// lease : Binky Moon, LLC +// https://www.iana.org/domains/root/db/lease.html +lease + +// leclerc : A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc +// https://www.iana.org/domains/root/db/leclerc.html +leclerc + +// lefrak : LeFrak Organization, Inc. +// https://www.iana.org/domains/root/db/lefrak.html +lefrak + +// legal : Binky Moon, LLC +// https://www.iana.org/domains/root/db/legal.html +legal + +// lego : LEGO Juris A/S +// https://www.iana.org/domains/root/db/lego.html +lego + +// lexus : TOYOTA MOTOR CORPORATION +// https://www.iana.org/domains/root/db/lexus.html +lexus + +// lgbt : Identity Digital Limited +// https://www.iana.org/domains/root/db/lgbt.html +lgbt + +// lidl : Schwarz Domains und Services GmbH & Co. KG +// https://www.iana.org/domains/root/db/lidl.html +lidl + +// life : Binky Moon, LLC +// https://www.iana.org/domains/root/db/life.html +life + +// lifeinsurance : American Council of Life Insurers +// https://www.iana.org/domains/root/db/lifeinsurance.html +lifeinsurance + +// lifestyle : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/lifestyle.html +lifestyle + +// lighting : Binky Moon, LLC +// https://www.iana.org/domains/root/db/lighting.html +lighting + +// like : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/like.html +like + +// lilly : Eli Lilly and Company +// https://www.iana.org/domains/root/db/lilly.html +lilly + +// limited : Binky Moon, LLC +// https://www.iana.org/domains/root/db/limited.html +limited + +// limo : Binky Moon, LLC +// https://www.iana.org/domains/root/db/limo.html +limo + +// lincoln : Ford Motor Company +// https://www.iana.org/domains/root/db/lincoln.html +lincoln + +// link : Nova Registry Ltd +// https://www.iana.org/domains/root/db/link.html +link + +// lipsy : Lipsy Ltd +// https://www.iana.org/domains/root/db/lipsy.html +lipsy + +// live : Dog Beach, LLC +// https://www.iana.org/domains/root/db/live.html +live + +// living : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/living.html +living + +// llc : Identity Digital Limited +// https://www.iana.org/domains/root/db/llc.html +llc + +// llp : Intercap Registry Inc. +// https://www.iana.org/domains/root/db/llp.html +llp + +// loan : dot Loan Limited +// https://www.iana.org/domains/root/db/loan.html +loan + +// loans : Binky Moon, LLC +// https://www.iana.org/domains/root/db/loans.html +loans + +// locker : Orange Domains LLC +// https://www.iana.org/domains/root/db/locker.html +locker + +// locus : Locus Analytics LLC +// https://www.iana.org/domains/root/db/locus.html +locus + +// lol : XYZ.COM LLC +// https://www.iana.org/domains/root/db/lol.html +lol + +// london : Dot London Domains Limited +// https://www.iana.org/domains/root/db/london.html +london + +// lotte : Lotte Holdings Co., Ltd. +// https://www.iana.org/domains/root/db/lotte.html +lotte + +// lotto : Identity Digital Limited +// https://www.iana.org/domains/root/db/lotto.html +lotto + +// love : Waterford Limited +// https://www.iana.org/domains/root/db/love.html +love + +// lpl : LPL Holdings, Inc. +// https://www.iana.org/domains/root/db/lpl.html +lpl + +// lplfinancial : LPL Holdings, Inc. +// https://www.iana.org/domains/root/db/lplfinancial.html +lplfinancial + +// ltd : Binky Moon, LLC +// https://www.iana.org/domains/root/db/ltd.html +ltd + +// ltda : InterNetX, Corp +// https://www.iana.org/domains/root/db/ltda.html +ltda + +// lundbeck : H. Lundbeck A/S +// https://www.iana.org/domains/root/db/lundbeck.html +lundbeck + +// luxe : Registry Services, LLC +// https://www.iana.org/domains/root/db/luxe.html +luxe + +// luxury : Luxury Partners, LLC +// https://www.iana.org/domains/root/db/luxury.html +luxury + +// madrid : Comunidad de Madrid +// https://www.iana.org/domains/root/db/madrid.html +madrid + +// maif : Mutuelle Assurance Instituteur France (MAIF) +// https://www.iana.org/domains/root/db/maif.html +maif + +// maison : Binky Moon, LLC +// https://www.iana.org/domains/root/db/maison.html +maison + +// makeup : XYZ.COM LLC +// https://www.iana.org/domains/root/db/makeup.html +makeup + +// man : MAN Truck & Bus SE +// https://www.iana.org/domains/root/db/man.html +man + +// management : Binky Moon, LLC +// https://www.iana.org/domains/root/db/management.html +management + +// mango : PUNTO FA S.L. +// https://www.iana.org/domains/root/db/mango.html +mango + +// map : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/map.html +map + +// market : Dog Beach, LLC +// https://www.iana.org/domains/root/db/market.html +market + +// marketing : Binky Moon, LLC +// https://www.iana.org/domains/root/db/marketing.html +marketing + +// markets : Dog Beach, LLC +// https://www.iana.org/domains/root/db/markets.html +markets + +// marriott : Marriott Worldwide Corporation +// https://www.iana.org/domains/root/db/marriott.html +marriott + +// marshalls : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/marshalls.html +marshalls + +// mattel : Mattel Sites, Inc. +// https://www.iana.org/domains/root/db/mattel.html +mattel + +// mba : Binky Moon, LLC +// https://www.iana.org/domains/root/db/mba.html +mba + +// mckinsey : McKinsey Holdings, Inc. +// https://www.iana.org/domains/root/db/mckinsey.html +mckinsey + +// med : Medistry LLC +// https://www.iana.org/domains/root/db/med.html +med + +// media : Binky Moon, LLC +// https://www.iana.org/domains/root/db/media.html +media + +// meet : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/meet.html +meet + +// melbourne : The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation +// https://www.iana.org/domains/root/db/melbourne.html +melbourne + +// meme : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/meme.html +meme + +// memorial : Dog Beach, LLC +// https://www.iana.org/domains/root/db/memorial.html +memorial + +// men : Exclusive Registry Limited +// https://www.iana.org/domains/root/db/men.html +men + +// menu : Dot Menu Registry, LLC +// https://www.iana.org/domains/root/db/menu.html +menu + +// merck : Merck Registry Holdings, Inc. +// https://www.iana.org/domains/root/db/merck.html +merck + +// merckmsd : MSD Registry Holdings, Inc. +// https://www.iana.org/domains/root/db/merckmsd.html +merckmsd + +// miami : Registry Services, LLC +// https://www.iana.org/domains/root/db/miami.html +miami + +// microsoft : Microsoft Corporation +// https://www.iana.org/domains/root/db/microsoft.html +microsoft + +// mini : Bayerische Motoren Werke Aktiengesellschaft +// https://www.iana.org/domains/root/db/mini.html +mini + +// mint : Intuit Administrative Services, Inc. +// https://www.iana.org/domains/root/db/mint.html +mint + +// mit : Massachusetts Institute of Technology +// https://www.iana.org/domains/root/db/mit.html +mit + +// mitsubishi : Mitsubishi Corporation +// https://www.iana.org/domains/root/db/mitsubishi.html +mitsubishi + +// mlb : MLB Advanced Media DH, LLC +// https://www.iana.org/domains/root/db/mlb.html +mlb + +// mls : The Canadian Real Estate Association +// https://www.iana.org/domains/root/db/mls.html +mls + +// mma : MMA IARD +// https://www.iana.org/domains/root/db/mma.html +mma + +// mobile : Dish DBS Corporation +// https://www.iana.org/domains/root/db/mobile.html +mobile + +// moda : Dog Beach, LLC +// https://www.iana.org/domains/root/db/moda.html +moda + +// moe : Interlink Systems Innovation Institute K.K. +// https://www.iana.org/domains/root/db/moe.html +moe + +// moi : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/moi.html +moi + +// mom : XYZ.COM LLC +// https://www.iana.org/domains/root/db/mom.html +mom + +// monash : Monash University +// https://www.iana.org/domains/root/db/monash.html +monash + +// money : Binky Moon, LLC +// https://www.iana.org/domains/root/db/money.html +money + +// monster : XYZ.COM LLC +// https://www.iana.org/domains/root/db/monster.html +monster + +// mormon : IRI Domain Management, LLC +// https://www.iana.org/domains/root/db/mormon.html +mormon + +// mortgage : Dog Beach, LLC +// https://www.iana.org/domains/root/db/mortgage.html +mortgage + +// moscow : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) +// https://www.iana.org/domains/root/db/moscow.html +moscow + +// moto : Motorola Trademark Holdings, LLC +// https://www.iana.org/domains/root/db/moto.html +moto + +// motorcycles : XYZ.COM LLC +// https://www.iana.org/domains/root/db/motorcycles.html +motorcycles + +// mov : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/mov.html +mov + +// movie : Binky Moon, LLC +// https://www.iana.org/domains/root/db/movie.html +movie + +// msd : MSD Registry Holdings, Inc. +// https://www.iana.org/domains/root/db/msd.html +msd + +// mtn : MTN Dubai Limited +// https://www.iana.org/domains/root/db/mtn.html +mtn + +// mtr : MTR Corporation Limited +// https://www.iana.org/domains/root/db/mtr.html +mtr + +// music : DotMusic Limited +// https://www.iana.org/domains/root/db/music.html +music + +// nab : National Australia Bank Limited +// https://www.iana.org/domains/root/db/nab.html +nab + +// nagoya : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/nagoya.html +nagoya + +// navy : Dog Beach, LLC +// https://www.iana.org/domains/root/db/navy.html +navy + +// nba : NBA REGISTRY, LLC +// https://www.iana.org/domains/root/db/nba.html +nba + +// nec : NEC Corporation +// https://www.iana.org/domains/root/db/nec.html +nec + +// netbank : COMMONWEALTH BANK OF AUSTRALIA +// https://www.iana.org/domains/root/db/netbank.html +netbank + +// netflix : Netflix, Inc. +// https://www.iana.org/domains/root/db/netflix.html +netflix + +// network : Binky Moon, LLC +// https://www.iana.org/domains/root/db/network.html +network + +// neustar : NeuStar, Inc. +// https://www.iana.org/domains/root/db/neustar.html +neustar + +// new : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/new.html +new + +// news : Dog Beach, LLC +// https://www.iana.org/domains/root/db/news.html +news + +// next : Next plc +// https://www.iana.org/domains/root/db/next.html +next + +// nextdirect : Next plc +// https://www.iana.org/domains/root/db/nextdirect.html +nextdirect + +// nexus : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/nexus.html +nexus + +// nfl : NFL Reg Ops LLC +// https://www.iana.org/domains/root/db/nfl.html +nfl + +// ngo : Public Interest Registry +// https://www.iana.org/domains/root/db/ngo.html +ngo + +// nhk : Japan Broadcasting Corporation (NHK) +// https://www.iana.org/domains/root/db/nhk.html +nhk + +// nico : DWANGO Co., Ltd. +// https://www.iana.org/domains/root/db/nico.html +nico + +// nike : NIKE, Inc. +// https://www.iana.org/domains/root/db/nike.html +nike + +// nikon : NIKON CORPORATION +// https://www.iana.org/domains/root/db/nikon.html +nikon + +// ninja : Dog Beach, LLC +// https://www.iana.org/domains/root/db/ninja.html +ninja + +// nissan : NISSAN MOTOR CO., LTD. +// https://www.iana.org/domains/root/db/nissan.html +nissan + +// nissay : Nippon Life Insurance Company +// https://www.iana.org/domains/root/db/nissay.html +nissay + +// nokia : Nokia Corporation +// https://www.iana.org/domains/root/db/nokia.html +nokia + +// norton : Gen Digital Inc. +// https://www.iana.org/domains/root/db/norton.html +norton + +// now : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/now.html +now + +// nowruz : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +// https://www.iana.org/domains/root/db/nowruz.html +nowruz + +// nowtv : Starbucks (HK) Limited +// https://www.iana.org/domains/root/db/nowtv.html +nowtv + +// nra : NRA Holdings Company, INC. +// https://www.iana.org/domains/root/db/nra.html +nra + +// nrw : Minds + Machines GmbH +// https://www.iana.org/domains/root/db/nrw.html +nrw + +// ntt : NIPPON TELEGRAPH AND TELEPHONE CORPORATION +// https://www.iana.org/domains/root/db/ntt.html +ntt + +// nyc : The City of New York by and through the New York City Department of Information Technology & Telecommunications +// https://www.iana.org/domains/root/db/nyc.html +nyc + +// obi : OBI Group Holding SE & Co. KGaA +// https://www.iana.org/domains/root/db/obi.html +obi + +// observer : Fegistry, LLC +// https://www.iana.org/domains/root/db/observer.html +observer + +// office : Microsoft Corporation +// https://www.iana.org/domains/root/db/office.html +office + +// okinawa : BRregistry, Inc. +// https://www.iana.org/domains/root/db/okinawa.html +okinawa + +// olayan : Competrol (Luxembourg) Sarl +// https://www.iana.org/domains/root/db/olayan.html +olayan + +// olayangroup : Competrol (Luxembourg) Sarl +// https://www.iana.org/domains/root/db/olayangroup.html +olayangroup + +// ollo : Dish DBS Corporation +// https://www.iana.org/domains/root/db/ollo.html +ollo + +// omega : The Swatch Group Ltd +// https://www.iana.org/domains/root/db/omega.html +omega + +// one : One.com A/S +// https://www.iana.org/domains/root/db/one.html +one + +// ong : Public Interest Registry +// https://www.iana.org/domains/root/db/ong.html +ong + +// onl : iRegistry GmbH +// https://www.iana.org/domains/root/db/onl.html +onl + +// online : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/online.html +online + +// ooo : INFIBEAM AVENUES LIMITED +// https://www.iana.org/domains/root/db/ooo.html +ooo + +// open : American Express Travel Related Services Company, Inc. +// https://www.iana.org/domains/root/db/open.html +open + +// oracle : Oracle Corporation +// https://www.iana.org/domains/root/db/oracle.html +oracle + +// orange : Orange Brand Services Limited +// https://www.iana.org/domains/root/db/orange.html +orange + +// organic : Identity Digital Limited +// https://www.iana.org/domains/root/db/organic.html +organic + +// origins : The Estée Lauder Companies Inc. +// https://www.iana.org/domains/root/db/origins.html +origins + +// osaka : Osaka Registry Co., Ltd. +// https://www.iana.org/domains/root/db/osaka.html +osaka + +// otsuka : Otsuka Holdings Co., Ltd. +// https://www.iana.org/domains/root/db/otsuka.html +otsuka + +// ott : Dish DBS Corporation +// https://www.iana.org/domains/root/db/ott.html +ott + +// ovh : MédiaBC +// https://www.iana.org/domains/root/db/ovh.html +ovh + +// page : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/page.html +page + +// panasonic : Panasonic Holdings Corporation +// https://www.iana.org/domains/root/db/panasonic.html +panasonic + +// paris : City of Paris +// https://www.iana.org/domains/root/db/paris.html +paris + +// pars : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +// https://www.iana.org/domains/root/db/pars.html +pars + +// partners : Binky Moon, LLC +// https://www.iana.org/domains/root/db/partners.html +partners + +// parts : Binky Moon, LLC +// https://www.iana.org/domains/root/db/parts.html +parts + +// party : Blue Sky Registry Limited +// https://www.iana.org/domains/root/db/party.html +party + +// pay : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/pay.html +pay + +// pccw : PCCW Enterprises Limited +// https://www.iana.org/domains/root/db/pccw.html +pccw + +// pet : Identity Digital Limited +// https://www.iana.org/domains/root/db/pet.html +pet + +// pfizer : Pfizer Inc. +// https://www.iana.org/domains/root/db/pfizer.html +pfizer + +// pharmacy : National Association of Boards of Pharmacy +// https://www.iana.org/domains/root/db/pharmacy.html +pharmacy + +// phd : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/phd.html +phd + +// philips : Koninklijke Philips N.V. +// https://www.iana.org/domains/root/db/philips.html +philips + +// phone : Dish DBS Corporation +// https://www.iana.org/domains/root/db/phone.html +phone + +// photo : Registry Services, LLC +// https://www.iana.org/domains/root/db/photo.html +photo + +// photography : Binky Moon, LLC +// https://www.iana.org/domains/root/db/photography.html +photography + +// photos : Binky Moon, LLC +// https://www.iana.org/domains/root/db/photos.html +photos + +// physio : PhysBiz Pty Ltd +// https://www.iana.org/domains/root/db/physio.html +physio + +// pics : XYZ.COM LLC +// https://www.iana.org/domains/root/db/pics.html +pics + +// pictet : Pictet Europe S.A. +// https://www.iana.org/domains/root/db/pictet.html +pictet + +// pictures : Binky Moon, LLC +// https://www.iana.org/domains/root/db/pictures.html +pictures + +// pid : Top Level Spectrum, Inc. +// https://www.iana.org/domains/root/db/pid.html +pid + +// pin : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/pin.html +pin + +// ping : Ping Registry Provider, Inc. +// https://www.iana.org/domains/root/db/ping.html +ping + +// pink : Identity Digital Limited +// https://www.iana.org/domains/root/db/pink.html +pink + +// pioneer : Pioneer Corporation +// https://www.iana.org/domains/root/db/pioneer.html +pioneer + +// pizza : Binky Moon, LLC +// https://www.iana.org/domains/root/db/pizza.html +pizza + +// place : Binky Moon, LLC +// https://www.iana.org/domains/root/db/place.html +place + +// play : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/play.html +play + +// playstation : Sony Interactive Entertainment Inc. +// https://www.iana.org/domains/root/db/playstation.html +playstation + +// plumbing : Binky Moon, LLC +// https://www.iana.org/domains/root/db/plumbing.html +plumbing + +// plus : Binky Moon, LLC +// https://www.iana.org/domains/root/db/plus.html +plus + +// pnc : PNC Domain Co., LLC +// https://www.iana.org/domains/root/db/pnc.html +pnc + +// pohl : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/pohl.html +pohl + +// poker : Identity Digital Limited +// https://www.iana.org/domains/root/db/poker.html +poker + +// politie : Politie Nederland +// https://www.iana.org/domains/root/db/politie.html +politie + +// porn : ICM Registry PN LLC +// https://www.iana.org/domains/root/db/porn.html +porn + +// pramerica : Prudential Financial, Inc. +// https://www.iana.org/domains/root/db/pramerica.html +pramerica + +// praxi : Praxi S.p.A. +// https://www.iana.org/domains/root/db/praxi.html +praxi + +// press : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/press.html +press + +// prime : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/prime.html +prime + +// prod : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/prod.html +prod + +// productions : Binky Moon, LLC +// https://www.iana.org/domains/root/db/productions.html +productions + +// prof : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/prof.html +prof + +// progressive : Progressive Casualty Insurance Company +// https://www.iana.org/domains/root/db/progressive.html +progressive + +// promo : Identity Digital Limited +// https://www.iana.org/domains/root/db/promo.html +promo + +// properties : Binky Moon, LLC +// https://www.iana.org/domains/root/db/properties.html +properties + +// property : Digital Property Infrastructure Limited +// https://www.iana.org/domains/root/db/property.html +property + +// protection : XYZ.COM LLC +// https://www.iana.org/domains/root/db/protection.html +protection + +// pru : Prudential Financial, Inc. +// https://www.iana.org/domains/root/db/pru.html +pru + +// prudential : Prudential Financial, Inc. +// https://www.iana.org/domains/root/db/prudential.html +prudential + +// pub : Dog Beach, LLC +// https://www.iana.org/domains/root/db/pub.html +pub + +// pwc : PricewaterhouseCoopers LLP +// https://www.iana.org/domains/root/db/pwc.html +pwc + +// qpon : dotQPON LLC +// https://www.iana.org/domains/root/db/qpon.html +qpon + +// quebec : PointQuébec Inc +// https://www.iana.org/domains/root/db/quebec.html +quebec + +// quest : XYZ.COM LLC +// https://www.iana.org/domains/root/db/quest.html +quest + +// racing : Premier Registry Limited +// https://www.iana.org/domains/root/db/racing.html +racing + +// radio : European Broadcasting Union (EBU) +// https://www.iana.org/domains/root/db/radio.html +radio + +// read : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/read.html +read + +// realestate : dotRealEstate LLC +// https://www.iana.org/domains/root/db/realestate.html +realestate + +// realtor : Real Estate Domains LLC +// https://www.iana.org/domains/root/db/realtor.html +realtor + +// realty : Waterford Limited +// https://www.iana.org/domains/root/db/realty.html +realty + +// recipes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/recipes.html +recipes + +// red : Identity Digital Limited +// https://www.iana.org/domains/root/db/red.html +red + +// redstone : Redstone Haute Couture Co., Ltd. +// https://www.iana.org/domains/root/db/redstone.html +redstone + +// redumbrella : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/redumbrella.html +redumbrella + +// rehab : Dog Beach, LLC +// https://www.iana.org/domains/root/db/rehab.html +rehab + +// reise : Binky Moon, LLC +// https://www.iana.org/domains/root/db/reise.html +reise + +// reisen : Binky Moon, LLC +// https://www.iana.org/domains/root/db/reisen.html +reisen + +// reit : National Association of Real Estate Investment Trusts, Inc. +// https://www.iana.org/domains/root/db/reit.html +reit + +// reliance : Reliance Industries Limited +// https://www.iana.org/domains/root/db/reliance.html +reliance + +// ren : ZDNS International Limited +// https://www.iana.org/domains/root/db/ren.html +ren + +// rent : XYZ.COM LLC +// https://www.iana.org/domains/root/db/rent.html +rent + +// rentals : Binky Moon, LLC +// https://www.iana.org/domains/root/db/rentals.html +rentals + +// repair : Binky Moon, LLC +// https://www.iana.org/domains/root/db/repair.html +repair + +// report : Binky Moon, LLC +// https://www.iana.org/domains/root/db/report.html +report + +// republican : Dog Beach, LLC +// https://www.iana.org/domains/root/db/republican.html +republican + +// rest : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable +// https://www.iana.org/domains/root/db/rest.html +rest + +// restaurant : Binky Moon, LLC +// https://www.iana.org/domains/root/db/restaurant.html +restaurant + +// review : dot Review Limited +// https://www.iana.org/domains/root/db/review.html +review + +// reviews : Dog Beach, LLC +// https://www.iana.org/domains/root/db/reviews.html +reviews + +// rexroth : Robert Bosch GMBH +// https://www.iana.org/domains/root/db/rexroth.html +rexroth + +// rich : iRegistry GmbH +// https://www.iana.org/domains/root/db/rich.html +rich + +// richardli : Pacific Century Asset Management (HK) Limited +// https://www.iana.org/domains/root/db/richardli.html +richardli + +// ricoh : Ricoh Company, Ltd. +// https://www.iana.org/domains/root/db/ricoh.html +ricoh + +// ril : Reliance Industries Limited +// https://www.iana.org/domains/root/db/ril.html +ril + +// rio : Empresa Municipal de Informática SA - IPLANRIO +// https://www.iana.org/domains/root/db/rio.html +rio + +// rip : Dog Beach, LLC +// https://www.iana.org/domains/root/db/rip.html +rip + +// rocks : Dog Beach, LLC +// https://www.iana.org/domains/root/db/rocks.html +rocks + +// rodeo : Registry Services, LLC +// https://www.iana.org/domains/root/db/rodeo.html +rodeo + +// rogers : Rogers Communications Canada Inc. +// https://www.iana.org/domains/root/db/rogers.html +rogers + +// room : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/room.html +room + +// rsvp : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/rsvp.html +rsvp + +// rugby : World Rugby Strategic Developments Limited +// https://www.iana.org/domains/root/db/rugby.html +rugby + +// ruhr : dotSaarland GmbH +// https://www.iana.org/domains/root/db/ruhr.html +ruhr + +// run : Binky Moon, LLC +// https://www.iana.org/domains/root/db/run.html +run + +// rwe : RWE AG +// https://www.iana.org/domains/root/db/rwe.html +rwe + +// ryukyu : BRregistry, Inc. +// https://www.iana.org/domains/root/db/ryukyu.html +ryukyu + +// saarland : dotSaarland GmbH +// https://www.iana.org/domains/root/db/saarland.html +saarland + +// safe : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/safe.html +safe + +// safety : Safety Registry Services, LLC. +// https://www.iana.org/domains/root/db/safety.html +safety + +// sakura : SAKURA Internet Inc. +// https://www.iana.org/domains/root/db/sakura.html +sakura + +// sale : Dog Beach, LLC +// https://www.iana.org/domains/root/db/sale.html +sale + +// salon : Binky Moon, LLC +// https://www.iana.org/domains/root/db/salon.html +salon + +// samsclub : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/samsclub.html +samsclub + +// samsung : SAMSUNG SDS CO., LTD +// https://www.iana.org/domains/root/db/samsung.html +samsung + +// sandvik : Sandvik AB +// https://www.iana.org/domains/root/db/sandvik.html +sandvik + +// sandvikcoromant : Sandvik AB +// https://www.iana.org/domains/root/db/sandvikcoromant.html +sandvikcoromant + +// sanofi : Sanofi +// https://www.iana.org/domains/root/db/sanofi.html +sanofi + +// sap : SAP AG +// https://www.iana.org/domains/root/db/sap.html +sap + +// sarl : Binky Moon, LLC +// https://www.iana.org/domains/root/db/sarl.html +sarl + +// sas : Research IP LLC +// https://www.iana.org/domains/root/db/sas.html +sas + +// save : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/save.html +save + +// saxo : Saxo Bank A/S +// https://www.iana.org/domains/root/db/saxo.html +saxo + +// sbi : STATE BANK OF INDIA +// https://www.iana.org/domains/root/db/sbi.html +sbi + +// sbs : ShortDot SA +// https://www.iana.org/domains/root/db/sbs.html +sbs + +// scb : The Siam Commercial Bank Public Company Limited ("SCB") +// https://www.iana.org/domains/root/db/scb.html +scb + +// schaeffler : Schaeffler Technologies AG & Co. KG +// https://www.iana.org/domains/root/db/schaeffler.html +schaeffler + +// schmidt : SCHMIDT GROUPE S.A.S. +// https://www.iana.org/domains/root/db/schmidt.html +schmidt + +// scholarships : Scholarships.com, LLC +// https://www.iana.org/domains/root/db/scholarships.html +scholarships + +// school : Binky Moon, LLC +// https://www.iana.org/domains/root/db/school.html +school + +// schule : Binky Moon, LLC +// https://www.iana.org/domains/root/db/schule.html +schule + +// schwarz : Schwarz Domains und Services GmbH & Co. KG +// https://www.iana.org/domains/root/db/schwarz.html +schwarz + +// science : dot Science Limited +// https://www.iana.org/domains/root/db/science.html +science + +// scot : Dot Scot Registry Limited +// https://www.iana.org/domains/root/db/scot.html +scot + +// search : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/search.html +search + +// seat : SEAT, S.A. (Sociedad Unipersonal) +// https://www.iana.org/domains/root/db/seat.html +seat + +// secure : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/secure.html +secure + +// security : XYZ.COM LLC +// https://www.iana.org/domains/root/db/security.html +security + +// seek : Seek Limited +// https://www.iana.org/domains/root/db/seek.html +seek + +// select : Registry Services, LLC +// https://www.iana.org/domains/root/db/select.html +select + +// sener : Sener Ingeniería y Sistemas, S.A. +// https://www.iana.org/domains/root/db/sener.html +sener + +// services : Binky Moon, LLC +// https://www.iana.org/domains/root/db/services.html +services + +// seven : Seven West Media Ltd +// https://www.iana.org/domains/root/db/seven.html +seven + +// sew : SEW-EURODRIVE GmbH & Co KG +// https://www.iana.org/domains/root/db/sew.html +sew + +// sex : ICM Registry SX LLC +// https://www.iana.org/domains/root/db/sex.html +sex + +// sexy : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/sexy.html +sexy + +// sfr : Societe Francaise du Radiotelephone - SFR +// https://www.iana.org/domains/root/db/sfr.html +sfr + +// shangrila : Shangri‐La International Hotel Management Limited +// https://www.iana.org/domains/root/db/shangrila.html +shangrila + +// sharp : Sharp Corporation +// https://www.iana.org/domains/root/db/sharp.html +sharp + +// shell : Shell Information Technology International Inc +// https://www.iana.org/domains/root/db/shell.html +shell + +// shia : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +// https://www.iana.org/domains/root/db/shia.html +shia + +// shiksha : Identity Digital Limited +// https://www.iana.org/domains/root/db/shiksha.html +shiksha + +// shoes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/shoes.html +shoes + +// shop : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/shop.html +shop + +// shopping : Binky Moon, LLC +// https://www.iana.org/domains/root/db/shopping.html +shopping + +// shouji : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/shouji.html +shouji + +// show : Binky Moon, LLC +// https://www.iana.org/domains/root/db/show.html +show + +// silk : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/silk.html +silk + +// sina : Sina Corporation +// https://www.iana.org/domains/root/db/sina.html +sina + +// singles : Binky Moon, LLC +// https://www.iana.org/domains/root/db/singles.html +singles + +// site : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/site.html +site + +// ski : Identity Digital Limited +// https://www.iana.org/domains/root/db/ski.html +ski + +// skin : XYZ.COM LLC +// https://www.iana.org/domains/root/db/skin.html +skin + +// sky : Sky International AG +// https://www.iana.org/domains/root/db/sky.html +sky + +// skype : Microsoft Corporation +// https://www.iana.org/domains/root/db/skype.html +skype + +// sling : DISH Technologies L.L.C. +// https://www.iana.org/domains/root/db/sling.html +sling + +// smart : Smart Communications, Inc. (SMART) +// https://www.iana.org/domains/root/db/smart.html +smart + +// smile : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/smile.html +smile + +// sncf : Société Nationale SNCF +// https://www.iana.org/domains/root/db/sncf.html +sncf + +// soccer : Binky Moon, LLC +// https://www.iana.org/domains/root/db/soccer.html +soccer + +// social : Dog Beach, LLC +// https://www.iana.org/domains/root/db/social.html +social + +// softbank : SoftBank Group Corp. +// https://www.iana.org/domains/root/db/softbank.html +softbank + +// software : Dog Beach, LLC +// https://www.iana.org/domains/root/db/software.html +software + +// sohu : Sohu.com Limited +// https://www.iana.org/domains/root/db/sohu.html +sohu + +// solar : Binky Moon, LLC +// https://www.iana.org/domains/root/db/solar.html +solar + +// solutions : Binky Moon, LLC +// https://www.iana.org/domains/root/db/solutions.html +solutions + +// song : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/song.html +song + +// sony : Sony Corporation +// https://www.iana.org/domains/root/db/sony.html +sony + +// soy : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/soy.html +soy + +// spa : Asia Spa and Wellness Promotion Council Limited +// https://www.iana.org/domains/root/db/spa.html +spa + +// space : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/space.html +space + +// sport : SportAccord +// https://www.iana.org/domains/root/db/sport.html +sport + +// spot : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/spot.html +spot + +// srl : InterNetX, Corp +// https://www.iana.org/domains/root/db/srl.html +srl + +// stada : STADA Arzneimittel AG +// https://www.iana.org/domains/root/db/stada.html +stada + +// staples : Staples, Inc. +// https://www.iana.org/domains/root/db/staples.html +staples + +// star : Star India Private Limited +// https://www.iana.org/domains/root/db/star.html +star + +// statebank : STATE BANK OF INDIA +// https://www.iana.org/domains/root/db/statebank.html +statebank + +// statefarm : State Farm Mutual Automobile Insurance Company +// https://www.iana.org/domains/root/db/statefarm.html +statefarm + +// stc : Saudi Telecom Company +// https://www.iana.org/domains/root/db/stc.html +stc + +// stcgroup : Saudi Telecom Company +// https://www.iana.org/domains/root/db/stcgroup.html +stcgroup + +// stockholm : Stockholms kommun +// https://www.iana.org/domains/root/db/stockholm.html +stockholm + +// storage : XYZ.COM LLC +// https://www.iana.org/domains/root/db/storage.html +storage + +// store : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/store.html +store + +// stream : dot Stream Limited +// https://www.iana.org/domains/root/db/stream.html +stream + +// studio : Dog Beach, LLC +// https://www.iana.org/domains/root/db/studio.html +studio + +// study : Registry Services, LLC +// https://www.iana.org/domains/root/db/study.html +study + +// style : Binky Moon, LLC +// https://www.iana.org/domains/root/db/style.html +style + +// sucks : Vox Populi Registry Ltd. +// https://www.iana.org/domains/root/db/sucks.html +sucks + +// supplies : Binky Moon, LLC +// https://www.iana.org/domains/root/db/supplies.html +supplies + +// supply : Binky Moon, LLC +// https://www.iana.org/domains/root/db/supply.html +supply + +// support : Binky Moon, LLC +// https://www.iana.org/domains/root/db/support.html +support + +// surf : Registry Services, LLC +// https://www.iana.org/domains/root/db/surf.html +surf + +// surgery : Binky Moon, LLC +// https://www.iana.org/domains/root/db/surgery.html +surgery + +// suzuki : SUZUKI MOTOR CORPORATION +// https://www.iana.org/domains/root/db/suzuki.html +suzuki + +// swatch : The Swatch Group Ltd +// https://www.iana.org/domains/root/db/swatch.html +swatch + +// swiss : Swiss Confederation +// https://www.iana.org/domains/root/db/swiss.html +swiss + +// sydney : State of New South Wales, Department of Premier and Cabinet +// https://www.iana.org/domains/root/db/sydney.html +sydney + +// systems : Binky Moon, LLC +// https://www.iana.org/domains/root/db/systems.html +systems + +// tab : Tabcorp Holdings Limited +// https://www.iana.org/domains/root/db/tab.html +tab + +// taipei : Taipei City Government +// https://www.iana.org/domains/root/db/taipei.html +taipei + +// talk : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/talk.html +talk + +// taobao : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/taobao.html +taobao + +// target : Target Domain Holdings, LLC +// https://www.iana.org/domains/root/db/target.html +target + +// tatamotors : Tata Motors Ltd +// https://www.iana.org/domains/root/db/tatamotors.html +tatamotors + +// tatar : Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" +// https://www.iana.org/domains/root/db/tatar.html +tatar + +// tattoo : Registry Services, LLC +// https://www.iana.org/domains/root/db/tattoo.html +tattoo + +// tax : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tax.html +tax + +// taxi : Binky Moon, LLC +// https://www.iana.org/domains/root/db/taxi.html +taxi + +// tci : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +// https://www.iana.org/domains/root/db/tci.html +tci + +// tdk : TDK Corporation +// https://www.iana.org/domains/root/db/tdk.html +tdk + +// team : Binky Moon, LLC +// https://www.iana.org/domains/root/db/team.html +team + +// tech : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/tech.html +tech + +// technology : Binky Moon, LLC +// https://www.iana.org/domains/root/db/technology.html +technology + +// temasek : Temasek Holdings (Private) Limited +// https://www.iana.org/domains/root/db/temasek.html +temasek + +// tennis : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tennis.html +tennis + +// teva : Teva Pharmaceutical Industries Limited +// https://www.iana.org/domains/root/db/teva.html +teva + +// thd : Home Depot Product Authority, LLC +// https://www.iana.org/domains/root/db/thd.html +thd + +// theater : Binky Moon, LLC +// https://www.iana.org/domains/root/db/theater.html +theater + +// theatre : XYZ.COM LLC +// https://www.iana.org/domains/root/db/theatre.html +theatre + +// tiaa : Teachers Insurance and Annuity Association of America +// https://www.iana.org/domains/root/db/tiaa.html +tiaa + +// tickets : XYZ.COM LLC +// https://www.iana.org/domains/root/db/tickets.html +tickets + +// tienda : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tienda.html +tienda + +// tips : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tips.html +tips + +// tires : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tires.html +tires + +// tirol : punkt Tirol GmbH +// https://www.iana.org/domains/root/db/tirol.html +tirol + +// tjmaxx : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/tjmaxx.html +tjmaxx + +// tjx : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/tjx.html +tjx + +// tkmaxx : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/tkmaxx.html +tkmaxx + +// tmall : Alibaba Group Holding Limited +// https://www.iana.org/domains/root/db/tmall.html +tmall + +// today : Binky Moon, LLC +// https://www.iana.org/domains/root/db/today.html +today + +// tokyo : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/tokyo.html +tokyo + +// tools : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tools.html +tools + +// top : .TOP Registry +// https://www.iana.org/domains/root/db/top.html +top + +// toray : Toray Industries, Inc. +// https://www.iana.org/domains/root/db/toray.html +toray + +// toshiba : TOSHIBA Corporation +// https://www.iana.org/domains/root/db/toshiba.html +toshiba + +// total : TotalEnergies SE +// https://www.iana.org/domains/root/db/total.html +total + +// tours : Binky Moon, LLC +// https://www.iana.org/domains/root/db/tours.html +tours + +// town : Binky Moon, LLC +// https://www.iana.org/domains/root/db/town.html +town + +// toyota : TOYOTA MOTOR CORPORATION +// https://www.iana.org/domains/root/db/toyota.html +toyota + +// toys : Binky Moon, LLC +// https://www.iana.org/domains/root/db/toys.html +toys + +// trade : Elite Registry Limited +// https://www.iana.org/domains/root/db/trade.html +trade + +// trading : Dog Beach, LLC +// https://www.iana.org/domains/root/db/trading.html +trading + +// training : Binky Moon, LLC +// https://www.iana.org/domains/root/db/training.html +training + +// travel : Dog Beach, LLC +// https://www.iana.org/domains/root/db/travel.html +travel + +// travelers : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/travelers.html +travelers + +// travelersinsurance : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/travelersinsurance.html +travelersinsurance + +// trust : Internet Naming Company LLC +// https://www.iana.org/domains/root/db/trust.html +trust + +// trv : Travelers TLD, LLC +// https://www.iana.org/domains/root/db/trv.html +trv + +// tube : Latin American Telecom LLC +// https://www.iana.org/domains/root/db/tube.html +tube + +// tui : TUI AG +// https://www.iana.org/domains/root/db/tui.html +tui + +// tunes : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/tunes.html +tunes + +// tushu : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/tushu.html +tushu + +// tvs : T V SUNDRAM IYENGAR & SONS LIMITED +// https://www.iana.org/domains/root/db/tvs.html +tvs + +// ubank : National Australia Bank Limited +// https://www.iana.org/domains/root/db/ubank.html +ubank + +// ubs : UBS AG +// https://www.iana.org/domains/root/db/ubs.html +ubs + +// unicom : China United Network Communications Corporation Limited +// https://www.iana.org/domains/root/db/unicom.html +unicom + +// university : Binky Moon, LLC +// https://www.iana.org/domains/root/db/university.html +university + +// uno : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/uno.html +uno + +// uol : UBN INTERNET LTDA. +// https://www.iana.org/domains/root/db/uol.html +uol + +// ups : UPS Market Driver, Inc. +// https://www.iana.org/domains/root/db/ups.html +ups + +// vacations : Binky Moon, LLC +// https://www.iana.org/domains/root/db/vacations.html +vacations + +// vana : D3 Registry LLC +// https://www.iana.org/domains/root/db/vana.html +vana + +// vanguard : The Vanguard Group, Inc. +// https://www.iana.org/domains/root/db/vanguard.html +vanguard + +// vegas : Dot Vegas, Inc. +// https://www.iana.org/domains/root/db/vegas.html +vegas + +// ventures : Binky Moon, LLC +// https://www.iana.org/domains/root/db/ventures.html +ventures + +// verisign : VeriSign, Inc. +// https://www.iana.org/domains/root/db/verisign.html +verisign + +// versicherung : tldbox GmbH +// https://www.iana.org/domains/root/db/versicherung.html +versicherung + +// vet : Dog Beach, LLC +// https://www.iana.org/domains/root/db/vet.html +vet + +// viajes : Binky Moon, LLC +// https://www.iana.org/domains/root/db/viajes.html +viajes + +// video : Dog Beach, LLC +// https://www.iana.org/domains/root/db/video.html +video + +// vig : VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe +// https://www.iana.org/domains/root/db/vig.html +vig + +// viking : Viking River Cruises (Bermuda) Ltd. +// https://www.iana.org/domains/root/db/viking.html +viking + +// villas : Binky Moon, LLC +// https://www.iana.org/domains/root/db/villas.html +villas + +// vin : Binky Moon, LLC +// https://www.iana.org/domains/root/db/vin.html +vin + +// vip : Registry Services, LLC +// https://www.iana.org/domains/root/db/vip.html +vip + +// virgin : Virgin Enterprises Limited +// https://www.iana.org/domains/root/db/virgin.html +virgin + +// visa : Visa Worldwide Pte. Limited +// https://www.iana.org/domains/root/db/visa.html +visa + +// vision : Binky Moon, LLC +// https://www.iana.org/domains/root/db/vision.html +vision + +// viva : Saudi Telecom Company +// https://www.iana.org/domains/root/db/viva.html +viva + +// vivo : Telefonica Brasil S.A. +// https://www.iana.org/domains/root/db/vivo.html +vivo + +// vlaanderen : DNS.be vzw +// https://www.iana.org/domains/root/db/vlaanderen.html +vlaanderen + +// vodka : Registry Services, LLC +// https://www.iana.org/domains/root/db/vodka.html +vodka + +// volvo : Volvo Holding Sverige Aktiebolag +// https://www.iana.org/domains/root/db/volvo.html +volvo + +// vote : Monolith Registry LLC +// https://www.iana.org/domains/root/db/vote.html +vote + +// voting : Valuetainment Corp. +// https://www.iana.org/domains/root/db/voting.html +voting + +// voto : Monolith Registry LLC +// https://www.iana.org/domains/root/db/voto.html +voto + +// voyage : Binky Moon, LLC +// https://www.iana.org/domains/root/db/voyage.html +voyage + +// wales : Nominet UK +// https://www.iana.org/domains/root/db/wales.html +wales + +// walmart : Wal-Mart Stores, Inc. +// https://www.iana.org/domains/root/db/walmart.html +walmart + +// walter : Sandvik AB +// https://www.iana.org/domains/root/db/walter.html +walter + +// wang : Zodiac Wang Limited +// https://www.iana.org/domains/root/db/wang.html +wang + +// wanggou : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/wanggou.html +wanggou + +// watch : Binky Moon, LLC +// https://www.iana.org/domains/root/db/watch.html +watch + +// watches : Identity Digital Limited +// https://www.iana.org/domains/root/db/watches.html +watches + +// weather : International Business Machines Corporation +// https://www.iana.org/domains/root/db/weather.html +weather + +// weatherchannel : International Business Machines Corporation +// https://www.iana.org/domains/root/db/weatherchannel.html +weatherchannel + +// webcam : dot Webcam Limited +// https://www.iana.org/domains/root/db/webcam.html +webcam + +// weber : Saint-Gobain Weber SA +// https://www.iana.org/domains/root/db/weber.html +weber + +// website : Radix Technologies Inc. +// https://www.iana.org/domains/root/db/website.html +website + +// wed +// https://www.iana.org/domains/root/db/wed.html +wed + +// wedding : Registry Services, LLC +// https://www.iana.org/domains/root/db/wedding.html +wedding + +// weibo : Sina Corporation +// https://www.iana.org/domains/root/db/weibo.html +weibo + +// weir : Weir Group IP Limited +// https://www.iana.org/domains/root/db/weir.html +weir + +// whoswho : Who's Who Registry +// https://www.iana.org/domains/root/db/whoswho.html +whoswho + +// wien : punkt.wien GmbH +// https://www.iana.org/domains/root/db/wien.html +wien + +// wiki : Registry Services, LLC +// https://www.iana.org/domains/root/db/wiki.html +wiki + +// williamhill : William Hill Organization Limited +// https://www.iana.org/domains/root/db/williamhill.html +williamhill + +// win : First Registry Limited +// https://www.iana.org/domains/root/db/win.html +win + +// windows : Microsoft Corporation +// https://www.iana.org/domains/root/db/windows.html +windows + +// wine : Binky Moon, LLC +// https://www.iana.org/domains/root/db/wine.html +wine + +// winners : The TJX Companies, Inc. +// https://www.iana.org/domains/root/db/winners.html +winners + +// wme : William Morris Endeavor Entertainment, LLC +// https://www.iana.org/domains/root/db/wme.html +wme + +// wolterskluwer : Wolters Kluwer N.V. +// https://www.iana.org/domains/root/db/wolterskluwer.html +wolterskluwer + +// woodside : Woodside Petroleum Limited +// https://www.iana.org/domains/root/db/woodside.html +woodside + +// work : Registry Services, LLC +// https://www.iana.org/domains/root/db/work.html +work + +// works : Binky Moon, LLC +// https://www.iana.org/domains/root/db/works.html +works + +// world : Binky Moon, LLC +// https://www.iana.org/domains/root/db/world.html +world + +// wow : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/wow.html +wow + +// wtc : World Trade Centers Association, Inc. +// https://www.iana.org/domains/root/db/wtc.html +wtc + +// wtf : Binky Moon, LLC +// https://www.iana.org/domains/root/db/wtf.html +wtf + +// xbox : Microsoft Corporation +// https://www.iana.org/domains/root/db/xbox.html +xbox + +// xerox : Xerox DNHC LLC +// https://www.iana.org/domains/root/db/xerox.html +xerox + +// xihuan : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/xihuan.html +xihuan + +// xin : Elegant Leader Limited +// https://www.iana.org/domains/root/db/xin.html +xin + +// xn--11b4c3d : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--11b4c3d.html +कॉम + +// xn--1ck2e1b : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--1ck2e1b.html +セール + +// xn--1qqw23a : Guangzhou YU Wei Information Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--1qqw23a.html +佛山 + +// xn--30rr7y : Excellent First Limited +// https://www.iana.org/domains/root/db/xn--30rr7y.html +慈善 + +// xn--3bst00m : Eagle Horizon Limited +// https://www.iana.org/domains/root/db/xn--3bst00m.html +集团 + +// xn--3ds443g : TLD REGISTRY LIMITED OY +// https://www.iana.org/domains/root/db/xn--3ds443g.html +在线 + +// xn--3pxu8k : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--3pxu8k.html +点看 + +// xn--42c2d9a : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--42c2d9a.html +คอม + +// xn--45q11c : Zodiac Gemini Ltd +// https://www.iana.org/domains/root/db/xn--45q11c.html +八卦 + +// xn--4gbrim : Helium TLDs Ltd +// https://www.iana.org/domains/root/db/xn--4gbrim.html +موقع + +// xn--55qw42g : China Organizational Name Administration Center +// https://www.iana.org/domains/root/db/xn--55qw42g.html +公益 + +// xn--55qx5d : China Internet Network Information Center (CNNIC) +// https://www.iana.org/domains/root/db/xn--55qx5d.html +公司 + +// xn--5su34j936bgsg : Shangri‐La International Hotel Management Limited +// https://www.iana.org/domains/root/db/xn--5su34j936bgsg.html +香格里拉 + +// xn--5tzm5g : Global Website TLD Asia Limited +// https://www.iana.org/domains/root/db/xn--5tzm5g.html +网站 + +// xn--6frz82g : Identity Digital Limited +// https://www.iana.org/domains/root/db/xn--6frz82g.html +移动 + +// xn--6qq986b3xl : Tycoon Treasure Limited +// https://www.iana.org/domains/root/db/xn--6qq986b3xl.html +我爱你 + +// xn--80adxhks : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) +// https://www.iana.org/domains/root/db/xn--80adxhks.html +москва + +// xn--80aqecdr1a : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/xn--80aqecdr1a.html +католик + +// xn--80asehdb : CORE Association +// https://www.iana.org/domains/root/db/xn--80asehdb.html +онлайн + +// xn--80aswg : CORE Association +// https://www.iana.org/domains/root/db/xn--80aswg.html +сайт + +// xn--8y0a063a : China United Network Communications Corporation Limited +// https://www.iana.org/domains/root/db/xn--8y0a063a.html +联通 + +// xn--9dbq2a : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--9dbq2a.html +קום + +// xn--9et52u : RISE VICTORY LIMITED +// https://www.iana.org/domains/root/db/xn--9et52u.html +时尚 + +// xn--9krt00a : Sina Corporation +// https://www.iana.org/domains/root/db/xn--9krt00a.html +微博 + +// xn--b4w605ferd : Temasek Holdings (Private) Limited +// https://www.iana.org/domains/root/db/xn--b4w605ferd.html +淡马锡 + +// xn--bck1b9a5dre4c : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--bck1b9a5dre4c.html +ファッション + +// xn--c1avg : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--c1avg.html +орг + +// xn--c2br7g : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--c2br7g.html +नेट + +// xn--cck2b3b : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--cck2b3b.html +ストア + +// xn--cckwcxetd : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--cckwcxetd.html +アマゾン + +// xn--cg4bki : SAMSUNG SDS CO., LTD +// https://www.iana.org/domains/root/db/xn--cg4bki.html +삼성 + +// xn--czr694b : Internet DotTrademark Organisation Limited +// https://www.iana.org/domains/root/db/xn--czr694b.html +商标 + +// xn--czrs0t : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--czrs0t.html +商店 + +// xn--czru2d : Zodiac Aquarius Limited +// https://www.iana.org/domains/root/db/xn--czru2d.html +商城 + +// xn--d1acj3b : The Foundation for Network Initiatives “The Smart Internet” +// https://www.iana.org/domains/root/db/xn--d1acj3b.html +дети + +// xn--eckvdtc9d : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--eckvdtc9d.html +ポイント + +// xn--efvy88h : Guangzhou YU Wei Information Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--efvy88h.html +新闻 + +// xn--fct429k : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--fct429k.html +家電 + +// xn--fhbei : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--fhbei.html +كوم + +// xn--fiq228c5hs : TLD REGISTRY LIMITED OY +// https://www.iana.org/domains/root/db/xn--fiq228c5hs.html +中文网 + +// xn--fiq64b : CITIC Group Corporation +// https://www.iana.org/domains/root/db/xn--fiq64b.html +中信 + +// xn--fjq720a : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--fjq720a.html +娱乐 + +// xn--flw351e : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/xn--flw351e.html +谷歌 + +// xn--fzys8d69uvgm : PCCW Enterprises Limited +// https://www.iana.org/domains/root/db/xn--fzys8d69uvgm.html +電訊盈科 + +// xn--g2xx48c : Nawang Heli(Xiamen) Network Service Co., LTD. +// https://www.iana.org/domains/root/db/xn--g2xx48c.html +购物 + +// xn--gckr3f0f : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--gckr3f0f.html +クラウド + +// xn--gk3at1e : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--gk3at1e.html +通販 + +// xn--hxt814e : Zodiac Taurus Limited +// https://www.iana.org/domains/root/db/xn--hxt814e.html +网店 + +// xn--i1b6b1a6a2e : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--i1b6b1a6a2e.html +संगठन + +// xn--imr513n : Internet DotTrademark Organisation Limited +// https://www.iana.org/domains/root/db/xn--imr513n.html +餐厅 + +// xn--io0a7i : China Internet Network Information Center (CNNIC) +// https://www.iana.org/domains/root/db/xn--io0a7i.html +网络 + +// xn--j1aef : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--j1aef.html +ком + +// xn--jlq480n2rg : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--jlq480n2rg.html +亚马逊 + +// xn--jvr189m : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--jvr189m.html +食品 + +// xn--kcrx77d1x4a : Koninklijke Philips N.V. +// https://www.iana.org/domains/root/db/xn--kcrx77d1x4a.html +飞利浦 + +// xn--kput3i : Beijing RITT-Net Technology Development Co., Ltd +// https://www.iana.org/domains/root/db/xn--kput3i.html +手机 + +// xn--mgba3a3ejt : Aramco Services Company +// https://www.iana.org/domains/root/db/xn--mgba3a3ejt.html +ارامكو + +// xn--mgba7c0bbn0a : Competrol (Luxembourg) Sarl +// https://www.iana.org/domains/root/db/xn--mgba7c0bbn0a.html +العليان + +// xn--mgbab2bd : CORE Association +// https://www.iana.org/domains/root/db/xn--mgbab2bd.html +بازار + +// xn--mgbca7dzdo : Abu Dhabi Systems and Information Centre +// https://www.iana.org/domains/root/db/xn--mgbca7dzdo.html +ابوظبي + +// xn--mgbi4ecexp : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/xn--mgbi4ecexp.html +كاثوليك + +// xn--mgbt3dhd : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. +// https://www.iana.org/domains/root/db/xn--mgbt3dhd.html +همراه + +// xn--mk1bu44c : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--mk1bu44c.html +닷컴 + +// xn--mxtq1m : Net-Chinese Co., Ltd. +// https://www.iana.org/domains/root/db/xn--mxtq1m.html +政府 + +// xn--ngbc5azd : International Domain Registry Pty. Ltd. +// https://www.iana.org/domains/root/db/xn--ngbc5azd.html +شبكة + +// xn--ngbe9e0a : Kuwait Finance House +// https://www.iana.org/domains/root/db/xn--ngbe9e0a.html +بيتك + +// xn--ngbrx : League of Arab States +// https://www.iana.org/domains/root/db/xn--ngbrx.html +عرب + +// xn--nqv7f : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--nqv7f.html +机构 + +// xn--nqv7fs00ema : Public Interest Registry +// https://www.iana.org/domains/root/db/xn--nqv7fs00ema.html +组织机构 + +// xn--nyqy26a : Stable Tone Limited +// https://www.iana.org/domains/root/db/xn--nyqy26a.html +健康 + +// xn--otu796d : Jiang Yu Liang Cai Technology Company Limited +// https://www.iana.org/domains/root/db/xn--otu796d.html +招聘 + +// xn--p1acf : Rusnames Limited +// https://www.iana.org/domains/root/db/xn--p1acf.html +рус + +// xn--pssy2u : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--pssy2u.html +大拿 + +// xn--q9jyb4c : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/xn--q9jyb4c.html +みんな + +// xn--qcka1pmc : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/xn--qcka1pmc.html +グーグル + +// xn--rhqv96g : Stable Tone Limited +// https://www.iana.org/domains/root/db/xn--rhqv96g.html +世界 + +// xn--rovu88b : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/xn--rovu88b.html +書籍 + +// xn--ses554g : KNET Co., Ltd. +// https://www.iana.org/domains/root/db/xn--ses554g.html +网址 + +// xn--t60b56a : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--t60b56a.html +닷넷 + +// xn--tckwe : VeriSign Sarl +// https://www.iana.org/domains/root/db/xn--tckwe.html +コム + +// xn--tiq49xqyj : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) +// https://www.iana.org/domains/root/db/xn--tiq49xqyj.html +天主教 + +// xn--unup4y : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--unup4y.html +游戏 + +// xn--vermgensberater-ctb : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/xn--vermgensberater-ctb.html +vermögensberater + +// xn--vermgensberatung-pwb : Deutsche Vermögensberatung Aktiengesellschaft DVAG +// https://www.iana.org/domains/root/db/xn--vermgensberatung-pwb.html +vermögensberatung + +// xn--vhquv : Binky Moon, LLC +// https://www.iana.org/domains/root/db/xn--vhquv.html +企业 + +// xn--vuq861b : Beijing Tele-info Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--vuq861b.html +信息 + +// xn--w4r85el8fhu5dnra : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/xn--w4r85el8fhu5dnra.html +嘉里大酒店 + +// xn--w4rs40l : Kerry Trading Co. Limited +// https://www.iana.org/domains/root/db/xn--w4rs40l.html +嘉里 + +// xn--xhq521b : Guangzhou YU Wei Information Technology Co., Ltd. +// https://www.iana.org/domains/root/db/xn--xhq521b.html +广东 + +// xn--zfr164b : China Organizational Name Administration Center +// https://www.iana.org/domains/root/db/xn--zfr164b.html +政务 + +// xyz : XYZ.COM LLC +// https://www.iana.org/domains/root/db/xyz.html +xyz + +// yachts : XYZ.COM LLC +// https://www.iana.org/domains/root/db/yachts.html +yachts + +// yahoo : Yahoo Inc. +// https://www.iana.org/domains/root/db/yahoo.html +yahoo + +// yamaxun : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/yamaxun.html +yamaxun + +// yandex : Yandex Europe B.V. +// https://www.iana.org/domains/root/db/yandex.html +yandex + +// yodobashi : YODOBASHI CAMERA CO.,LTD. +// https://www.iana.org/domains/root/db/yodobashi.html +yodobashi + +// yoga : Registry Services, LLC +// https://www.iana.org/domains/root/db/yoga.html +yoga + +// yokohama : GMO Registry, Inc. +// https://www.iana.org/domains/root/db/yokohama.html +yokohama + +// you : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/you.html +you + +// youtube : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/youtube.html +youtube + +// yun : Beijing Qihu Keji Co., Ltd. +// https://www.iana.org/domains/root/db/yun.html +yun + +// zappos : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/zappos.html +zappos + +// zara : Industria de Diseño Textil, S.A. (INDITEX, S.A.) +// https://www.iana.org/domains/root/db/zara.html +zara + +// zero : Amazon Registry Services, Inc. +// https://www.iana.org/domains/root/db/zero.html +zero + +// zip : Charleston Road Registry Inc. +// https://www.iana.org/domains/root/db/zip.html +zip + +// zone : Binky Moon, LLC +// https://www.iana.org/domains/root/db/zone.html +zone + +// zuerich : Kanton Zürich (Canton of Zurich) +// https://www.iana.org/domains/root/db/zuerich.html +zuerich + +// ===END ICANN DOMAINS=== + +// ===BEGIN PRIVATE DOMAINS=== + +// (Note: these are in alphabetical order by company name) + +// .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf +co.krd +edu.krd + +// .pl domains (grandfathered) +art.pl +gliwice.pl +krakow.pl +poznan.pl +wroc.pl +zakopane.pl + +// .US +// Submitted by Ed Moore +lib.de.us + +// 12CHARS: https://12chars.com +// Submitted by Kenny Niehage +12chars.dev +12chars.it +12chars.pro + +// 1GB LLC : https://www.1gb.ua/ +// Submitted by 1GB LLC +cc.ua +inf.ua +ltd.ua + +// 611 blockchain domain name system : https://611project.net/ +611.to + +// A2 Hosting +// Submitted by Tyler Hall +a2hosted.com +cpserver.com + +// AAA workspace : https://aaa.vodka +// Submitted by Kirill Rezraf +aaa.vodka + +// Acorn Labs : https://acorn.io +// Submitted by Craig Jellick +*.on-acorn.io + +// ActiveTrail: https://www.activetrail.biz/ +// Submitted by Ofer Kalaora +activetrail.biz + +// Adaptable.io : https://adaptable.io +// Submitted by Mark Terrel +adaptable.app + +// Adobe : https://www.adobe.com/ +// Submitted by Ian Boston and Lars Trieloff +adobeaemcloud.com +*.dev.adobeaemcloud.com +aem.live +hlx.live +adobeaemcloud.net +aem.page +hlx.page +hlx3.page + +// Adobe Developer Platform : https://developer.adobe.com +// Submitted by Jesse MacFadyen +adobeio-static.net +adobeioruntime.net + +// Africa.com Web Solutions Ltd : https://registry.africa.com +// Submitted by Gavin Brown +africa.com + +// Agnat sp. z o.o. : https://domena.pl +// Submitted by Przemyslaw Plewa +beep.pl + +// Airkit : https://www.airkit.com/ +// Submitted by Grant Cooksey +airkitapps.com +airkitapps-au.com +airkitapps.eu + +// Aiven: https://aiven.io/ +// Submitted by Etienne Stalmans +aivencloud.com + +// Akamai : https://www.akamai.com/ +// Submitted by Akamai Team +akadns.net +akamai.net +akamai-staging.net +akamaiedge.net +akamaiedge-staging.net +akamaihd.net +akamaihd-staging.net +akamaiorigin.net +akamaiorigin-staging.net +akamaized.net +akamaized-staging.net +edgekey.net +edgekey-staging.net +edgesuite.net +edgesuite-staging.net + +// alboto.ca : http://alboto.ca +// Submitted by Anton Avramov +barsy.ca + +// Alces Software Ltd : http://alces-software.com +// Submitted by Mark J. Titorenko +*.compute.estate +*.alces.network + +// all-inkl.com : https://all-inkl.com +// Submitted by Werner Kaltofen +kasserver.com + +// Altervista: https://www.altervista.org +// Submitted by Carlo Cannas +altervista.org + +// alwaysdata : https://www.alwaysdata.com +// Submitted by Cyril +alwaysdata.net + +// Amaze Software : https://amaze.co +// Submitted by Domain Admin +myamaze.net + +// Amazon : https://www.amazon.com/ +// Submitted by AWS Security +// Subsections of Amazon/subsidiaries will appear until "concludes" tag + +// Amazon API Gateway +// Submitted by AWS Security +// Reference: 6a4f5a95-8c7d-4077-a7af-9cf1abec0a53 +execute-api.cn-north-1.amazonaws.com.cn +execute-api.cn-northwest-1.amazonaws.com.cn +execute-api.af-south-1.amazonaws.com +execute-api.ap-east-1.amazonaws.com +execute-api.ap-northeast-1.amazonaws.com +execute-api.ap-northeast-2.amazonaws.com +execute-api.ap-northeast-3.amazonaws.com +execute-api.ap-south-1.amazonaws.com +execute-api.ap-south-2.amazonaws.com +execute-api.ap-southeast-1.amazonaws.com +execute-api.ap-southeast-2.amazonaws.com +execute-api.ap-southeast-3.amazonaws.com +execute-api.ap-southeast-4.amazonaws.com +execute-api.ap-southeast-5.amazonaws.com +execute-api.ca-central-1.amazonaws.com +execute-api.ca-west-1.amazonaws.com +execute-api.eu-central-1.amazonaws.com +execute-api.eu-central-2.amazonaws.com +execute-api.eu-north-1.amazonaws.com +execute-api.eu-south-1.amazonaws.com +execute-api.eu-south-2.amazonaws.com +execute-api.eu-west-1.amazonaws.com +execute-api.eu-west-2.amazonaws.com +execute-api.eu-west-3.amazonaws.com +execute-api.il-central-1.amazonaws.com +execute-api.me-central-1.amazonaws.com +execute-api.me-south-1.amazonaws.com +execute-api.sa-east-1.amazonaws.com +execute-api.us-east-1.amazonaws.com +execute-api.us-east-2.amazonaws.com +execute-api.us-gov-east-1.amazonaws.com +execute-api.us-gov-west-1.amazonaws.com +execute-api.us-west-1.amazonaws.com +execute-api.us-west-2.amazonaws.com + +// Amazon CloudFront +// Submitted by Donavan Miller +// Reference: 54144616-fd49-4435-8535-19c6a601bdb3 +cloudfront.net + +// Amazon Cognito +// Submitted by AWS Security +// Reference: cb38c251-c93d-4cda-81ec-e72c4f0fdb72 +auth.af-south-1.amazoncognito.com +auth.ap-east-1.amazoncognito.com +auth.ap-northeast-1.amazoncognito.com +auth.ap-northeast-2.amazoncognito.com +auth.ap-northeast-3.amazoncognito.com +auth.ap-south-1.amazoncognito.com +auth.ap-south-2.amazoncognito.com +auth.ap-southeast-1.amazoncognito.com +auth.ap-southeast-2.amazoncognito.com +auth.ap-southeast-3.amazoncognito.com +auth.ap-southeast-4.amazoncognito.com +auth.ca-central-1.amazoncognito.com +auth.ca-west-1.amazoncognito.com +auth.eu-central-1.amazoncognito.com +auth.eu-central-2.amazoncognito.com +auth.eu-north-1.amazoncognito.com +auth.eu-south-1.amazoncognito.com +auth.eu-south-2.amazoncognito.com +auth.eu-west-1.amazoncognito.com +auth.eu-west-2.amazoncognito.com +auth.eu-west-3.amazoncognito.com +auth.il-central-1.amazoncognito.com +auth.me-central-1.amazoncognito.com +auth.me-south-1.amazoncognito.com +auth.sa-east-1.amazoncognito.com +auth.us-east-1.amazoncognito.com +auth-fips.us-east-1.amazoncognito.com +auth.us-east-2.amazoncognito.com +auth-fips.us-east-2.amazoncognito.com +auth-fips.us-gov-west-1.amazoncognito.com +auth.us-west-1.amazoncognito.com +auth-fips.us-west-1.amazoncognito.com +auth.us-west-2.amazoncognito.com +auth-fips.us-west-2.amazoncognito.com + +// Amazon EC2 +// Submitted by Luke Wells +// Reference: 4c38fa71-58ac-4768-99e5-689c1767e537 +*.compute.amazonaws.com.cn +*.compute.amazonaws.com +*.compute-1.amazonaws.com +us-east-1.amazonaws.com + +// Amazon EMR +// Submitted by AWS Security +// Reference: 82f43f9f-bbb8-400e-8349-854f5a62f20d +emrappui-prod.cn-north-1.amazonaws.com.cn +emrnotebooks-prod.cn-north-1.amazonaws.com.cn +emrstudio-prod.cn-north-1.amazonaws.com.cn +emrappui-prod.cn-northwest-1.amazonaws.com.cn +emrnotebooks-prod.cn-northwest-1.amazonaws.com.cn +emrstudio-prod.cn-northwest-1.amazonaws.com.cn +emrappui-prod.af-south-1.amazonaws.com +emrnotebooks-prod.af-south-1.amazonaws.com +emrstudio-prod.af-south-1.amazonaws.com +emrappui-prod.ap-east-1.amazonaws.com +emrnotebooks-prod.ap-east-1.amazonaws.com +emrstudio-prod.ap-east-1.amazonaws.com +emrappui-prod.ap-northeast-1.amazonaws.com +emrnotebooks-prod.ap-northeast-1.amazonaws.com +emrstudio-prod.ap-northeast-1.amazonaws.com +emrappui-prod.ap-northeast-2.amazonaws.com +emrnotebooks-prod.ap-northeast-2.amazonaws.com +emrstudio-prod.ap-northeast-2.amazonaws.com +emrappui-prod.ap-northeast-3.amazonaws.com +emrnotebooks-prod.ap-northeast-3.amazonaws.com +emrstudio-prod.ap-northeast-3.amazonaws.com +emrappui-prod.ap-south-1.amazonaws.com +emrnotebooks-prod.ap-south-1.amazonaws.com +emrstudio-prod.ap-south-1.amazonaws.com +emrappui-prod.ap-south-2.amazonaws.com +emrnotebooks-prod.ap-south-2.amazonaws.com +emrstudio-prod.ap-south-2.amazonaws.com +emrappui-prod.ap-southeast-1.amazonaws.com +emrnotebooks-prod.ap-southeast-1.amazonaws.com +emrstudio-prod.ap-southeast-1.amazonaws.com +emrappui-prod.ap-southeast-2.amazonaws.com +emrnotebooks-prod.ap-southeast-2.amazonaws.com +emrstudio-prod.ap-southeast-2.amazonaws.com +emrappui-prod.ap-southeast-3.amazonaws.com +emrnotebooks-prod.ap-southeast-3.amazonaws.com +emrstudio-prod.ap-southeast-3.amazonaws.com +emrappui-prod.ap-southeast-4.amazonaws.com +emrnotebooks-prod.ap-southeast-4.amazonaws.com +emrstudio-prod.ap-southeast-4.amazonaws.com +emrappui-prod.ca-central-1.amazonaws.com +emrnotebooks-prod.ca-central-1.amazonaws.com +emrstudio-prod.ca-central-1.amazonaws.com +emrappui-prod.ca-west-1.amazonaws.com +emrnotebooks-prod.ca-west-1.amazonaws.com +emrstudio-prod.ca-west-1.amazonaws.com +emrappui-prod.eu-central-1.amazonaws.com +emrnotebooks-prod.eu-central-1.amazonaws.com +emrstudio-prod.eu-central-1.amazonaws.com +emrappui-prod.eu-central-2.amazonaws.com +emrnotebooks-prod.eu-central-2.amazonaws.com +emrstudio-prod.eu-central-2.amazonaws.com +emrappui-prod.eu-north-1.amazonaws.com +emrnotebooks-prod.eu-north-1.amazonaws.com +emrstudio-prod.eu-north-1.amazonaws.com +emrappui-prod.eu-south-1.amazonaws.com +emrnotebooks-prod.eu-south-1.amazonaws.com +emrstudio-prod.eu-south-1.amazonaws.com +emrappui-prod.eu-south-2.amazonaws.com +emrnotebooks-prod.eu-south-2.amazonaws.com +emrstudio-prod.eu-south-2.amazonaws.com +emrappui-prod.eu-west-1.amazonaws.com +emrnotebooks-prod.eu-west-1.amazonaws.com +emrstudio-prod.eu-west-1.amazonaws.com +emrappui-prod.eu-west-2.amazonaws.com +emrnotebooks-prod.eu-west-2.amazonaws.com +emrstudio-prod.eu-west-2.amazonaws.com +emrappui-prod.eu-west-3.amazonaws.com +emrnotebooks-prod.eu-west-3.amazonaws.com +emrstudio-prod.eu-west-3.amazonaws.com +emrappui-prod.il-central-1.amazonaws.com +emrnotebooks-prod.il-central-1.amazonaws.com +emrstudio-prod.il-central-1.amazonaws.com +emrappui-prod.me-central-1.amazonaws.com +emrnotebooks-prod.me-central-1.amazonaws.com +emrstudio-prod.me-central-1.amazonaws.com +emrappui-prod.me-south-1.amazonaws.com +emrnotebooks-prod.me-south-1.amazonaws.com +emrstudio-prod.me-south-1.amazonaws.com +emrappui-prod.sa-east-1.amazonaws.com +emrnotebooks-prod.sa-east-1.amazonaws.com +emrstudio-prod.sa-east-1.amazonaws.com +emrappui-prod.us-east-1.amazonaws.com +emrnotebooks-prod.us-east-1.amazonaws.com +emrstudio-prod.us-east-1.amazonaws.com +emrappui-prod.us-east-2.amazonaws.com +emrnotebooks-prod.us-east-2.amazonaws.com +emrstudio-prod.us-east-2.amazonaws.com +emrappui-prod.us-gov-east-1.amazonaws.com +emrnotebooks-prod.us-gov-east-1.amazonaws.com +emrstudio-prod.us-gov-east-1.amazonaws.com +emrappui-prod.us-gov-west-1.amazonaws.com +emrnotebooks-prod.us-gov-west-1.amazonaws.com +emrstudio-prod.us-gov-west-1.amazonaws.com +emrappui-prod.us-west-1.amazonaws.com +emrnotebooks-prod.us-west-1.amazonaws.com +emrstudio-prod.us-west-1.amazonaws.com +emrappui-prod.us-west-2.amazonaws.com +emrnotebooks-prod.us-west-2.amazonaws.com +emrstudio-prod.us-west-2.amazonaws.com + +// Amazon Managed Workflows for Apache Airflow +// Submitted by AWS Security +// Reference: f5ea5d0a-ec6a-4f23-ac1c-553fbff13f5c +*.cn-north-1.airflow.amazonaws.com.cn +*.cn-northwest-1.airflow.amazonaws.com.cn +*.af-south-1.airflow.amazonaws.com +*.ap-east-1.airflow.amazonaws.com +*.ap-northeast-1.airflow.amazonaws.com +*.ap-northeast-2.airflow.amazonaws.com +*.ap-northeast-3.airflow.amazonaws.com +*.ap-south-1.airflow.amazonaws.com +*.ap-south-2.airflow.amazonaws.com +*.ap-southeast-1.airflow.amazonaws.com +*.ap-southeast-2.airflow.amazonaws.com +*.ap-southeast-3.airflow.amazonaws.com +*.ap-southeast-4.airflow.amazonaws.com +*.ca-central-1.airflow.amazonaws.com +*.ca-west-1.airflow.amazonaws.com +*.eu-central-1.airflow.amazonaws.com +*.eu-central-2.airflow.amazonaws.com +*.eu-north-1.airflow.amazonaws.com +*.eu-south-1.airflow.amazonaws.com +*.eu-south-2.airflow.amazonaws.com +*.eu-west-1.airflow.amazonaws.com +*.eu-west-2.airflow.amazonaws.com +*.eu-west-3.airflow.amazonaws.com +*.il-central-1.airflow.amazonaws.com +*.me-central-1.airflow.amazonaws.com +*.me-south-1.airflow.amazonaws.com +*.sa-east-1.airflow.amazonaws.com +*.us-east-1.airflow.amazonaws.com +*.us-east-2.airflow.amazonaws.com +*.us-west-1.airflow.amazonaws.com +*.us-west-2.airflow.amazonaws.com + +// Amazon S3 +// Submitted by AWS Security +// Reference: ada5c9df-55e1-4195-a1ce-732d6c81e357 +s3.dualstack.cn-north-1.amazonaws.com.cn +s3-accesspoint.dualstack.cn-north-1.amazonaws.com.cn +s3-website.dualstack.cn-north-1.amazonaws.com.cn +s3.cn-north-1.amazonaws.com.cn +s3-accesspoint.cn-north-1.amazonaws.com.cn +s3-deprecated.cn-north-1.amazonaws.com.cn +s3-object-lambda.cn-north-1.amazonaws.com.cn +s3-website.cn-north-1.amazonaws.com.cn +s3.dualstack.cn-northwest-1.amazonaws.com.cn +s3-accesspoint.dualstack.cn-northwest-1.amazonaws.com.cn +s3.cn-northwest-1.amazonaws.com.cn +s3-accesspoint.cn-northwest-1.amazonaws.com.cn +s3-object-lambda.cn-northwest-1.amazonaws.com.cn +s3-website.cn-northwest-1.amazonaws.com.cn +s3.dualstack.af-south-1.amazonaws.com +s3-accesspoint.dualstack.af-south-1.amazonaws.com +s3-website.dualstack.af-south-1.amazonaws.com +s3.af-south-1.amazonaws.com +s3-accesspoint.af-south-1.amazonaws.com +s3-object-lambda.af-south-1.amazonaws.com +s3-website.af-south-1.amazonaws.com +s3.dualstack.ap-east-1.amazonaws.com +s3-accesspoint.dualstack.ap-east-1.amazonaws.com +s3.ap-east-1.amazonaws.com +s3-accesspoint.ap-east-1.amazonaws.com +s3-object-lambda.ap-east-1.amazonaws.com +s3-website.ap-east-1.amazonaws.com +s3.dualstack.ap-northeast-1.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-1.amazonaws.com +s3-website.dualstack.ap-northeast-1.amazonaws.com +s3.ap-northeast-1.amazonaws.com +s3-accesspoint.ap-northeast-1.amazonaws.com +s3-object-lambda.ap-northeast-1.amazonaws.com +s3-website.ap-northeast-1.amazonaws.com +s3.dualstack.ap-northeast-2.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-2.amazonaws.com +s3-website.dualstack.ap-northeast-2.amazonaws.com +s3.ap-northeast-2.amazonaws.com +s3-accesspoint.ap-northeast-2.amazonaws.com +s3-object-lambda.ap-northeast-2.amazonaws.com +s3-website.ap-northeast-2.amazonaws.com +s3.dualstack.ap-northeast-3.amazonaws.com +s3-accesspoint.dualstack.ap-northeast-3.amazonaws.com +s3-website.dualstack.ap-northeast-3.amazonaws.com +s3.ap-northeast-3.amazonaws.com +s3-accesspoint.ap-northeast-3.amazonaws.com +s3-object-lambda.ap-northeast-3.amazonaws.com +s3-website.ap-northeast-3.amazonaws.com +s3.dualstack.ap-south-1.amazonaws.com +s3-accesspoint.dualstack.ap-south-1.amazonaws.com +s3-website.dualstack.ap-south-1.amazonaws.com +s3.ap-south-1.amazonaws.com +s3-accesspoint.ap-south-1.amazonaws.com +s3-object-lambda.ap-south-1.amazonaws.com +s3-website.ap-south-1.amazonaws.com +s3.dualstack.ap-south-2.amazonaws.com +s3-accesspoint.dualstack.ap-south-2.amazonaws.com +s3-website.dualstack.ap-south-2.amazonaws.com +s3.ap-south-2.amazonaws.com +s3-accesspoint.ap-south-2.amazonaws.com +s3-object-lambda.ap-south-2.amazonaws.com +s3-website.ap-south-2.amazonaws.com +s3.dualstack.ap-southeast-1.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-1.amazonaws.com +s3-website.dualstack.ap-southeast-1.amazonaws.com +s3.ap-southeast-1.amazonaws.com +s3-accesspoint.ap-southeast-1.amazonaws.com +s3-object-lambda.ap-southeast-1.amazonaws.com +s3-website.ap-southeast-1.amazonaws.com +s3.dualstack.ap-southeast-2.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-2.amazonaws.com +s3-website.dualstack.ap-southeast-2.amazonaws.com +s3.ap-southeast-2.amazonaws.com +s3-accesspoint.ap-southeast-2.amazonaws.com +s3-object-lambda.ap-southeast-2.amazonaws.com +s3-website.ap-southeast-2.amazonaws.com +s3.dualstack.ap-southeast-3.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-3.amazonaws.com +s3-website.dualstack.ap-southeast-3.amazonaws.com +s3.ap-southeast-3.amazonaws.com +s3-accesspoint.ap-southeast-3.amazonaws.com +s3-object-lambda.ap-southeast-3.amazonaws.com +s3-website.ap-southeast-3.amazonaws.com +s3.dualstack.ap-southeast-4.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-4.amazonaws.com +s3-website.dualstack.ap-southeast-4.amazonaws.com +s3.ap-southeast-4.amazonaws.com +s3-accesspoint.ap-southeast-4.amazonaws.com +s3-object-lambda.ap-southeast-4.amazonaws.com +s3-website.ap-southeast-4.amazonaws.com +s3.dualstack.ap-southeast-5.amazonaws.com +s3-accesspoint.dualstack.ap-southeast-5.amazonaws.com +s3-website.dualstack.ap-southeast-5.amazonaws.com +s3.ap-southeast-5.amazonaws.com +s3-accesspoint.ap-southeast-5.amazonaws.com +s3-deprecated.ap-southeast-5.amazonaws.com +s3-object-lambda.ap-southeast-5.amazonaws.com +s3-website.ap-southeast-5.amazonaws.com +s3.dualstack.ca-central-1.amazonaws.com +s3-accesspoint.dualstack.ca-central-1.amazonaws.com +s3-accesspoint-fips.dualstack.ca-central-1.amazonaws.com +s3-fips.dualstack.ca-central-1.amazonaws.com +s3-website.dualstack.ca-central-1.amazonaws.com +s3.ca-central-1.amazonaws.com +s3-accesspoint.ca-central-1.amazonaws.com +s3-accesspoint-fips.ca-central-1.amazonaws.com +s3-fips.ca-central-1.amazonaws.com +s3-object-lambda.ca-central-1.amazonaws.com +s3-website.ca-central-1.amazonaws.com +s3.dualstack.ca-west-1.amazonaws.com +s3-accesspoint.dualstack.ca-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.ca-west-1.amazonaws.com +s3-fips.dualstack.ca-west-1.amazonaws.com +s3-website.dualstack.ca-west-1.amazonaws.com +s3.ca-west-1.amazonaws.com +s3-accesspoint.ca-west-1.amazonaws.com +s3-accesspoint-fips.ca-west-1.amazonaws.com +s3-fips.ca-west-1.amazonaws.com +s3-object-lambda.ca-west-1.amazonaws.com +s3-website.ca-west-1.amazonaws.com +s3.dualstack.eu-central-1.amazonaws.com +s3-accesspoint.dualstack.eu-central-1.amazonaws.com +s3-website.dualstack.eu-central-1.amazonaws.com +s3.eu-central-1.amazonaws.com +s3-accesspoint.eu-central-1.amazonaws.com +s3-object-lambda.eu-central-1.amazonaws.com +s3-website.eu-central-1.amazonaws.com +s3.dualstack.eu-central-2.amazonaws.com +s3-accesspoint.dualstack.eu-central-2.amazonaws.com +s3-website.dualstack.eu-central-2.amazonaws.com +s3.eu-central-2.amazonaws.com +s3-accesspoint.eu-central-2.amazonaws.com +s3-object-lambda.eu-central-2.amazonaws.com +s3-website.eu-central-2.amazonaws.com +s3.dualstack.eu-north-1.amazonaws.com +s3-accesspoint.dualstack.eu-north-1.amazonaws.com +s3.eu-north-1.amazonaws.com +s3-accesspoint.eu-north-1.amazonaws.com +s3-object-lambda.eu-north-1.amazonaws.com +s3-website.eu-north-1.amazonaws.com +s3.dualstack.eu-south-1.amazonaws.com +s3-accesspoint.dualstack.eu-south-1.amazonaws.com +s3-website.dualstack.eu-south-1.amazonaws.com +s3.eu-south-1.amazonaws.com +s3-accesspoint.eu-south-1.amazonaws.com +s3-object-lambda.eu-south-1.amazonaws.com +s3-website.eu-south-1.amazonaws.com +s3.dualstack.eu-south-2.amazonaws.com +s3-accesspoint.dualstack.eu-south-2.amazonaws.com +s3-website.dualstack.eu-south-2.amazonaws.com +s3.eu-south-2.amazonaws.com +s3-accesspoint.eu-south-2.amazonaws.com +s3-object-lambda.eu-south-2.amazonaws.com +s3-website.eu-south-2.amazonaws.com +s3.dualstack.eu-west-1.amazonaws.com +s3-accesspoint.dualstack.eu-west-1.amazonaws.com +s3-website.dualstack.eu-west-1.amazonaws.com +s3.eu-west-1.amazonaws.com +s3-accesspoint.eu-west-1.amazonaws.com +s3-deprecated.eu-west-1.amazonaws.com +s3-object-lambda.eu-west-1.amazonaws.com +s3-website.eu-west-1.amazonaws.com +s3.dualstack.eu-west-2.amazonaws.com +s3-accesspoint.dualstack.eu-west-2.amazonaws.com +s3.eu-west-2.amazonaws.com +s3-accesspoint.eu-west-2.amazonaws.com +s3-object-lambda.eu-west-2.amazonaws.com +s3-website.eu-west-2.amazonaws.com +s3.dualstack.eu-west-3.amazonaws.com +s3-accesspoint.dualstack.eu-west-3.amazonaws.com +s3-website.dualstack.eu-west-3.amazonaws.com +s3.eu-west-3.amazonaws.com +s3-accesspoint.eu-west-3.amazonaws.com +s3-object-lambda.eu-west-3.amazonaws.com +s3-website.eu-west-3.amazonaws.com +s3.dualstack.il-central-1.amazonaws.com +s3-accesspoint.dualstack.il-central-1.amazonaws.com +s3-website.dualstack.il-central-1.amazonaws.com +s3.il-central-1.amazonaws.com +s3-accesspoint.il-central-1.amazonaws.com +s3-object-lambda.il-central-1.amazonaws.com +s3-website.il-central-1.amazonaws.com +s3.dualstack.me-central-1.amazonaws.com +s3-accesspoint.dualstack.me-central-1.amazonaws.com +s3-website.dualstack.me-central-1.amazonaws.com +s3.me-central-1.amazonaws.com +s3-accesspoint.me-central-1.amazonaws.com +s3-object-lambda.me-central-1.amazonaws.com +s3-website.me-central-1.amazonaws.com +s3.dualstack.me-south-1.amazonaws.com +s3-accesspoint.dualstack.me-south-1.amazonaws.com +s3.me-south-1.amazonaws.com +s3-accesspoint.me-south-1.amazonaws.com +s3-object-lambda.me-south-1.amazonaws.com +s3-website.me-south-1.amazonaws.com +s3.amazonaws.com +s3-1.amazonaws.com +s3-ap-east-1.amazonaws.com +s3-ap-northeast-1.amazonaws.com +s3-ap-northeast-2.amazonaws.com +s3-ap-northeast-3.amazonaws.com +s3-ap-south-1.amazonaws.com +s3-ap-southeast-1.amazonaws.com +s3-ap-southeast-2.amazonaws.com +s3-ca-central-1.amazonaws.com +s3-eu-central-1.amazonaws.com +s3-eu-north-1.amazonaws.com +s3-eu-west-1.amazonaws.com +s3-eu-west-2.amazonaws.com +s3-eu-west-3.amazonaws.com +s3-external-1.amazonaws.com +s3-fips-us-gov-east-1.amazonaws.com +s3-fips-us-gov-west-1.amazonaws.com +mrap.accesspoint.s3-global.amazonaws.com +s3-me-south-1.amazonaws.com +s3-sa-east-1.amazonaws.com +s3-us-east-2.amazonaws.com +s3-us-gov-east-1.amazonaws.com +s3-us-gov-west-1.amazonaws.com +s3-us-west-1.amazonaws.com +s3-us-west-2.amazonaws.com +s3-website-ap-northeast-1.amazonaws.com +s3-website-ap-southeast-1.amazonaws.com +s3-website-ap-southeast-2.amazonaws.com +s3-website-eu-west-1.amazonaws.com +s3-website-sa-east-1.amazonaws.com +s3-website-us-east-1.amazonaws.com +s3-website-us-gov-west-1.amazonaws.com +s3-website-us-west-1.amazonaws.com +s3-website-us-west-2.amazonaws.com +s3.dualstack.sa-east-1.amazonaws.com +s3-accesspoint.dualstack.sa-east-1.amazonaws.com +s3-website.dualstack.sa-east-1.amazonaws.com +s3.sa-east-1.amazonaws.com +s3-accesspoint.sa-east-1.amazonaws.com +s3-object-lambda.sa-east-1.amazonaws.com +s3-website.sa-east-1.amazonaws.com +s3.dualstack.us-east-1.amazonaws.com +s3-accesspoint.dualstack.us-east-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-east-1.amazonaws.com +s3-fips.dualstack.us-east-1.amazonaws.com +s3-website.dualstack.us-east-1.amazonaws.com +s3.us-east-1.amazonaws.com +s3-accesspoint.us-east-1.amazonaws.com +s3-accesspoint-fips.us-east-1.amazonaws.com +s3-deprecated.us-east-1.amazonaws.com +s3-fips.us-east-1.amazonaws.com +s3-object-lambda.us-east-1.amazonaws.com +s3-website.us-east-1.amazonaws.com +s3.dualstack.us-east-2.amazonaws.com +s3-accesspoint.dualstack.us-east-2.amazonaws.com +s3-accesspoint-fips.dualstack.us-east-2.amazonaws.com +s3-fips.dualstack.us-east-2.amazonaws.com +s3-website.dualstack.us-east-2.amazonaws.com +s3.us-east-2.amazonaws.com +s3-accesspoint.us-east-2.amazonaws.com +s3-accesspoint-fips.us-east-2.amazonaws.com +s3-deprecated.us-east-2.amazonaws.com +s3-fips.us-east-2.amazonaws.com +s3-object-lambda.us-east-2.amazonaws.com +s3-website.us-east-2.amazonaws.com +s3.dualstack.us-gov-east-1.amazonaws.com +s3-accesspoint.dualstack.us-gov-east-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com +s3-fips.dualstack.us-gov-east-1.amazonaws.com +s3.us-gov-east-1.amazonaws.com +s3-accesspoint.us-gov-east-1.amazonaws.com +s3-accesspoint-fips.us-gov-east-1.amazonaws.com +s3-fips.us-gov-east-1.amazonaws.com +s3-object-lambda.us-gov-east-1.amazonaws.com +s3-website.us-gov-east-1.amazonaws.com +s3.dualstack.us-gov-west-1.amazonaws.com +s3-accesspoint.dualstack.us-gov-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-gov-west-1.amazonaws.com +s3-fips.dualstack.us-gov-west-1.amazonaws.com +s3.us-gov-west-1.amazonaws.com +s3-accesspoint.us-gov-west-1.amazonaws.com +s3-accesspoint-fips.us-gov-west-1.amazonaws.com +s3-fips.us-gov-west-1.amazonaws.com +s3-object-lambda.us-gov-west-1.amazonaws.com +s3-website.us-gov-west-1.amazonaws.com +s3.dualstack.us-west-1.amazonaws.com +s3-accesspoint.dualstack.us-west-1.amazonaws.com +s3-accesspoint-fips.dualstack.us-west-1.amazonaws.com +s3-fips.dualstack.us-west-1.amazonaws.com +s3-website.dualstack.us-west-1.amazonaws.com +s3.us-west-1.amazonaws.com +s3-accesspoint.us-west-1.amazonaws.com +s3-accesspoint-fips.us-west-1.amazonaws.com +s3-fips.us-west-1.amazonaws.com +s3-object-lambda.us-west-1.amazonaws.com +s3-website.us-west-1.amazonaws.com +s3.dualstack.us-west-2.amazonaws.com +s3-accesspoint.dualstack.us-west-2.amazonaws.com +s3-accesspoint-fips.dualstack.us-west-2.amazonaws.com +s3-fips.dualstack.us-west-2.amazonaws.com +s3-website.dualstack.us-west-2.amazonaws.com +s3.us-west-2.amazonaws.com +s3-accesspoint.us-west-2.amazonaws.com +s3-accesspoint-fips.us-west-2.amazonaws.com +s3-deprecated.us-west-2.amazonaws.com +s3-fips.us-west-2.amazonaws.com +s3-object-lambda.us-west-2.amazonaws.com +s3-website.us-west-2.amazonaws.com + +// Amazon SageMaker Ground Truth +// Submitted by AWS Security +// Reference: 98dbfde4-7802-48c3-8751-b60f204e0d9c +labeling.ap-northeast-1.sagemaker.aws +labeling.ap-northeast-2.sagemaker.aws +labeling.ap-south-1.sagemaker.aws +labeling.ap-southeast-1.sagemaker.aws +labeling.ap-southeast-2.sagemaker.aws +labeling.ca-central-1.sagemaker.aws +labeling.eu-central-1.sagemaker.aws +labeling.eu-west-1.sagemaker.aws +labeling.eu-west-2.sagemaker.aws +labeling.us-east-1.sagemaker.aws +labeling.us-east-2.sagemaker.aws +labeling.us-west-2.sagemaker.aws + +// Amazon SageMaker Notebook Instances +// Submitted by AWS Security +// Reference: b5ea56df-669e-43cc-9537-14aa172f5dfc +notebook.af-south-1.sagemaker.aws +notebook.ap-east-1.sagemaker.aws +notebook.ap-northeast-1.sagemaker.aws +notebook.ap-northeast-2.sagemaker.aws +notebook.ap-northeast-3.sagemaker.aws +notebook.ap-south-1.sagemaker.aws +notebook.ap-south-2.sagemaker.aws +notebook.ap-southeast-1.sagemaker.aws +notebook.ap-southeast-2.sagemaker.aws +notebook.ap-southeast-3.sagemaker.aws +notebook.ap-southeast-4.sagemaker.aws +notebook.ca-central-1.sagemaker.aws +notebook-fips.ca-central-1.sagemaker.aws +notebook.ca-west-1.sagemaker.aws +notebook-fips.ca-west-1.sagemaker.aws +notebook.eu-central-1.sagemaker.aws +notebook.eu-central-2.sagemaker.aws +notebook.eu-north-1.sagemaker.aws +notebook.eu-south-1.sagemaker.aws +notebook.eu-south-2.sagemaker.aws +notebook.eu-west-1.sagemaker.aws +notebook.eu-west-2.sagemaker.aws +notebook.eu-west-3.sagemaker.aws +notebook.il-central-1.sagemaker.aws +notebook.me-central-1.sagemaker.aws +notebook.me-south-1.sagemaker.aws +notebook.sa-east-1.sagemaker.aws +notebook.us-east-1.sagemaker.aws +notebook-fips.us-east-1.sagemaker.aws +notebook.us-east-2.sagemaker.aws +notebook-fips.us-east-2.sagemaker.aws +notebook.us-gov-east-1.sagemaker.aws +notebook-fips.us-gov-east-1.sagemaker.aws +notebook.us-gov-west-1.sagemaker.aws +notebook-fips.us-gov-west-1.sagemaker.aws +notebook.us-west-1.sagemaker.aws +notebook-fips.us-west-1.sagemaker.aws +notebook.us-west-2.sagemaker.aws +notebook-fips.us-west-2.sagemaker.aws +notebook.cn-north-1.sagemaker.com.cn +notebook.cn-northwest-1.sagemaker.com.cn + +// Amazon SageMaker Studio +// Submitted by AWS Security +// Reference: 69c723d9-6e1a-4bff-a203-48eecd203183 +studio.af-south-1.sagemaker.aws +studio.ap-east-1.sagemaker.aws +studio.ap-northeast-1.sagemaker.aws +studio.ap-northeast-2.sagemaker.aws +studio.ap-northeast-3.sagemaker.aws +studio.ap-south-1.sagemaker.aws +studio.ap-southeast-1.sagemaker.aws +studio.ap-southeast-2.sagemaker.aws +studio.ap-southeast-3.sagemaker.aws +studio.ca-central-1.sagemaker.aws +studio.eu-central-1.sagemaker.aws +studio.eu-north-1.sagemaker.aws +studio.eu-south-1.sagemaker.aws +studio.eu-south-2.sagemaker.aws +studio.eu-west-1.sagemaker.aws +studio.eu-west-2.sagemaker.aws +studio.eu-west-3.sagemaker.aws +studio.il-central-1.sagemaker.aws +studio.me-central-1.sagemaker.aws +studio.me-south-1.sagemaker.aws +studio.sa-east-1.sagemaker.aws +studio.us-east-1.sagemaker.aws +studio.us-east-2.sagemaker.aws +studio.us-gov-east-1.sagemaker.aws +studio-fips.us-gov-east-1.sagemaker.aws +studio.us-gov-west-1.sagemaker.aws +studio-fips.us-gov-west-1.sagemaker.aws +studio.us-west-1.sagemaker.aws +studio.us-west-2.sagemaker.aws +studio.cn-north-1.sagemaker.com.cn +studio.cn-northwest-1.sagemaker.com.cn + +// Amazon SageMaker with MLflow +// Submited by: AWS Security +// Reference: c19f92b3-a82a-452d-8189-831b572eea7e +*.experiments.sagemaker.aws + +// Analytics on AWS +// Submitted by AWS Security +// Reference: 955f9f40-a495-4e73-ae85-67b77ac9cadd +analytics-gateway.ap-northeast-1.amazonaws.com +analytics-gateway.ap-northeast-2.amazonaws.com +analytics-gateway.ap-south-1.amazonaws.com +analytics-gateway.ap-southeast-1.amazonaws.com +analytics-gateway.ap-southeast-2.amazonaws.com +analytics-gateway.eu-central-1.amazonaws.com +analytics-gateway.eu-west-1.amazonaws.com +analytics-gateway.us-east-1.amazonaws.com +analytics-gateway.us-east-2.amazonaws.com +analytics-gateway.us-west-2.amazonaws.com + +// AWS Amplify +// Submitted by AWS Security +// Reference: c35bed18-6f4f-424f-9298-5756f2f7d72b +amplifyapp.com + +// AWS App Runner +// Submitted by AWS Security +// Reference: 6828c008-ba5d-442f-ade5-48da4e7c2316 +*.awsapprunner.com + +// AWS Cloud9 +// Submitted by: AWS Security +// Reference: 30717f72-4007-4f0f-8ed4-864c6f2efec9 +webview-assets.aws-cloud9.af-south-1.amazonaws.com +vfs.cloud9.af-south-1.amazonaws.com +webview-assets.cloud9.af-south-1.amazonaws.com +webview-assets.aws-cloud9.ap-east-1.amazonaws.com +vfs.cloud9.ap-east-1.amazonaws.com +webview-assets.cloud9.ap-east-1.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com +vfs.cloud9.ap-northeast-1.amazonaws.com +webview-assets.cloud9.ap-northeast-1.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com +vfs.cloud9.ap-northeast-2.amazonaws.com +webview-assets.cloud9.ap-northeast-2.amazonaws.com +webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com +vfs.cloud9.ap-northeast-3.amazonaws.com +webview-assets.cloud9.ap-northeast-3.amazonaws.com +webview-assets.aws-cloud9.ap-south-1.amazonaws.com +vfs.cloud9.ap-south-1.amazonaws.com +webview-assets.cloud9.ap-south-1.amazonaws.com +webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com +vfs.cloud9.ap-southeast-1.amazonaws.com +webview-assets.cloud9.ap-southeast-1.amazonaws.com +webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com +vfs.cloud9.ap-southeast-2.amazonaws.com +webview-assets.cloud9.ap-southeast-2.amazonaws.com +webview-assets.aws-cloud9.ca-central-1.amazonaws.com +vfs.cloud9.ca-central-1.amazonaws.com +webview-assets.cloud9.ca-central-1.amazonaws.com +webview-assets.aws-cloud9.eu-central-1.amazonaws.com +vfs.cloud9.eu-central-1.amazonaws.com +webview-assets.cloud9.eu-central-1.amazonaws.com +webview-assets.aws-cloud9.eu-north-1.amazonaws.com +vfs.cloud9.eu-north-1.amazonaws.com +webview-assets.cloud9.eu-north-1.amazonaws.com +webview-assets.aws-cloud9.eu-south-1.amazonaws.com +vfs.cloud9.eu-south-1.amazonaws.com +webview-assets.cloud9.eu-south-1.amazonaws.com +webview-assets.aws-cloud9.eu-west-1.amazonaws.com +vfs.cloud9.eu-west-1.amazonaws.com +webview-assets.cloud9.eu-west-1.amazonaws.com +webview-assets.aws-cloud9.eu-west-2.amazonaws.com +vfs.cloud9.eu-west-2.amazonaws.com +webview-assets.cloud9.eu-west-2.amazonaws.com +webview-assets.aws-cloud9.eu-west-3.amazonaws.com +vfs.cloud9.eu-west-3.amazonaws.com +webview-assets.cloud9.eu-west-3.amazonaws.com +webview-assets.aws-cloud9.il-central-1.amazonaws.com +vfs.cloud9.il-central-1.amazonaws.com +webview-assets.aws-cloud9.me-south-1.amazonaws.com +vfs.cloud9.me-south-1.amazonaws.com +webview-assets.cloud9.me-south-1.amazonaws.com +webview-assets.aws-cloud9.sa-east-1.amazonaws.com +vfs.cloud9.sa-east-1.amazonaws.com +webview-assets.cloud9.sa-east-1.amazonaws.com +webview-assets.aws-cloud9.us-east-1.amazonaws.com +vfs.cloud9.us-east-1.amazonaws.com +webview-assets.cloud9.us-east-1.amazonaws.com +webview-assets.aws-cloud9.us-east-2.amazonaws.com +vfs.cloud9.us-east-2.amazonaws.com +webview-assets.cloud9.us-east-2.amazonaws.com +webview-assets.aws-cloud9.us-west-1.amazonaws.com +vfs.cloud9.us-west-1.amazonaws.com +webview-assets.cloud9.us-west-1.amazonaws.com +webview-assets.aws-cloud9.us-west-2.amazonaws.com +vfs.cloud9.us-west-2.amazonaws.com +webview-assets.cloud9.us-west-2.amazonaws.com + +// AWS Directory Service +// Submitted by AWS Security +// Reference: a13203e8-42dc-4045-a0d2-2ee67bed1068 +awsapps.com + +// AWS Elastic Beanstalk +// Submitted by AWS Security +// Reference: bb5a965c-dec3-4967-aa22-e306ad064797 +cn-north-1.eb.amazonaws.com.cn +cn-northwest-1.eb.amazonaws.com.cn +elasticbeanstalk.com +af-south-1.elasticbeanstalk.com +ap-east-1.elasticbeanstalk.com +ap-northeast-1.elasticbeanstalk.com +ap-northeast-2.elasticbeanstalk.com +ap-northeast-3.elasticbeanstalk.com +ap-south-1.elasticbeanstalk.com +ap-southeast-1.elasticbeanstalk.com +ap-southeast-2.elasticbeanstalk.com +ap-southeast-3.elasticbeanstalk.com +ca-central-1.elasticbeanstalk.com +eu-central-1.elasticbeanstalk.com +eu-north-1.elasticbeanstalk.com +eu-south-1.elasticbeanstalk.com +eu-west-1.elasticbeanstalk.com +eu-west-2.elasticbeanstalk.com +eu-west-3.elasticbeanstalk.com +il-central-1.elasticbeanstalk.com +me-south-1.elasticbeanstalk.com +sa-east-1.elasticbeanstalk.com +us-east-1.elasticbeanstalk.com +us-east-2.elasticbeanstalk.com +us-gov-east-1.elasticbeanstalk.com +us-gov-west-1.elasticbeanstalk.com +us-west-1.elasticbeanstalk.com +us-west-2.elasticbeanstalk.com + +// (AWS) Elastic Load Balancing +// Submitted by Luke Wells +// Reference: 12a3d528-1bac-4433-a359-a395867ffed2 +*.elb.amazonaws.com.cn +*.elb.amazonaws.com + +// AWS Global Accelerator +// Submitted by Daniel Massaguer +// Reference: d916759d-a08b-4241-b536-4db887383a6a +awsglobalaccelerator.com + +// AWS re:Post Private +// Submitted by AWS Security +// Reference: 83385945-225f-416e-9aa0-ad0632bfdcee +*.private.repost.aws + +// eero +// Submitted by Yue Kang +// Reference: 264afe70-f62c-4c02-8ab9-b5281ed24461 +eero.online +eero-stage.online + +// concludes Amazon + +// Apigee : https://apigee.com/ +// Submitted by Apigee Security Team +apigee.io + +// Apis Networks : https://apisnetworks.com +// Submitted by Matt Saladna +panel.dev + +// Apphud : https://apphud.com +// Submitted by Alexander Selivanov +siiites.com + +// Appspace : https://www.appspace.com +// Submitted by Appspace Security Team +appspacehosted.com +appspaceusercontent.com + +// Appudo UG (haftungsbeschränkt) : https://www.appudo.com +// Submitted by Alexander Hochbaum +appudo.net + +// Aptible : https://www.aptible.com/ +// Submitted by Thomas Orozco +on-aptible.com + +// Aquapal : https://aquapal.net/ +// Submitted by Aki Ueno +f5.si + +// ArvanCloud EdgeCompute +// Submitted by ArvanCloud CDN +arvanedge.ir + +// ASEINet : https://www.aseinet.com/ +// Submitted by Asei SEKIGUCHI +user.aseinet.ne.jp +gv.vc +d.gv.vc + +// Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/ +// Submitted by Hector Martin +user.party.eus + +// Association potager.org : https://potager.org/ +// Submitted by Lunar +pimienta.org +poivron.org +potager.org +sweetpepper.org + +// ASUSTOR Inc. : http://www.asustor.com +// Submitted by Vincent Tseng +myasustor.com + +// Atlassian : https://atlassian.com +// Submitted by Sam Smyth +cdn.prod.atlassian-dev.net + +// Authentick UG (haftungsbeschränkt) : https://authentick.net +// Submitted by Lukas Reschke +translated.page + +// AVM : https://avm.de +// Submitted by Andreas Weise +myfritz.link +myfritz.net + +// AVStack Pte. Ltd. : https://avstack.io +// Submitted by Jasper Hugo +onavstack.net + +// AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com +// Submitted by James Kennedy +*.awdev.ca +*.advisor.ws + +// AZ.pl sp. z.o.o : https://az.pl +// Submitted by Krzysztof Wolski +ecommerce-shop.pl + +// b-data GmbH : https://www.b-data.io +// Submitted by Olivier Benz +b-data.io + +// Balena : https://www.balena.io +// Submitted by Petros Angelatos +balena-devices.com + +// BASE, Inc. : https://binc.jp +// Submitted by Yuya NAGASAWA +base.ec +official.ec +buyshop.jp +fashionstore.jp +handcrafted.jp +kawaiishop.jp +supersale.jp +theshop.jp +shopselect.net +base.shop + +// BeagleBoard.org Foundation : https://beagleboard.org +// Submitted by Jason Kridner +beagleboard.io + +// Beget Ltd +// Submitted by Lev Nekrasov +*.beget.app + +// Besties : https://besties.house +// Submitted by Hazel Cora +pages.gay + +// BetaInABox +// Submitted by Adrian +betainabox.com + +// BinaryLane : http://www.binarylane.com +// Submitted by Nathan O'Sullivan +bnr.la + +// Bitbucket : http://bitbucket.org +// Submitted by Andy Ortlieb +bitbucket.io + +// Blackbaud, Inc. : https://www.blackbaud.com +// Submitted by Paul Crowder +blackbaudcdn.net + +// Blatech : http://www.blatech.net +// Submitted by Luke Bratch +of.je + +// Blue Bite, LLC : https://bluebite.com +// Submitted by Joshua Weiss +bluebite.io + +// Boomla : https://boomla.com +// Submitted by Tibor Halter +boomla.net + +// Boutir : https://www.boutir.com +// Submitted by Eric Ng Ka Ka +boutir.com + +// Boxfuse : https://boxfuse.com +// Submitted by Axel Fontaine +boxfuse.io + +// bplaced : https://www.bplaced.net/ +// Submitted by Miroslav Bozic +square7.ch +bplaced.com +bplaced.de +square7.de +bplaced.net +square7.net + +// Brave : https://brave.com +// Submitted by Andrea Brancaleoni +*.s.brave.io + +// Brendly : https://brendly.rs +// Submitted by Dusan Radovanovic +shop.brendly.hr +shop.brendly.rs + +// BrowserSafetyMark +// Submitted by Dave Tharp +browsersafetymark.io + +// BRS Media : https://brsmedia.com/ +// Submitted by Gavin Brown +radio.am +radio.fm + +// Bytemark Hosting : https://www.bytemark.co.uk +// Submitted by Paul Cammish +uk0.bigv.io +dh.bytemark.co.uk +vm.bytemark.co.uk + +// Caf.js Labs LLC : https://www.cafjs.com +// Submitted by Antonio Lain +cafjs.com + +// Canva Pty Ltd : https://canva.com/ +// Submitted by Joel Aquilina +canva-apps.cn +*.my.canvasite.cn +canva-apps.com +*.my.canva.site + +// Carrd : https://carrd.co +// Submitted by AJ +drr.ac +uwu.ai +carrd.co +crd.co +ju.mp + +// CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk +// Submitted by Jamie Tanna +api.gov.uk + +// CDN77.com : http://www.cdn77.com +// Submitted by Jan Krpes +cdn77-storage.com +rsc.contentproxy9.cz +r.cdn77.net +cdn77-ssl.net +c.cdn77.org +rsc.cdn77.org +ssl.origin.cdn77-secure.org + +// CentralNic : http://www.centralnic.com/names/domains +// Submitted by registry +za.bz +br.com +cn.com +de.com +eu.com +jpn.com +mex.com +ru.com +sa.com +uk.com +us.com +za.com +com.de +gb.net +hu.net +jp.net +se.net +uk.net +ae.org +com.se + +// Cityhost LLC : https://cityhost.ua +// Submitted by Maksym Rivtin +cx.ua + +// Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/ +// Submitted by Rishabh Nambiar & Michael Brown +discourse.group +discourse.team + +// Clerk : https://www.clerk.dev +// Submitted by Colin Sidoti +clerk.app +clerkstage.app +*.lcl.dev +*.lclstage.dev +*.stg.dev +*.stgstage.dev + +// Clever Cloud : https://www.clever-cloud.com/ +// Submitted by Quentin Adam +cleverapps.cc +*.services.clever-cloud.com +cleverapps.io +cleverapps.tech + +// ClickRising : https://clickrising.com/ +// Submitted by Umut Gumeli +clickrising.net + +// Cloud DNS Ltd : http://www.cloudns.net +// Submitted by Aleksander Hristov & Boyan Peychev +cloudns.asia +cloudns.be +cloud-ip.biz +cloudns.biz +cloudns.cc +cloudns.ch +cloudns.cl +cloudns.club +dnsabr.com +cloudns.cx +cloudns.eu +cloudns.in +cloudns.info +dns-cloud.net +dns-dynamic.net +cloudns.nz +cloudns.org +ip-dynamic.org +cloudns.ph +cloudns.pro +cloudns.pw +cloudns.us + +// Cloud66 : https://www.cloud66.com/ +// Submitted by Khash Sajadi +c66.me +cloud66.ws +cloud66.zone + +// CloudAccess.net : https://www.cloudaccess.net/ +// Submitted by Pawel Panek +jdevcloud.com +wpdevcloud.com +cloudaccess.host +freesite.host +cloudaccess.net + +// Cloudera, Inc. : https://www.cloudera.com/ +// Submitted by Kedarnath Waikar +*.cloudera.site + +// Cloudflare, Inc. : https://www.cloudflare.com/ +// Submitted by Cloudflare Team +cf-ipfs.com +cloudflare-ipfs.com +trycloudflare.com +pages.dev +r2.dev +workers.dev +cloudflare.net +cdn.cloudflare.net +cdn.cloudflareanycast.net +cdn.cloudflarecn.net +cdn.cloudflareglobal.net + +// cloudscale.ch AG : https://www.cloudscale.ch/ +// Submitted by Gaudenz Steinlin +cust.cloudscale.ch +objects.lpg.cloudscale.ch +objects.rma.cloudscale.ch + +// Clovyr : https://clovyr.io +// Submitted by Patrick Nielsen +wnext.app + +// CNPY : https://cnpy.gdn +// Submitted by Angelo Gladding +cnpy.gdn + +// Co & Co : https://co-co.nl/ +// Submitted by Govert Versluis +*.otap.co + +// co.ca : http://registry.co.ca/ +co.ca + +// co.com Registry, LLC : https://registry.co.com +// Submitted by Gavin Brown +co.com + +// Codeberg e. V. : https://codeberg.org +// Submitted by Moritz Marquardt +codeberg.page + +// CodeSandbox B.V. : https://codesandbox.io +// Submitted by Ives van Hoorne +csb.app +preview.csb.app + +// CoDNS B.V. +co.nl +co.no + +// Combell.com : https://www.combell.com +// Submitted by Thomas Wouters +webhosting.be +hosting-cluster.nl + +// Contentful GmbH : https://www.contentful.com +// Submitted by Contentful Developer Experience Team +ctfcloud.net + +// Convex : https://convex.dev/ +// Submitted by James Cowling +convex.site + +// Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ +// Submitted by George Georgievsky +ac.ru +edu.ru +gov.ru +int.ru +mil.ru +test.ru + +// COSIMO GmbH : http://www.cosimo.de +// Submitted by Rene Marticke +dyn.cosidns.de +dnsupdater.de +dynamisches-dns.de +internet-dns.de +l-o-g-i-n.de +dynamic-dns.info +feste-ip.net +knx-server.net +static-access.net + +// Craft Docs Ltd : https://www.craft.do/ +// Submitted by Zsombor Fuszenecker +craft.me + +// Craynic, s.r.o. : http://www.craynic.com/ +// Submitted by Ales Krajnik +realm.cz + +// Crisp IM SAS : https://crisp.chat/ +// Submitted by Baptiste Jamin +on.crisp.email + +// Cryptonomic : https://cryptonomic.net/ +// Submitted by Andrew Cady +*.cryptonomic.net + +// Curv UG : https://curv-labs.de/ +// Submitted by Marvin Wiesner +curv.dev + +// cyber_Folks S.A. : https://cyberfolks.pl +// Submitted by Bartlomiej Kida +cfolks.pl + +// cyon GmbH : https://www.cyon.ch/ +// Submitted by Dominic Luechinger +cyon.link +cyon.site + +// Danger Science Group: https://dangerscience.com/ +// Submitted by Skylar MacDonald +platform0.app +fnwk.site +folionetwork.site + +// Dansk.net : http://www.dansk.net/ +// Submitted by Anani Voule +biz.dk +co.dk +firm.dk +reg.dk +store.dk + +// dappnode.io : https://dappnode.io/ +// Submitted by Abel Boldu / DAppNode Team +dyndns.dappnode.io + +// Dark, Inc. : https://darklang.com +// Submitted by Paul Biggar +builtwithdark.com +darklang.io + +// DataDetect, LLC. : https://datadetect.com +// Submitted by Andrew Banchich +demo.datadetect.com +instance.datadetect.com + +// Datawire, Inc : https://www.datawire.io +// Submitted by Richard Li +edgestack.me + +// Datto, Inc. : https://www.datto.com/ +// Submitted by Philipp Heckel +dattolocal.com +dattorelay.com +dattoweb.com +mydatto.com +dattolocal.net +mydatto.net + +// ddnss.de : https://www.ddnss.de/ +// Submitted by Robert Niedziela +ddnss.de +dyn.ddnss.de +dyndns.ddnss.de +dyn-ip24.de +dyndns1.de +home-webserver.de +dyn.home-webserver.de +myhome-server.de +ddnss.org + +// Debian : https://www.debian.org/ +// Submitted by Peter Palfrader / Debian Sysadmin Team +debian.net + +// Definima : http://www.definima.com/ +// Submitted by Maxence Bitterli +definima.io +definima.net + +// Deno Land Inc : https://deno.com/ +// Submitted by Luca Casonato +deno.dev +deno-staging.dev + +// deSEC : https://desec.io/ +// Submitted by Peter Thomassen +dedyn.io + +// Deta: https://www.deta.sh/ +// Submitted by Aavash Shrestha +deta.app +deta.dev + +// dhosting.pl Sp. z o.o.: https://dhosting.pl/ +// Submitted by Michal Kokoszkiewicz +dfirma.pl +dkonto.pl +you2.pl + +// DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ +// Submitted by Braxton Huggins +ondigitalocean.app + +// DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ +// Submitted by Robin H. Johnson +*.digitaloceanspaces.com + +// DigitalPlat : https://www.digitalplat.org/ +// Submitted by Edward Hsing +us.kg + +// Diher Solutions : https://diher.solutions +// Submitted by Didi Hermawan +rss.my.id +diher.solutions + +// Discord Inc : https://discord.com +// Submitted by Sahn Lam +discordsays.com +discordsez.com + +// DNS Africa Ltd https://dns.business +// Submitted by Calvin Browne +jozi.biz + +// DNShome : https://www.dnshome.de/ +// Submitted by Norbert Auler +dnshome.de + +// dnstrace.pro : https://dnstrace.pro/ +// Submitted by Chris Partridge +bci.dnstrace.pro + +// DotArai : https://www.dotarai.com/ +// Submitted by Atsadawat Netcharadsang +online.th +shop.th + +// DrayTek Corp. : https://www.draytek.com/ +// Submitted by Paul Fang +drayddns.com + +// DreamCommerce : https://shoper.pl/ +// Submitted by Konrad Kotarba +shoparena.pl + +// DreamHost : http://www.dreamhost.com/ +// Submitted by Andrew Farmer +dreamhosters.com + +// Dreamyoungs, Inc. : https://durumis.com +// Submitted by Infra Team +durumis.com + +// Drobo : http://www.drobo.com/ +// Submitted by Ricardo Padilha +mydrobo.com + +// Drud Holdings, LLC. : https://www.drud.com/ +// Submitted by Kevin Bridges +drud.io +drud.us + +// DuckDNS : http://www.duckdns.org/ +// Submitted by Richard Harper +duckdns.org + +// dy.fi : http://dy.fi/ +// Submitted by Heikki Hannikainen +dy.fi +tunk.org + +// DynDNS.com : http://www.dyndns.com/services/dns/dyndns/ +dyndns.biz +for-better.biz +for-more.biz +for-some.biz +for-the.biz +selfip.biz +webhop.biz +ftpaccess.cc +game-server.cc +myphotos.cc +scrapping.cc +blogdns.com +cechire.com +dnsalias.com +dnsdojo.com +doesntexist.com +dontexist.com +doomdns.com +dyn-o-saur.com +dynalias.com +dyndns-at-home.com +dyndns-at-work.com +dyndns-blog.com +dyndns-free.com +dyndns-home.com +dyndns-ip.com +dyndns-mail.com +dyndns-office.com +dyndns-pics.com +dyndns-remote.com +dyndns-server.com +dyndns-web.com +dyndns-wiki.com +dyndns-work.com +est-a-la-maison.com +est-a-la-masion.com +est-le-patron.com +est-mon-blogueur.com +from-ak.com +from-al.com +from-ar.com +from-ca.com +from-ct.com +from-dc.com +from-de.com +from-fl.com +from-ga.com +from-hi.com +from-ia.com +from-id.com +from-il.com +from-in.com +from-ks.com +from-ky.com +from-ma.com +from-md.com +from-mi.com +from-mn.com +from-mo.com +from-ms.com +from-mt.com +from-nc.com +from-nd.com +from-ne.com +from-nh.com +from-nj.com +from-nm.com +from-nv.com +from-oh.com +from-ok.com +from-or.com +from-pa.com +from-pr.com +from-ri.com +from-sc.com +from-sd.com +from-tn.com +from-tx.com +from-ut.com +from-va.com +from-vt.com +from-wa.com +from-wi.com +from-wv.com +from-wy.com +getmyip.com +gotdns.com +hobby-site.com +homelinux.com +homeunix.com +iamallama.com +is-a-anarchist.com +is-a-blogger.com +is-a-bookkeeper.com +is-a-bulls-fan.com +is-a-caterer.com +is-a-chef.com +is-a-conservative.com +is-a-cpa.com +is-a-cubicle-slave.com +is-a-democrat.com +is-a-designer.com +is-a-doctor.com +is-a-financialadvisor.com +is-a-geek.com +is-a-green.com +is-a-guru.com +is-a-hard-worker.com +is-a-hunter.com +is-a-landscaper.com +is-a-lawyer.com +is-a-liberal.com +is-a-libertarian.com +is-a-llama.com +is-a-musician.com +is-a-nascarfan.com +is-a-nurse.com +is-a-painter.com +is-a-personaltrainer.com +is-a-photographer.com +is-a-player.com +is-a-republican.com +is-a-rockstar.com +is-a-socialist.com +is-a-student.com +is-a-teacher.com +is-a-techie.com +is-a-therapist.com +is-an-accountant.com +is-an-actor.com +is-an-actress.com +is-an-anarchist.com +is-an-artist.com +is-an-engineer.com +is-an-entertainer.com +is-certified.com +is-gone.com +is-into-anime.com +is-into-cars.com +is-into-cartoons.com +is-into-games.com +is-leet.com +is-not-certified.com +is-slick.com +is-uberleet.com +is-with-theband.com +isa-geek.com +isa-hockeynut.com +issmarterthanyou.com +likes-pie.com +likescandy.com +neat-url.com +saves-the-whales.com +selfip.com +sells-for-less.com +sells-for-u.com +servebbs.com +simple-url.com +space-to-rent.com +teaches-yoga.com +writesthisblog.com +ath.cx +fuettertdasnetz.de +isteingeek.de +istmein.de +lebtimnetz.de +leitungsen.de +traeumtgerade.de +barrel-of-knowledge.info +barrell-of-knowledge.info +dyndns.info +for-our.info +groks-the.info +groks-this.info +here-for-more.info +knowsitall.info +selfip.info +webhop.info +forgot.her.name +forgot.his.name +at-band-camp.net +blogdns.net +broke-it.net +buyshouses.net +dnsalias.net +dnsdojo.net +does-it.net +dontexist.net +dynalias.net +dynathome.net +endofinternet.net +from-az.net +from-co.net +from-la.net +from-ny.net +gets-it.net +ham-radio-op.net +homeftp.net +homeip.net +homelinux.net +homeunix.net +in-the-band.net +is-a-chef.net +is-a-geek.net +isa-geek.net +kicks-ass.net +office-on-the.net +podzone.net +scrapper-site.net +selfip.net +sells-it.net +servebbs.net +serveftp.net +thruhere.net +webhop.net +merseine.nu +mine.nu +shacknet.nu +blogdns.org +blogsite.org +boldlygoingnowhere.org +dnsalias.org +dnsdojo.org +doesntexist.org +dontexist.org +doomdns.org +dvrdns.org +dynalias.org +dyndns.org +go.dyndns.org +home.dyndns.org +endofinternet.org +endoftheinternet.org +from-me.org +game-host.org +gotdns.org +hobby-site.org +homedns.org +homeftp.org +homelinux.org +homeunix.org +is-a-bruinsfan.org +is-a-candidate.org +is-a-celticsfan.org +is-a-chef.org +is-a-geek.org +is-a-knight.org +is-a-linux-user.org +is-a-patsfan.org +is-a-soxfan.org +is-found.org +is-lost.org +is-saved.org +is-very-bad.org +is-very-evil.org +is-very-good.org +is-very-nice.org +is-very-sweet.org +isa-geek.org +kicks-ass.org +misconfused.org +podzone.org +readmyblog.org +selfip.org +sellsyourhome.org +servebbs.org +serveftp.org +servegame.org +stuff-4-sale.org +webhop.org +better-than.tv +dyndns.tv +on-the-web.tv +worse-than.tv +is-by.us +land-4-sale.us +stuff-4-sale.us +dyndns.ws +mypets.ws + +// Dynu.com : https://www.dynu.com/ +// Submitted by Sue Ye +ddnsfree.com +ddnsgeek.com +giize.com +gleeze.com +kozow.com +loseyourip.com +ooguy.com +theworkpc.com +casacam.net +dynu.net +accesscam.org +camdvr.org +freeddns.org +mywire.org +webredirect.org +myddns.rocks + +// dynv6 : https://dynv6.com +// Submitted by Dominik Menke +dynv6.net + +// E4YOU spol. s.r.o. : https://e4you.cz/ +// Submitted by Vladimir Dudr +e4.cz + +// Easypanel : https://easypanel.io +// Submitted by Andrei Canta +easypanel.app +easypanel.host + +// EasyWP : https://www.easywp.com +// Submitted by +*.ewp.live + +// ECG Robotics, Inc : https://ecgrobotics.org +// Submitted by +onred.one +staging.onred.one + +// eDirect Corp. : https://hosting.url.com.tw/ +// Submitted by C.S. chang +twmail.cc +twmail.net +twmail.org +mymailer.com.tw +url.tw + +// Electromagnetic Field : https://www.emfcamp.org +// Submitted by +at.emf.camp + +// Elefunc, Inc. : https://elefunc.com +// Submitted by Cetin Sert +rt.ht + +// Elementor : Elementor Ltd. +// Submitted by Anton Barkan +elementor.cloud +elementor.cool + +// En root‽ : https://en-root.org +// Submitted by Emmanuel Raviart +en-root.fr + +// Enalean SAS: https://www.enalean.com +// Submitted by Enalean Security Team +mytuleap.com +tuleap-partners.com + +// Encoretivity AB: https://encore.dev +// Submitted by André Eriksson +encr.app +encoreapi.com + +// encoway GmbH : https://www.encoway.de +// Submitted by Marcel Daus +eu.encoway.cloud + +// EU.org https://eu.org/ +// Submitted by Pierre Beyssac +eu.org +al.eu.org +asso.eu.org +at.eu.org +au.eu.org +be.eu.org +bg.eu.org +ca.eu.org +cd.eu.org +ch.eu.org +cn.eu.org +cy.eu.org +cz.eu.org +de.eu.org +dk.eu.org +edu.eu.org +ee.eu.org +es.eu.org +fi.eu.org +fr.eu.org +gr.eu.org +hr.eu.org +hu.eu.org +ie.eu.org +il.eu.org +in.eu.org +int.eu.org +is.eu.org +it.eu.org +jp.eu.org +kr.eu.org +lt.eu.org +lu.eu.org +lv.eu.org +me.eu.org +mk.eu.org +mt.eu.org +my.eu.org +net.eu.org +ng.eu.org +nl.eu.org +no.eu.org +nz.eu.org +pl.eu.org +pt.eu.org +ro.eu.org +ru.eu.org +se.eu.org +si.eu.org +sk.eu.org +tr.eu.org +uk.eu.org +us.eu.org + +// Eurobyte : https://eurobyte.ru +// Submitted by Evgeniy Subbotin +eurodir.ru + +// Evennode : http://www.evennode.com/ +// Submitted by Michal Kralik +eu-1.evennode.com +eu-2.evennode.com +eu-3.evennode.com +eu-4.evennode.com +us-1.evennode.com +us-2.evennode.com +us-3.evennode.com +us-4.evennode.com + +// Evervault : https://evervault.com +// Submitted by Hannah Neary +relay.evervault.app +relay.evervault.dev + +// Expo : https://expo.dev/ +// Submitted by James Ide +expo.app +staging.expo.app + +// Fabrica Technologies, Inc. : https://www.fabrica.dev/ +// Submitted by Eric Jiang +onfabrica.com + +// FAITID : https://faitid.org/ +// Submitted by Maxim Alzoba +// https://www.flexireg.net/stat_info +ru.net +adygeya.ru +bashkiria.ru +bir.ru +cbg.ru +com.ru +dagestan.ru +grozny.ru +kalmykia.ru +kustanai.ru +marine.ru +mordovia.ru +msk.ru +mytis.ru +nalchik.ru +nov.ru +pyatigorsk.ru +spb.ru +vladikavkaz.ru +vladimir.ru +abkhazia.su +adygeya.su +aktyubinsk.su +arkhangelsk.su +armenia.su +ashgabad.su +azerbaijan.su +balashov.su +bashkiria.su +bryansk.su +bukhara.su +chimkent.su +dagestan.su +east-kazakhstan.su +exnet.su +georgia.su +grozny.su +ivanovo.su +jambyl.su +kalmykia.su +kaluga.su +karacol.su +karaganda.su +karelia.su +khakassia.su +krasnodar.su +kurgan.su +kustanai.su +lenug.su +mangyshlak.su +mordovia.su +msk.su +murmansk.su +nalchik.su +navoi.su +north-kazakhstan.su +nov.su +obninsk.su +penza.su +pokrovsk.su +sochi.su +spb.su +tashkent.su +termez.su +togliatti.su +troitsk.su +tselinograd.su +tula.su +tuva.su +vladikavkaz.su +vladimir.su +vologda.su + +// Fancy Bits, LLC : http://getchannels.com +// Submitted by Aman Gupta +channelsdvr.net +u.channelsdvr.net + +// Fastly Inc. : http://www.fastly.com/ +// Submitted by Fastly Security +edgecompute.app +fastly-edge.com +fastly-terrarium.com +freetls.fastly.net +map.fastly.net +a.prod.fastly.net +global.prod.fastly.net +a.ssl.fastly.net +b.ssl.fastly.net +global.ssl.fastly.net +fastlylb.net +map.fastlylb.net + +// Fastmail : https://www.fastmail.com/ +// Submitted by Marc Bradshaw +*.user.fm + +// FASTVPS EESTI OU : https://fastvps.ru/ +// Submitted by Likhachev Vasiliy +fastvps-server.com +fastvps.host +myfast.host +fastvps.site +myfast.space + +// FearWorks Media Ltd. : https://fearworksmedia.co.uk +// submitted by Keith Fairley +conn.uk +copro.uk +hosp.uk + +// Fedora : https://fedoraproject.org/ +// submitted by Patrick Uiterwijk +fedorainfracloud.org +fedorapeople.org +cloud.fedoraproject.org +app.os.fedoraproject.org +app.os.stg.fedoraproject.org + +// Fermax : https://fermax.com/ +// submitted by Koen Van Isterdael +mydobiss.com + +// FH Muenster : https://www.fh-muenster.de +// Submitted by Robin Naundorf +fh-muenster.io + +// Filegear Inc. : https://www.filegear.com +// Submitted by Jason Zhu +filegear.me + +// Firebase, Inc. +// Submitted by Chris Raynor +firebaseapp.com + +// FlashDrive : https://flashdrive.io +// Submitted by Eric Chan +fldrv.com + +// FlutterFlow : https://flutterflow.io +// Submitted by Anton Emelyanov +flutterflow.app + +// fly.io: https://fly.io +// Submitted by Kurt Mackey +fly.dev +shw.io +edgeapp.net + +// Forgerock : https://www.forgerock.com +// Submitted by Roderick Parr +forgeblocks.com +id.forgerock.io + +// Framer : https://www.framer.com +// Submitted by Koen Rouwhorst +framer.ai +framer.app +framercanvas.com +framer.media +framer.photos +framer.website +framer.wiki + +// Frederik Braun : https://frederik-braun.com +// Submitted by Frederik Braun +0e.vc + +// Freebox : http://www.freebox.fr +// Submitted by Romain Fliedel +freebox-os.com +freeboxos.com +fbx-os.fr +fbxos.fr +freebox-os.fr +freeboxos.fr + +// freedesktop.org : https://www.freedesktop.org +// Submitted by Daniel Stone +freedesktop.org + +// freemyip.com : https://freemyip.com +// Submitted by Cadence +freemyip.com + +// Frusky MEDIA&PR : https://www.frusky.de +// Submitted by Victor Pupynin +*.frusky.de + +// FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at +// Submitted by Daniel A. Maierhofer +wien.funkfeuer.at + +// Future Versatile Group. : https://www.fvg-on.net/ +// T.Kabu +daemon.asia +dix.asia +mydns.bz +0am.jp +0g0.jp +0j0.jp +0t0.jp +mydns.jp +pgw.jp +wjg.jp +keyword-on.net +live-on.net +server-on.net +mydns.tw +mydns.vc + +// Futureweb GmbH : https://www.futureweb.at +// Submitted by Andreas Schnederle-Wagner +*.futurecms.at +*.ex.futurecms.at +*.in.futurecms.at +futurehosting.at +futuremailing.at +*.ex.ortsinfo.at +*.kunden.ortsinfo.at +*.statics.cloud + +// GCom Internet : https://www.gcom.net.au +// Submitted by Leo Julius +aliases121.com + +// GDS : https://www.gov.uk/service-manual/technology/managing-domain-names +// Submitted by Stephen Ford +campaign.gov.uk +service.gov.uk +independent-commission.uk +independent-inquest.uk +independent-inquiry.uk +independent-panel.uk +independent-review.uk +public-inquiry.uk +royal-commission.uk + +// Gehirn Inc. : https://www.gehirn.co.jp/ +// Submitted by Kohei YOSHIDA +gehirn.ne.jp +usercontent.jp + +// Gentlent, Inc. : https://www.gentlent.com +// Submitted by Tom Klein +gentapps.com +gentlentapis.com +lab.ms +cdn-edges.net + +// Getlocalcert: https://www.getlocalcert.net +// Submitted by Robert Alexander +localcert.net +localhostcert.net +corpnet.work + +// GignoSystemJapan: http://gsj.bz +// Submitted by GignoSystemJapan +gsj.bz + +// GitHub, Inc. +// Submitted by Patrick Toomey +githubusercontent.com +githubpreview.dev +github.io + +// GitLab, Inc. +// Submitted by Alex Hanselka +gitlab.io + +// Gitplac.si - https://gitplac.si +// Submitted by Aljaž Starc +gitapp.si +gitpage.si + +// Glitch, Inc : https://glitch.com +// Submitted by Mads Hartmann +glitch.me + +// Global NOG Alliance : https://nogalliance.org/ +// Submitted by Sander Steffann +nog.community + +// Globe Hosting SRL : https://www.globehosting.com/ +// Submitted by Gavin Brown +co.ro +shop.ro + +// GMO Pepabo, Inc. : https://pepabo.com/ +// Submitted by Hosting Div +lolipop.io +angry.jp +babyblue.jp +babymilk.jp +backdrop.jp +bambina.jp +bitter.jp +blush.jp +boo.jp +boy.jp +boyfriend.jp +but.jp +candypop.jp +capoo.jp +catfood.jp +cheap.jp +chicappa.jp +chillout.jp +chips.jp +chowder.jp +chu.jp +ciao.jp +cocotte.jp +coolblog.jp +cranky.jp +cutegirl.jp +daa.jp +deca.jp +deci.jp +digick.jp +egoism.jp +fakefur.jp +fem.jp +flier.jp +floppy.jp +fool.jp +frenchkiss.jp +girlfriend.jp +girly.jp +gloomy.jp +gonna.jp +greater.jp +hacca.jp +heavy.jp +her.jp +hiho.jp +hippy.jp +holy.jp +hungry.jp +icurus.jp +itigo.jp +jellybean.jp +kikirara.jp +kill.jp +kilo.jp +kuron.jp +littlestar.jp +lolipopmc.jp +lolitapunk.jp +lomo.jp +lovepop.jp +lovesick.jp +main.jp +mods.jp +mond.jp +mongolian.jp +moo.jp +namaste.jp +nikita.jp +nobushi.jp +noor.jp +oops.jp +parallel.jp +parasite.jp +pecori.jp +peewee.jp +penne.jp +pepper.jp +perma.jp +pigboat.jp +pinoko.jp +punyu.jp +pupu.jp +pussycat.jp +pya.jp +raindrop.jp +readymade.jp +sadist.jp +schoolbus.jp +secret.jp +staba.jp +stripper.jp +sub.jp +sunnyday.jp +thick.jp +tonkotsu.jp +under.jp +upper.jp +velvet.jp +verse.jp +versus.jp +vivian.jp +watson.jp +weblike.jp +whitesnow.jp +zombie.jp +heteml.net + +// GoDaddy Registry : https://registry.godaddy +// Submitted by Rohan Durrant +graphic.design + +// GoIP DNS Services : http://www.goip.de +// Submitted by Christian Poulter +goip.de + +// Google, Inc. +// Submitted by Shannon McCabe +blogspot.ae +blogspot.al +blogspot.am +*.hosted.app +*.run.app +web.app +blogspot.com.ar +blogspot.co.at +blogspot.com.au +blogspot.ba +blogspot.be +blogspot.bg +blogspot.bj +blogspot.com.br +blogspot.com.by +blogspot.ca +blogspot.cf +blogspot.ch +blogspot.cl +blogspot.com.co +*.0emm.com +appspot.com +*.r.appspot.com +blogspot.com +codespot.com +googleapis.com +googlecode.com +pagespeedmobilizer.com +withgoogle.com +withyoutube.com +blogspot.cv +blogspot.com.cy +blogspot.cz +blogspot.de +*.gateway.dev +blogspot.dk +blogspot.com.ee +blogspot.com.eg +blogspot.com.es +blogspot.fi +blogspot.fr +cloud.goog +translate.goog +*.usercontent.goog +blogspot.gr +blogspot.hk +blogspot.hr +blogspot.hu +blogspot.co.id +blogspot.ie +blogspot.co.il +blogspot.in +blogspot.is +blogspot.it +blogspot.jp +blogspot.co.ke +blogspot.kr +blogspot.li +blogspot.lt +blogspot.lu +blogspot.md +blogspot.mk +blogspot.com.mt +blogspot.mx +blogspot.my +cloudfunctions.net +blogspot.com.ng +blogspot.nl +blogspot.no +blogspot.co.nz +blogspot.pe +blogspot.pt +blogspot.qa +blogspot.re +blogspot.ro +blogspot.rs +blogspot.ru +blogspot.se +blogspot.sg +blogspot.si +blogspot.sk +blogspot.sn +blogspot.td +blogspot.com.tr +blogspot.tw +blogspot.ug +blogspot.co.uk +blogspot.com.uy +blogspot.vn +blogspot.co.za + +// Goupile : https://goupile.fr +// Submitted by Niels Martignene +goupile.fr + +// GOV.UK Pay : https://www.payments.service.gov.uk/ +// Submitted by Richard Baker +pymnt.uk + +// GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ +// Submitted by Tom Whitwell +cloudapps.digital +london.cloudapps.digital + +// Government of the Netherlands: https://www.government.nl +// Submitted by +gov.nl + +// Grafana Labs: https://grafana.com/ +// Submitted by Platform Engineering +grafana-dev.net + +// GrayJay Web Solutions Inc. : https://grayjaysports.ca +// Submitted by Matt Yamkowy +grayjayleagues.com + +// GünstigBestellen : https://günstigbestellen.de +// Submitted by Furkan Akkoc +günstigbestellen.de +günstigliefern.de + +// Hakaran group: http://hakaran.cz +// Submitted by Arseniy Sokolov +fin.ci +free.hr +caa.li +ua.rs +conf.se + +// Häkkinen.fi +// Submitted by Eero Häkkinen +häkkinen.fi + +// Harrison Network : https://hrsn.net +// Submitted by William Harrison +wdh.app +hrsn.dev + +// Hashbang : https://hashbang.sh +hashbang.sh + +// Hasura : https://hasura.io +// Submitted by Shahidh K Muhammed +hasura.app +hasura-app.io + +// Hatena Co., Ltd. : https://hatena.co.jp +// Submitted by Masato Nakamura +hatenablog.com +hatenadiary.com +hateblo.jp +hatenablog.jp +hatenadiary.jp +hatenadiary.org + +// Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages): https://www.hs-heilbronn.de +// Submitted by Richard Zowalla +pages.it.hs-heilbronn.de + +// HeiyuSpace: https://lazycat.cloud +// Submitted by Xia Bin +heiyu.space + +// Helio Networks : https://heliohost.org +// Submitted by Ben Frede +helioho.st +heliohost.us + +// Hepforge : https://www.hepforge.org +// Submitted by David Grellscheid +hepforge.org + +// Heroku : https://www.heroku.com/ +// Submitted by Tom Maher +herokuapp.com +herokussl.com + +// Hibernating Rhinos +// Submitted by Oren Eini +ravendb.cloud +ravendb.community +development.run +ravendb.run + +// home.pl S.A.: https://home.pl +// Submitted by Krzysztof Wolski +homesklep.pl + +// Homebase : https://homebase.id/ +// Submitted by Jason Babo +*.kin.one +*.id.pub +*.kin.pub + +// Hong Kong Productivity Council: https://www.hkpc.org/ +// Submitted by SECaaS Team +secaas.hk + +// Hoplix : https://www.hoplix.com +// Submitted by Danilo De Franco +hoplix.shop + +// HOSTBIP REGISTRY : https://www.hostbip.com/ +// Submitted by Atanunu Igbunuroghene +orx.biz +biz.gl +biz.ng +co.biz.ng +dl.biz.ng +go.biz.ng +lg.biz.ng +on.biz.ng +col.ng +firm.ng +gen.ng +ltd.ng +ngo.ng +plc.ng + +// HostFly : https://www.ie.ua +// Submitted by Bohdan Dub +ie.ua + +// HostyHosting : https://hostyhosting.com +hostyhosting.io + +// Hugging Face: https://huggingface.co +// Submitted by Eliott Coyac +hf.space +static.hf.space + +// Hypernode B.V. : https://www.hypernode.com/ +// Submitted by Cipriano Groenendal +hypernode.io + +// I-O DATA DEVICE, INC. : http://www.iodata.com/ +// Submitted by Yuji Minagawa +iobb.net + +// i-registry s.r.o. : http://www.i-registry.cz/ +// Submitted by Martin Semrad +co.cz + +// Ici la Lune : http://www.icilalune.com/ +// Submitted by Simon Morvan +*.moonscale.io +moonscale.net + +// iDOT Services Limited : http://www.domain.gr.com +// Submitted by Gavin Brown +gr.com + +// iki.fi +// Submitted by Hannu Aronsson +iki.fi + +// iliad italia : https://www.iliad.it +// Submitted by Marios Makassikis +ibxos.it +iliadboxos.it + +// Incsub, LLC : https://incsub.com/ +// Submitted by Aaron Edwards +smushcdn.com +wphostedmail.com +wpmucdn.com +tempurl.host +wpmudev.host + +// Individual Network Berlin e.V. : https://www.in-berlin.de/ +// Submitted by Christian Seitz +dyn-berlin.de +in-berlin.de +in-brb.de +in-butter.de +in-dsl.de +in-vpn.de +in-dsl.net +in-vpn.net +in-dsl.org +in-vpn.org + +// info.at : http://www.info.at/ +biz.at +info.at + +// info.cx : http://info.cx +// Submitted by June Slater +info.cx + +// Interlegis : http://www.interlegis.leg.br +// Submitted by Gabriel Ferreira +ac.leg.br +al.leg.br +am.leg.br +ap.leg.br +ba.leg.br +ce.leg.br +df.leg.br +es.leg.br +go.leg.br +ma.leg.br +mg.leg.br +ms.leg.br +mt.leg.br +pa.leg.br +pb.leg.br +pe.leg.br +pi.leg.br +pr.leg.br +rj.leg.br +rn.leg.br +ro.leg.br +rr.leg.br +rs.leg.br +sc.leg.br +se.leg.br +sp.leg.br +to.leg.br + +// intermetrics GmbH : https://pixolino.com/ +// Submitted by Wolfgang Schwarz +pixolino.com + +// Internet-Pro, LLP : https://netangels.ru/ +// Submitted by Vasiliy Sheredeko +na4u.ru + +// IONOS SE : https://www.ionos.com/, +// IONOS Group SE: https://www.ionos-group.com/ +// submitted by Henrik Willert +apps-1and1.com +live-website.com +apps-1and1.net +websitebuilder.online +app-ionos.space + +// iopsys software solutions AB : https://iopsys.eu/ +// Submitted by Roman Azarenko +iopsys.se + +// IPFS Project : https://ipfs.tech/ +// Submitted by Interplanetary Shipyard +*.dweb.link + +// IPiFony Systems, Inc. : https://www.ipifony.com/ +// Submitted by Matthew Hardeman +ipifony.net + +// ir.md : https://nic.ir.md +// Submitted by Ali Soizi +ir.md + +// is-a-good.dev : https://is-a-good.dev +// Submitted by William Harrison +is-a-good.dev + +// is-a.dev : https://is-a.dev +// Submitted by William Harrison +is-a.dev + +// IServ GmbH : https://iserv.de +// Submitted by Mario Hoberg +iservschule.de +mein-iserv.de +schulplattform.de +schulserver.de +test-iserv.de +iserv.dev + +// Jelastic, Inc. : https://jelastic.com/ +// Submitted by Ihor Kolodyuk +mel.cloudlets.com.au +cloud.interhostsolutions.be +alp1.ae.flow.ch +appengine.flow.ch +es-1.axarnet.cloud +diadem.cloud +vip.jelastic.cloud +jele.cloud +it1.eur.aruba.jenv-aruba.cloud +it1.jenv-aruba.cloud +keliweb.cloud +cs.keliweb.cloud +oxa.cloud +tn.oxa.cloud +uk.oxa.cloud +primetel.cloud +uk.primetel.cloud +ca.reclaim.cloud +uk.reclaim.cloud +us.reclaim.cloud +ch.trendhosting.cloud +de.trendhosting.cloud +jele.club +dopaas.com +paas.hosted-by-previder.com +rag-cloud.hosteur.com +rag-cloud-ch.hosteur.com +jcloud.ik-server.com +jcloud-ver-jpc.ik-server.com +demo.jelastic.com +paas.massivegrid.com +jed.wafaicloud.com +ryd.wafaicloud.com +j.scaleforce.com.cy +jelastic.dogado.eu +fi.cloudplatform.fi +demo.datacenter.fi +paas.datacenter.fi +jele.host +mircloud.host +paas.beebyte.io +sekd1.beebyteapp.io +jele.io +jc.neen.it +jcloud.kz +cloudjiffy.net +fra1-de.cloudjiffy.net +west1-us.cloudjiffy.net +jls-sto1.elastx.net +jls-sto2.elastx.net +jls-sto3.elastx.net +fr-1.paas.massivegrid.net +lon-1.paas.massivegrid.net +lon-2.paas.massivegrid.net +ny-1.paas.massivegrid.net +ny-2.paas.massivegrid.net +sg-1.paas.massivegrid.net +jelastic.saveincloud.net +nordeste-idc.saveincloud.net +j.scaleforce.net +sdscloud.pl +unicloud.pl +mircloud.ru +enscaled.sg +jele.site +jelastic.team +orangecloud.tn +j.layershift.co.uk +phx.enscaled.us +mircloud.us + +// Jino : https://www.jino.ru +// Submitted by Sergey Ulyashin +myjino.ru +*.hosting.myjino.ru +*.landing.myjino.ru +*.spectrum.myjino.ru +*.vps.myjino.ru + +// Jotelulu S.L. : https://jotelulu.com +// Submitted by Daniel Fariña +jotelulu.cloud + +// JouwWeb B.V. : https://www.jouwweb.nl +// Submitted by Camilo Sperberg +webadorsite.com +jouwweb.site + +// Joyent : https://www.joyent.com/ +// Submitted by Brian Bennett +*.cns.joyent.com +*.triton.zone + +// JS.ORG : http://dns.js.org +// Submitted by Stefan Keim +js.org + +// KaasHosting : http://www.kaashosting.nl/ +// Submitted by Wouter Bakker +kaas.gg +khplay.nl + +// Kapsi : https://kapsi.fi +// Submitted by Tomi Juntunen +kapsi.fi + +// Katholieke Universiteit Leuven: https://www.kuleuven.be +// Submitted by Abuse KU Leuven +ezproxy.kuleuven.be +kuleuven.cloud + +// Keyweb AG : https://www.keyweb.de +// Submitted by Martin Dannehl +keymachine.de + +// KingHost : https://king.host +// Submitted by Felipe Keller Braz +kinghost.net +uni5.net + +// KnightPoint Systems, LLC : http://www.knightpoint.com/ +// Submitted by Roy Keene +knightpoint.systems + +// KoobinEvent, SL: https://www.koobin.com +// Submitted by Iván Oliva +koobin.events + +// Krellian Ltd. : https://krellian.com +// Submitted by Ben Francis +webthings.io +krellian.net + +// KUROKU LTD : https://kuroku.ltd/ +// Submitted by DisposaBoy +oya.to + +// LCube - Professional hosting e.K. : https://www.lcube-webhosting.de +// Submitted by Lars Laehn +git-repos.de +lcube-server.de +svn-repos.de + +// Leadpages : https://www.leadpages.net +// Submitted by Greg Dallavalle +leadpages.co +lpages.co +lpusercontent.com + +// Lelux.fi : https://lelux.fi/ +// Submitted by Lelux Admin +lelux.site + +// libp2p project : https://libp2p.io +// Submitted by Interplanetary Shipyard +libp2p.direct + +// Libre IT Ltd : https://libre.nz +// Submitted by Tomas Maggio +runcontainers.dev + +// Lifetime Hosting : https://Lifetime.Hosting/ +// Submitted by Mike Fillator +co.business +co.education +co.events +co.financial +co.network +co.place +co.technology + +// linkyard ldt: https://www.linkyard.ch/ +// Submitted by Mario Siegenthaler +linkyard-cloud.ch +linkyard.cloud + +// Linode : https://linode.com +// Submitted by +members.linode.com +*.nodebalancer.linode.com +*.linodeobjects.com +ip.linodeusercontent.com + +// LiquidNet Ltd : http://www.liquidnetlimited.com/ +// Submitted by Victor Velchev +we.bs + +// Listen53 : https://www.l53.net +// Submitted by Gerry Keh +filegear-sg.me +ggff.net + +// Localcert : https://localcert.dev +// Submitted by Lann Martin +*.user.localcert.dev + +// Log'in Line : https://www.loginline.com/ +// Submitted by Rémi Mach +loginline.app +loginline.dev +loginline.io +loginline.services +loginline.site + +// Lõhmus Family, The +// Submitted by Heiki Lõhmus +lohmus.me + +// Lokalized : https://lokalized.nl +// Submitted by Noah Taheij +servers.run + +// LubMAN UMCS Sp. z o.o : https://lubman.pl/ +// Submitted by Ireneusz Maliszewski +krasnik.pl +leczna.pl +lubartow.pl +lublin.pl +poniatowa.pl +swidnik.pl + +// Lug.org.uk : https://lug.org.uk +// Submitted by Jon Spriggs +glug.org.uk +lug.org.uk +lugs.org.uk + +// Lukanet Ltd : https://lukanet.com +// Submitted by Anton Avramov +barsy.bg +barsy.club +barsycenter.com +barsyonline.com +barsy.de +barsy.dev +barsy.eu +barsy.gr +barsy.in +barsy.info +barsy.io +barsy.me +barsy.menu +barsyonline.menu +barsy.mobi +barsy.net +barsy.online +barsy.org +barsy.pro +barsy.pub +barsy.ro +barsy.rs +barsy.shop +barsyonline.shop +barsy.site +barsy.store +barsy.support +barsy.uk +barsy.co.uk +barsyonline.co.uk + +// Magento Commerce +// Submitted by Damien Tournoud +*.magentosite.cloud + +// Mail.Ru Group : https://hb.cldmail.ru +// Submitted by Ilya Zaretskiy +hb.cldmail.ru + +// MathWorks : https://www.mathworks.com/ +// Submitted by Emily Reed +matlab.cloud +modelscape.com +mwcloudnonprod.com +polyspace.com + +// May First - People Link : https://mayfirst.org/ +// Submitted by Jamie McClelland +mayfirst.info +mayfirst.org + +// Maze Play: https://www.mazeplay.com +// Submitted by Adam Humpherys +mazeplay.com + +// McHost : https://mchost.ru +// Submitted by Evgeniy Subbotin +mcdir.me +mcdir.ru +vps.mcdir.ru +mcpre.ru + +// mcpe.me : https://mcpe.me +// Submitted by Noa Heyl +mcpe.me + +// Mediatech : https://mediatech.by +// Submitted by Evgeniy Kozhuhovskiy +mediatech.by +mediatech.dev + +// Medicom Health : https://medicomhealth.com +// Submitted by Michael Olson +hra.health + +// MedusaJS, Inc : https://medusajs.com/ +// Submitted by Stevche Radevski +medusajs.app + +// Memset hosting : https://www.memset.com +// Submitted by Tom Whitwell +miniserver.com +memset.net + +// Messerli Informatik AG : https://www.messerli.ch/ +// Submitted by Ruben Schmidmeister +messerli.app + +// Meta Platforms, Inc. : https://meta.com/ +// Submitted by Jacob Cordero +atmeta.com +apps.fbsbx.com + +// MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ +// Submitted by Zdeněk Šustr and Radim Janča +*.cloud.metacentrum.cz +custom.metacentrum.cz +flt.cloud.muni.cz +usr.cloud.muni.cz + +// Meteor Development Group : https://www.meteor.com/hosting +// Submitted by Pierre Carrier +meteorapp.com +eu.meteorapp.com + +// Michau Enterprises Limited : http://www.co.pl/ +co.pl + +// Microsoft Corporation : http://microsoft.com +// Submitted by Public Suffix List Admin +// Managed by Corporate Domains +// Microsoft Azure : https://home.azure +*.azurecontainer.io +azure-api.net +azure-mobile.net +azureedge.net +azurefd.net +azurestaticapps.net +1.azurestaticapps.net +2.azurestaticapps.net +3.azurestaticapps.net +4.azurestaticapps.net +5.azurestaticapps.net +6.azurestaticapps.net +7.azurestaticapps.net +centralus.azurestaticapps.net +eastasia.azurestaticapps.net +eastus2.azurestaticapps.net +westeurope.azurestaticapps.net +westus2.azurestaticapps.net +azurewebsites.net +cloudapp.net +trafficmanager.net +blob.core.windows.net +servicebus.windows.net + +// MikroTik: https://mikrotik.com +// Submitted by MikroTik SysAdmin Team +routingthecloud.com +sn.mynetname.net +routingthecloud.net +routingthecloud.org + +// minion.systems : http://minion.systems +// Submitted by Robert Böttinger +csx.cc + +// Mittwald CM Service GmbH & Co. KG : https://mittwald.de +// Submitted by Marco Rieger +mydbserver.com +webspaceconfig.de +mittwald.info +mittwaldserver.info +typo3server.info +project.space + +// MODX Systems LLC : https://modx.com +// Submitted by Elizabeth Southwell +modx.dev + +// Mozilla Foundation : https://mozilla.org/ +// Submitted by glob +bmoattachments.org + +// MSK-IX : https://www.msk-ix.ru/ +// Submitted by Khannanov Roman +net.ru +org.ru +pp.ru + +// Mythic Beasts : https://www.mythic-beasts.com +// Submitted by Paul Cammish +hostedpi.com +caracal.mythic-beasts.com +customer.mythic-beasts.com +fentiger.mythic-beasts.com +lynx.mythic-beasts.com +ocelot.mythic-beasts.com +oncilla.mythic-beasts.com +onza.mythic-beasts.com +sphinx.mythic-beasts.com +vs.mythic-beasts.com +x.mythic-beasts.com +yali.mythic-beasts.com +cust.retrosnub.co.uk + +// Nabu Casa : https://www.nabucasa.com +// Submitted by Paulus Schoutsen +ui.nabu.casa + +// Net at Work Gmbh : https://www.netatwork.de +// Submitted by Jan Jaeschke +cloud.nospamproxy.com + +// Netfy Domains : https://netfy.domains +// Submitted by Suranga Ranasinghe +netfy.app + +// Netlify : https://www.netlify.com +// Submitted by Jessica Parsons +netlify.app + +// Neustar Inc. +// Submitted by Trung Tran +4u.com + +// NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ +// Submitted by Jeff Wheelhouse +nfshost.com + +// NFT.Storage : https://nft.storage/ +// Submitted by Vasco Santos or +ipfs.nftstorage.link + +// NGO.US Registry : https://nic.ngo.us +// Submitted by Alstra Solutions Ltd. Networking Team +ngo.us + +// ngrok : https://ngrok.com/ +// Submitted by Alan Shreve +ngrok.app +ngrok-free.app +ngrok.dev +ngrok-free.dev +ngrok.io +ap.ngrok.io +au.ngrok.io +eu.ngrok.io +in.ngrok.io +jp.ngrok.io +sa.ngrok.io +us.ngrok.io +ngrok.pizza +ngrok.pro + +// Nicolaus Copernicus University in Torun - MSK TORMAN (https://www.man.torun.pl) +torun.pl + +// Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ +// Submitted by Nicholas Ford +nh-serv.co.uk +nimsite.uk + +// No-IP.com : https://noip.com/ +// Submitted by Deven Reza +mmafan.biz +myftp.biz +no-ip.biz +no-ip.ca +fantasyleague.cc +gotdns.ch +3utilities.com +blogsyte.com +ciscofreak.com +damnserver.com +ddnsking.com +ditchyourip.com +dnsiskinky.com +dynns.com +geekgalaxy.com +health-carereform.com +homesecuritymac.com +homesecuritypc.com +myactivedirectory.com +mysecuritycamera.com +myvnc.com +net-freaks.com +onthewifi.com +point2this.com +quicksytes.com +securitytactics.com +servebeer.com +servecounterstrike.com +serveexchange.com +serveftp.com +servegame.com +servehalflife.com +servehttp.com +servehumour.com +serveirc.com +servemp3.com +servep2p.com +servepics.com +servequake.com +servesarcasm.com +stufftoread.com +unusualperson.com +workisboring.com +dvrcam.info +ilovecollege.info +no-ip.info +brasilia.me +ddns.me +dnsfor.me +hopto.me +loginto.me +noip.me +webhop.me +bounceme.net +ddns.net +eating-organic.net +mydissent.net +myeffect.net +mymediapc.net +mypsx.net +mysecuritycamera.net +nhlfan.net +no-ip.net +pgafan.net +privatizehealthinsurance.net +redirectme.net +serveblog.net +serveminecraft.net +sytes.net +cable-modem.org +collegefan.org +couchpotatofries.org +hopto.org +mlbfan.org +myftp.org +mysecuritycamera.org +nflfan.org +no-ip.org +read-books.org +ufcfan.org +zapto.org +no-ip.co.uk +golffan.us +noip.us +pointto.us + +// NodeArt : https://nodeart.io +// Submitted by Konstantin Nosov +stage.nodeart.io + +// Noop : https://noop.app +// Submitted by Nathaniel Schweinberg +*.developer.app +noop.app + +// Northflank Ltd. : https://northflank.com/ +// Submitted by Marco Suter +*.northflank.app +*.build.run +*.code.run +*.database.run +*.migration.run + +// Noticeable : https://noticeable.io +// Submitted by Laurent Pellegrino +noticeable.news + +// Notion Labs, Inc : https://www.notion.so/ +// Submitted by Jess Yao +notion.site + +// Now-DNS : https://now-dns.com +// Submitted by Steve Russell +dnsking.ch +mypi.co +n4t.co +001www.com +myiphost.com +forumz.info +soundcast.me +tcp4.me +dnsup.net +hicam.net +now-dns.net +ownip.net +vpndns.net +dynserv.org +now-dns.org +x443.pw +now-dns.top +ntdll.top +freeddns.us + +// nsupdate.info : https://www.nsupdate.info/ +// Submitted by Thomas Waldmann +nsupdate.info +nerdpol.ovh + +// NYC.mn : https://dot.nyc.mn/ +// Submitted by NYC.mn Subdomain Service +nyc.mn + +// O3O.Foundation : https://o3o.foundation/ +// Submitted by the prvcy.page Registry Team +prvcy.page + +// Obl.ong : +// Submitted by Reese Armstrong +obl.ong + +// Observable, Inc. : https://observablehq.com +// Submitted by Mike Bostock +observablehq.cloud +static.observableusercontent.com + +// OMG.LOL : https://omg.lol +// Submitted by Adam Newbold +omg.lol + +// Omnibond Systems, LLC. : https://www.omnibond.com +// Submitted by Cole Estep +cloudycluster.net + +// OmniWe Limited: https://omniwe.com +// Submitted by Vicary Archangel +omniwe.site + +// One.com: https://www.one.com/ +// Submitted by Jacob Bunk Nielsen +123webseite.at +123website.be +simplesite.com.br +123website.ch +simplesite.com +123webseite.de +123hjemmeside.dk +123miweb.es +123kotisivu.fi +123siteweb.fr +simplesite.gr +123homepage.it +123website.lu +123website.nl +123hjemmeside.no +service.one +simplesite.pl +123paginaweb.pt +123minsida.se + +// Open Domains : https://open-domains.net +// Submitted by William Harrison +is-a-fullstack.dev +is-cool.dev +is-not-a.dev +localplayer.dev +is-local.org + +// Open Social : https://www.getopensocial.com/ +// Submitted by Alexander Varwijk +opensocial.site + +// OpenCraft GmbH : http://opencraft.com/ +// Submitted by Sven Marnach +opencraft.hosting + +// OpenHost : https://registry.openhost.uk +// Submitted by OpenHost Registry Team +16-b.it +32-b.it +64-b.it + +// OpenResearch GmbH: https://openresearch.com/ +// Submitted by Philipp Schmid +orsites.com + +// Opera Software, A.S.A. +// Submitted by Yngve Pettersen +operaunite.com + +// Oracle Dyn : https://cloud.oracle.com/home https://dyn.com/dns/ +// Submitted by Gregory Drake +// Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label +*.customer-oci.com +*.oci.customer-oci.com +*.ocp.customer-oci.com +*.ocs.customer-oci.com +*.oraclecloudapps.com +*.oraclegovcloudapps.com +*.oraclegovcloudapps.uk + +// Orange : https://www.orange.com +// Submitted by Alexandre Linte +tech.orange + +// OsSav Technology Ltd. : https://ossav.com/ +// TLD Nic: http://nic.can.re - TLD Whois Server: whois.can.re +// Submitted by OsSav Technology Ltd. +can.re + +// Oursky Limited : https://authgear.com/, https://skygear.io/ +// Submitted by Authgear Team , Skygear Developer +authgear-staging.com +authgearapps.com +skygearapp.com + +// OutSystems +// Submitted by Duarte Santos +outsystemscloud.com + +// OVHcloud: https://ovhcloud.com +// Submitted by Vincent Cassé +*.hosting.ovh.net +*.webpaas.ovh.net + +// OwnProvider GmbH: http://www.ownprovider.com +// Submitted by Jan Moennich +ownprovider.com +own.pm + +// OwO : https://whats-th.is/ +// Submitted by Dean Sheather +*.owo.codes + +// OX : http://www.ox.rs +// Submitted by Adam Grand +ox.rs + +// oy.lc +// Submitted by Charly Coste +oy.lc + +// Pagefog : https://pagefog.com/ +// Submitted by Derek Myers +pgfog.com + +// PageXL : https://pagexl.com +// Submitted by Yann Guichard +pagexl.com + +// Pantheon Systems, Inc. : https://pantheon.io/ +// Submitted by Gary Dylina +gotpantheon.com +pantheonsite.io + +// Paywhirl, Inc : https://paywhirl.com/ +// Submitted by Daniel Netzer +*.paywhirl.com + +// pcarrier.ca Software Inc: https://pcarrier.ca/ +// Submitted by Pierre Carrier +*.xmit.co +xmit.dev +madethis.site +srv.us +gh.srv.us +gl.srv.us + +// PE Ulyanov Kirill Sergeevich : https://airy.host +// Submitted by Kirill Ulyanov +lk3.ru + +// Peplink | Pepwave : http://peplink.com/ +// Submitted by Steve Leung +mypep.link + +// Perspecta : https://perspecta.com/ +// Submitted by Kenneth Van Alstyne +perspecta.cloud + +// Planet-Work : https://www.planet-work.com/ +// Submitted by Frédéric VANNIÈRE +on-web.fr + +// Platform.sh : https://platform.sh +// Submitted by Nikola Kotur +*.upsun.app +upsunapp.com +ent.platform.sh +eu.platform.sh +us.platform.sh +*.platformsh.site +*.tst.site + +// Platter: https://platter.dev +// Submitted by Patrick Flor +platter-app.com +platter-app.dev +platterp.us + +// Pley AB : https://www.pley.com/ +// Submitted by Henning Pohl +pley.games + +// Porter : https://porter.run/ +// Submitted by Rudraksh MK +onporter.run + +// Positive Codes Technology Company : http://co.bn/faq.html +// Submitted by Zulfais +co.bn + +// Postman, Inc : https://postman.com +// Submitted by Rahul Dhawan +postman-echo.com +pstmn.io +mock.pstmn.io +httpbin.org + +// prequalifyme.today : https://prequalifyme.today +// Submitted by DeepakTiwari deepak@ivylead.io +prequalifyme.today + +// prgmr.com : https://prgmr.com/ +// Submitted by Sarah Newman +xen.prgmr.com + +// priv.at : http://www.nic.priv.at/ +// Submitted by registry +priv.at + +// Protonet GmbH : http://protonet.io +// Submitted by Martin Meier +protonet.io + +// Publication Presse Communication SARL : https://ppcom.fr +// Submitted by Yaacov Akiba Slama +chirurgiens-dentistes-en-france.fr +byen.site + +// pubtls.org: https://www.pubtls.org +// Submitted by Kor Nielsen +pubtls.org + +// PythonAnywhere LLP: https://www.pythonanywhere.com +// Submitted by Giles Thomas +pythonanywhere.com +eu.pythonanywhere.com + +// QA2 +// Submitted by Daniel Dent (https://www.danieldent.com/) +qa2.com + +// QCX +// Submitted by Cassandra Beelen +qcx.io +*.sys.qcx.io + +// QNAP System Inc : https://www.qnap.com +// Submitted by Nick Chang +myqnapcloud.cn +alpha-myqnapcloud.com +dev-myqnapcloud.com +mycloudnas.com +mynascloud.com +myqnapcloud.com + +// QOTO, Org. +// Submitted by Jeffrey Phillips Freeman +qoto.io + +// Qualifio : https://qualifio.com/ +// Submitted by Xavier De Cock +qualifioapp.com + +// Quality Unit : https://qualityunit.com +// Submitted by Vasyl Tsalko +ladesk.com + +// QuickBackend: https://www.quickbackend.com +// Submitted by Dani Biro +qbuser.com + +// Quip : https://quip.com +// Submitted by Patrick Linehan +*.quipelements.com + +// Qutheory LLC : http://qutheory.io +// Submitted by Jonas Schwartz +vapor.cloud +vaporcloud.io + +// Rackmaze LLC : https://www.rackmaze.com +// Submitted by Kirill Pertsev +rackmaze.com +rackmaze.net + +// Rad Web Hosting: https://radwebhosting.com +// Submitted by Scott Claeys +cloudsite.builders +myradweb.net +servername.us + +// Radix FZC : http://domains.in.net +// Submitted by Gavin Brown +web.in +in.net + +// Raidboxes GmbH : https://raidboxes.de +// Submitted by Auke Tembrink +myrdbx.io +site.rb-hosting.io + +// Rancher Labs, Inc : https://rancher.com +// Submitted by Vincent Fiduccia +*.on-rancher.cloud +*.on-k3s.io +*.on-rio.io + +// RavPage : https://www.ravpage.co.il +// Submitted by Roni Horowitz +ravpage.co.il + +// Read The Docs, Inc : https://www.readthedocs.org +// Submitted by David Fischer +readthedocs-hosted.com +readthedocs.io + +// Red Hat, Inc. OpenShift : https://openshift.redhat.com/ +// Submitted by Tim Kramer +rhcloud.com + +// Redgate Software: https://red-gate.com +// Submitted by Andrew Farries +instances.spawn.cc + +// Render : https://render.com +// Submitted by Anurag Goel +onrender.com +app.render.com + +// Repl.it : https://repl.it +// Submitted by Lincoln Bergeson +replit.app +id.replit.app +firewalledreplit.co +id.firewalledreplit.co +repl.co +id.repl.co +replit.dev +archer.replit.dev +bones.replit.dev +canary.replit.dev +global.replit.dev +hacker.replit.dev +id.replit.dev +janeway.replit.dev +kim.replit.dev +kira.replit.dev +kirk.replit.dev +odo.replit.dev +paris.replit.dev +picard.replit.dev +pike.replit.dev +prerelease.replit.dev +reed.replit.dev +riker.replit.dev +sisko.replit.dev +spock.replit.dev +staging.replit.dev +sulu.replit.dev +tarpit.replit.dev +teams.replit.dev +tucker.replit.dev +wesley.replit.dev +worf.replit.dev +repl.run + +// Resin.io : https://resin.io +// Submitted by Tim Perry +resindevice.io +devices.resinstaging.io + +// RethinkDB : https://www.rethinkdb.com/ +// Submitted by Chris Kastorff +hzc.io + +// Rico Developments Limited : https://adimo.co +// Submitted by Colin Brown +adimo.co.uk + +// Riseup Networks : https://riseup.net +// Submitted by Micah Anderson +itcouldbewor.se + +// Roar Domains LLC : https://roar.basketball/ +// Submitted by Gavin Brown +aus.basketball +nz.basketball + +// Rochester Institute of Technology : http://www.rit.edu/ +// Submitted by Jennifer Herting +git-pages.rit.edu + +// Rocky Enterprise Software Foundation : https://resf.org +// Submitted by Neil Hanlon +rocky.page + +// Rusnames Limited: http://rusnames.ru/ +// Submitted by Sergey Zotov +биз.рус +ком.рус +крым.рус +мир.рус +мск.рус +орг.рус +самара.рус +сочи.рус +спб.рус +я.рус + +// Russian Academy of Sciences +// Submitted by Tech Support +ras.ru + +// Sakura Frp : https://www.natfrp.com +// Submitted by Bobo Liu +nyat.app + +// SAKURA Internet Inc. : https://www.sakura.ad.jp/ +// Submitted by Internet Service Department +180r.com +dojin.com +sakuratan.com +sakuraweb.com +x0.com +2-d.jp +bona.jp +crap.jp +daynight.jp +eek.jp +flop.jp +halfmoon.jp +jeez.jp +matrix.jp +mimoza.jp +ivory.ne.jp +mail-box.ne.jp +mints.ne.jp +mokuren.ne.jp +opal.ne.jp +sakura.ne.jp +sumomo.ne.jp +topaz.ne.jp +netgamers.jp +nyanta.jp +o0o0.jp +rdy.jp +rgr.jp +rulez.jp +s3.isk01.sakurastorage.jp +s3.isk02.sakurastorage.jp +saloon.jp +sblo.jp +skr.jp +tank.jp +uh-oh.jp +undo.jp +rs.webaccel.jp +user.webaccel.jp +websozai.jp +xii.jp +squares.net +jpn.org +kirara.st +x0.to +from.tv +sakura.tv + +// Salesforce.com, Inc. https://salesforce.com/ +// Submitted by Salesforce Public Suffix List Team +*.builder.code.com +*.dev-builder.code.com +*.stg-builder.code.com +*.001.test.code-builder-stg.platform.salesforce.com +*.d.crm.dev +*.w.crm.dev +*.wa.crm.dev +*.wb.crm.dev +*.wc.crm.dev +*.wd.crm.dev +*.we.crm.dev +*.wf.crm.dev + +// Sandstorm Development Group, Inc. : https://sandcats.io/ +// Submitted by Asheesh Laroia +sandcats.io + +// SBE network solutions GmbH : https://www.sbe.de/ +// Submitted by Norman Meilick +logoip.com +logoip.de + +// Scaleway : https://www.scaleway.com/ +// Submitted by Rémy Léone +fr-par-1.baremetal.scw.cloud +fr-par-2.baremetal.scw.cloud +nl-ams-1.baremetal.scw.cloud +cockpit.fr-par.scw.cloud +fnc.fr-par.scw.cloud +functions.fnc.fr-par.scw.cloud +k8s.fr-par.scw.cloud +nodes.k8s.fr-par.scw.cloud +s3.fr-par.scw.cloud +s3-website.fr-par.scw.cloud +whm.fr-par.scw.cloud +priv.instances.scw.cloud +pub.instances.scw.cloud +k8s.scw.cloud +cockpit.nl-ams.scw.cloud +k8s.nl-ams.scw.cloud +nodes.k8s.nl-ams.scw.cloud +s3.nl-ams.scw.cloud +s3-website.nl-ams.scw.cloud +whm.nl-ams.scw.cloud +cockpit.pl-waw.scw.cloud +k8s.pl-waw.scw.cloud +nodes.k8s.pl-waw.scw.cloud +s3.pl-waw.scw.cloud +s3-website.pl-waw.scw.cloud +scalebook.scw.cloud +smartlabeling.scw.cloud +dedibox.fr + +// schokokeks.org GbR : https://schokokeks.org/ +// Submitted by Hanno Böck +schokokeks.net + +// Scottish Government: https://www.gov.scot +// Submitted by Martin Ellis +gov.scot +service.gov.scot + +// Scry Security : http://www.scrysec.com +// Submitted by Shante Adam +scrysec.com + +// Scrypted : https://scrypted.app +// Submitted by Koushik Dutta +client.scrypted.io + +// Securepoint GmbH : https://www.securepoint.de +// Submitted by Erik Anders +firewall-gateway.com +firewall-gateway.de +my-gateway.de +my-router.de +spdns.de +spdns.eu +firewall-gateway.net +my-firewall.org +myfirewall.org +spdns.org + +// Seidat : https://www.seidat.com +// Submitted by Artem Kondratev +seidat.net + +// Sellfy : https://sellfy.com +// Submitted by Yuriy Romadin +sellfy.store + +// Sendmsg: https://www.sendmsg.co.il +// Submitted by Assaf Stern +minisite.ms + +// Senseering GmbH : https://www.senseering.de +// Submitted by Felix Mönckemeyer +senseering.net + +// Servebolt AS: https://servebolt.com +// Submitted by Daniel Kjeserud +servebolt.cloud + +// Service Online LLC : http://drs.ua/ +// Submitted by Serhii Bulakh +biz.ua +co.ua +pp.ua + +// Shanghai Accounting Society : https://www.sasf.org.cn +// Submitted by Information Administration +as.sh.cn + +// Sheezy.Art : https://sheezy.art +// Submitted by Nyoom +sheezy.games + +// ShiftEdit : https://shiftedit.net/ +// Submitted by Adam Jimenez +shiftedit.io + +// Shopblocks : http://www.shopblocks.com/ +// Submitted by Alex Bowers +myshopblocks.com + +// Shopify : https://www.shopify.com +// Submitted by Alex Richter +myshopify.com + +// Shopit : https://www.shopitcommerce.com/ +// Submitted by Craig McMahon +shopitsite.com + +// shopware AG : https://shopware.com +// Submitted by Jens Küper +shopware.shop +shopware.store + +// Siemens Mobility GmbH +// Submitted by Oliver Graebner +mo-siemens.io + +// SinaAppEngine : http://sae.sina.com.cn/ +// Submitted by SinaAppEngine +1kapp.com +appchizi.com +applinzi.com +sinaapp.com +vipsinaapp.com + +// Siteleaf : https://www.siteleaf.com/ +// Submitted by Skylar Challand +siteleaf.net + +// Small Technology Foundation : https://small-tech.org +// Submitted by Aral Balkan +small-web.org + +// Smallregistry by Promopixel SARL: https://www.smallregistry.net +// Former AFNIC's SLDs +// Submitted by Jérôme Lipowicz +aeroport.fr +avocat.fr +chambagri.fr +chirurgiens-dentistes.fr +experts-comptables.fr +medecin.fr +notaires.fr +pharmacien.fr +port.fr +veterinaire.fr + +// Smoove.io : https://www.smoove.io/ +// Submitted by Dan Kozak +vp4.me + +// Snowflake Inc : https://www.snowflake.com/ +// Submitted by Sam Haar +*.snowflake.app +*.privatelink.snowflake.app +streamlit.app +streamlitapp.com + +// Snowplow Analytics : https://snowplowanalytics.com/ +// Submitted by Ian Streeter +try-snowplow.com + +// Software Consulting Michal Zalewski : https://www.mafelo.com +// Submitted by Michal Zalewski +mafelo.net + +// Sony Interactive Entertainment LLC : https://sie.com/ +// Submitted by David Coles +playstation-cloud.com + +// SourceHut : https://sourcehut.org +// Submitted by Drew DeVault +srht.site + +// SourceLair PC : https://www.sourcelair.com +// Submitted by Antonis Kalipetis +apps.lair.io +*.stolos.io + +// SpaceKit : https://www.spacekit.io/ +// Submitted by Reza Akhavan +spacekit.io + +// SparrowHost : https://sparrowhost.in/ +// Submitted by Anant Pandey +ind.mom + +// SpeedPartner GmbH: https://www.speedpartner.de/ +// Submitted by Stefan Neufeind +customer.speedpartner.de + +// Spreadshop (sprd.net AG) : https://www.spreadshop.com/ +// Submitted by Martin Breest +myspreadshop.at +myspreadshop.com.au +myspreadshop.be +myspreadshop.ca +myspreadshop.ch +myspreadshop.com +myspreadshop.de +myspreadshop.dk +myspreadshop.es +myspreadshop.fi +myspreadshop.fr +myspreadshop.ie +myspreadshop.it +myspreadshop.net +myspreadshop.nl +myspreadshop.no +myspreadshop.pl +myspreadshop.se +myspreadshop.co.uk + +// StackBlitz : https://stackblitz.com +// Submitted by Dominic Elm +w-corp-staticblitz.com +w-credentialless-staticblitz.com +w-staticblitz.com + +// Stackhero : https://www.stackhero.io +// Submitted by Adrien Gillon +stackhero-network.com + +// STACKIT : https://www.stackit.de/en/ +// Submitted by STACKIT-DNS Team (Simon Stier) +runs.onstackit.cloud +stackit.gg +stackit.rocks +stackit.run +stackit.zone + +// Staclar : https://staclar.com +// Submitted by Q Misell +// Submitted by Matthias Merkel +musician.io +novecore.site + +// Standard Library : https://stdlib.com +// Submitted by Jacob Lee +api.stdlib.com + +// stereosense GmbH : https://www.involve.me +// Submitted by Florian Burmann +feedback.ac +forms.ac +assessments.cx +calculators.cx +funnels.cx +paynow.cx +quizzes.cx +researched.cx +tests.cx +surveys.so + +// Storebase : https://www.storebase.io +// Submitted by Tony Schirmer +storebase.store + +// Storipress : https://storipress.com +// Submitted by Benno Liu +storipress.app + +// Storj Labs Inc. : https://storj.io/ +// Submitted by Philip Hutchins +storj.farm + +// Strapi : https://strapi.io/ +// Submitted by Florent Baldino +strapiapp.com +media.strapiapp.com + +// Strategic System Consulting (eApps Hosting): https://www.eapps.com/ +// Submitted by Alex Oancea +vps-host.net +atl.jelastic.vps-host.net +njs.jelastic.vps-host.net +ric.jelastic.vps-host.net + +// Streak : https://streak.com +// Submitted by Blake Kadatz +streak-link.com +streaklinks.com +streakusercontent.com + +// Student-Run Computing Facility : https://www.srcf.net/ +// Submitted by Edwin Balani +soc.srcf.net +user.srcf.net + +// Studenten Net Twente : http://www.snt.utwente.nl/ +// Submitted by Silke Hofstra +utwente.io + +// Sub 6 Limited: http://www.sub6.com +// Submitted by Dan Miller +temp-dns.com + +// Supabase : https://supabase.io +// Submitted by Inian Parameshwaran +supabase.co +supabase.in +supabase.net + +// Syncloud : https://syncloud.org +// Submitted by Boris Rybalkin +syncloud.it + +// Synology, Inc. : https://www.synology.com/ +// Submitted by Rony Weng +dscloud.biz +direct.quickconnect.cn +dsmynas.com +familyds.com +diskstation.me +dscloud.me +i234.me +myds.me +synology.me +dscloud.mobi +dsmynas.net +familyds.net +dsmynas.org +familyds.org +direct.quickconnect.to +vpnplus.to + +// Tabit Technologies Ltd. : https://tabit.cloud/ +// Submitted by Oren Agiv +mytabit.com +mytabit.co.il +tabitorder.co.il + +// TAIFUN Software AG : http://taifun-software.de +// Submitted by Bjoern Henke +taifun-dns.de + +// Tailscale Inc. : https://www.tailscale.com +// Submitted by David Anderson +ts.net +*.c.ts.net + +// TASK geographical domains (https://www.task.gda.pl/uslugi/dns) +gda.pl +gdansk.pl +gdynia.pl +med.pl +sopot.pl + +// tawk.to, Inc : https://www.tawk.to +// Submitted by tawk.to developer team +p.tawk.email +p.tawkto.email + +// team.blue https://team.blue +// Submitted by Cedric Dubois +site.tb-hosting.com + +// Teckids e.V. : https://www.teckids.org +// Submitted by Dominik George +edugit.io +s3.teckids.org + +// Telebit : https://telebit.cloud +// Submitted by AJ ONeal +telebit.app +telebit.io +*.telebit.xyz + +// Thingdust AG : https://thingdust.com/ +// Submitted by Adrian Imboden +*.firenet.ch +*.svc.firenet.ch +reservd.com +thingdustdata.com +cust.dev.thingdust.io +reservd.dev.thingdust.io +cust.disrec.thingdust.io +reservd.disrec.thingdust.io +cust.prod.thingdust.io +cust.testing.thingdust.io +reservd.testing.thingdust.io + +// ticket i/O GmbH : https://ticket.io +// Submitted by Christian Franke +tickets.io + +// Tlon.io : https://tlon.io +// Submitted by Mark Staarink +arvo.network +azimuth.network +tlon.network + +// Tor Project, Inc. : https://torproject.org +// Submitted by Antoine Beaupré +torproject.net +pages.torproject.net + +// TownNews.com : http://www.townnews.com +// Submitted by Dustin Ward +townnews-staging.com + +// TrafficPlex GmbH : https://www.trafficplex.de/ +// Submitted by Phillipp Röll +12hp.at +2ix.at +4lima.at +lima-city.at +12hp.ch +2ix.ch +4lima.ch +lima-city.ch +trafficplex.cloud +de.cool +12hp.de +2ix.de +4lima.de +lima-city.de +1337.pictures +clan.rip +lima-city.rocks +webspace.rocks +lima.zone + +// TransIP : https://www.transip.nl +// Submitted by Rory Breuk and Cedric Dubois +*.transurl.be +*.transurl.eu +site.transip.me +*.transurl.nl + +// TuxFamily : http://tuxfamily.org +// Submitted by TuxFamily administrators +tuxfamily.org + +// TwoDNS : https://www.twodns.de/ +// Submitted by TwoDNS-Support +dd-dns.de +dray-dns.de +draydns.de +dyn-vpn.de +dynvpn.de +mein-vigor.de +my-vigor.de +my-wan.de +syno-ds.de +synology-diskstation.de +synology-ds.de +diskstation.eu +diskstation.org + +// Typedream : https://typedream.com +// Submitted by Putri Karunia +typedream.app + +// Typeform : https://www.typeform.com +// Submitted by Sergi Ferriz +pro.typeform.com + +// Uberspace : https://uberspace.de +// Submitted by Moritz Werner +*.uberspace.de +uber.space + +// UDR Limited : http://www.udr.hk.com +// Submitted by registry +hk.com +inc.hk +ltd.hk +hk.org + +// UK Intis Telecom LTD : https://it.com +// Submitted by ITComdomains +it.com + +// Unison Computing, PBC : https://unison.cloud +// Submitted by Simon Højberg +unison-services.cloud + +// United Gameserver GmbH : https://united-gameserver.de +// Submitted by Stefan Schwarz +virtual-user.de +virtualuser.de + +// UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/ +// see also: whois -h whois.udr.org.yt help +// Submitted by Atanunu Igbunuroghene +name.pm +sch.tf +biz.wf +sch.wf +org.yt + +// University of Banja Luka : https://unibl.org +// Domains for Republic of Srpska administrative entity. +// Submitted by Marko Ivanovic +rs.ba + +// University of Bielsko-Biala regional domain: http://dns.bielsko.pl/ +// Submitted by Marcin +bielsko.pl + +// Upli : https://upli.io +// Submitted by Lenny Bakkalian +upli.io + +// urown.net : https://urown.net +// Submitted by Hostmaster +urown.cloud +dnsupdate.info + +// US REGISTRY LLC : http://us.org +// Submitted by Gavin Brown +us.org + +// V.UA Domain Administrator : https://domain.v.ua/ +// Submitted by Serhii Rostilo +v.ua + +// Val Town, Inc : https://val.town/ +// Submitted by Tom MacWright +express.val.run +web.val.run + +// Vercel, Inc : https://vercel.com/ +// Submitted by Max Leiter +vercel.app +v0.build +vercel.dev +vusercontent.net +now.sh + +// VeryPositive SIA : http://very.lv +// Submitted by Danko Aleksejevs +2038.io + +// Viprinet Europe GmbH : http://www.viprinet.com +// Submitted by Simon Kissel +router.management + +// Virtual-Info : https://www.virtual-info.info/ +// Submitted by Adnan RIHAN +v-info.info + +// Voorloper.com: https://voorloper.com +// Submitted by Nathan van Bakel +voorloper.cloud + +// Vultr Objects : https://www.vultr.com/products/object-storage/ +// Submitted by Niels Maumenee +*.vultrobjects.com + +// Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com +// Submitted by Masayuki Note +wafflecell.com + +// Webflow, Inc. : https://www.webflow.com +// Submitted by Webflow Security Team +webflow.io +webflowtest.io + +// WebHare bv : https://www.webhare.com/ +// Submitted by Arnold Hendriks +*.webhare.dev + +// WebHotelier Technologies Ltd : https://www.webhotelier.net/ +// Submitted by Apostolos Tsakpinis +bookonline.app +hotelwithflight.com +reserve-online.com +reserve-online.net + +// WebPros International, LLC : https://webpros.com/ +// Submitted by Nicolas Rochelemagne +cprapid.com +pleskns.com +wp2.host +pdns.page +plesk.page +wpsquared.site + +// WebWaddle Ltd : https://webwaddle.com/ +// Submitted by Merlin Glander +*.wadl.top + +// Western Digital Technologies, Inc : https://www.wdc.com +// Submitted by Jung Jin +remotewd.com + +// Whatbox Inc. : https://whatbox.ca/ +// Submitted by Anthony Ryan +box.ca + +// WIARD Enterprises : https://wiardweb.com +// Submitted by Kidd Hustle +pages.wiardweb.com + +// Wikimedia Labs : https://wikitech.wikimedia.org +// Submitted by Arturo Borrero Gonzalez +toolforge.org +wmcloud.org +wmflabs.org + +// WISP : https://wisp.gg +// Submitted by Stepan Fedotov +panel.gg +daemon.panel.gg + +// Wix.com, Inc. : https://www.wix.com +// Submitted by Shahar Talmi / Alon Kochba +wixsite.com +wixstudio.com +editorx.io +wixstudio.io +wix.run + +// Wizard Zines : https://wizardzines.com +// Submitted by Julia Evans +messwithdns.com + +// WoltLab GmbH : https://www.woltlab.com +// Submitted by Tim Düsterhus +woltlab-demo.com +myforum.community +community-pro.de +diskussionsbereich.de +community-pro.net +meinforum.net + +// Woods Valldata : https://www.woodsvalldata.co.uk/ +// Submitted by Chris Whittle +affinitylottery.org.uk +raffleentry.org.uk +weeklylottery.org.uk + +// WP Engine : https://wpengine.com/ +// Submitted by Michael Smith +// Submitted by Brandon DuRette +wpenginepowered.com +js.wpenginepowered.com + +// XenonCloud GbR: https://xenoncloud.net +// Submitted by Julian Uphoff +half.host + +// XnBay Technology : http://www.xnbay.com/ +// Submitted by XnBay Developer +xnbay.com +u2.xnbay.com +u2-local.xnbay.com + +// XS4ALL Internet bv : https://www.xs4all.nl/ +// Submitted by Daniel Mostertman +cistron.nl +demon.nl +xs4all.space + +// Yandex.Cloud LLC : https://cloud.yandex.com +// Submitted by Alexander Lodin +yandexcloud.net +storage.yandexcloud.net +website.yandexcloud.net + +// YesCourse Pty Ltd : https://yescourse.com +// Submitted by Atul Bhouraskar +official.academy + +// Yola : https://www.yola.com/ +// Submitted by Stefano Rivera +yolasite.com + +// Yombo : https://yombo.net +// Submitted by Mitch Schwenk +yombo.me + +// Yunohost : https://yunohost.org +// Submitted by Valentin Grimaud +ynh.fr +nohost.me +noho.st + +// ZaNiC : http://www.za.net/ +// Submitted by registry +za.net +za.org + +// ZAP-Hosting GmbH & Co. KG : https://zap-hosting.com +// Submitted by Julian Alker +zap.cloud + +// Zeabur : https://zeabur.com/ +// Submitted by Zeabur Team +zeabur.app + +// Zine EOOD : https://zine.bg/ +// Submitted by Martin Angelov +bss.design + +// Zitcom A/S : https://www.zitcom.dk +// Submitted by Emil Stahl +basicserver.io +virtualserver.io +enterprisecloud.nu + +// ===END PRIVATE DOMAINS=== diff --git a/etc/test.sqlite.sql b/etc/test.sqlite.sql new file mode 100644 index 0000000..f7409d8 --- /dev/null +++ b/etc/test.sqlite.sql @@ -0,0 +1,123 @@ +-- 1 up + +CREATE TABLE IF NOT EXISTS metrics ( + metric_id INTEGER PRIMARY KEY AUTOINCREMENT, + year INT, + month INT, + day INT, + hour INT, + metric TEXT NOT NULL, + value NUMERIC (6,2) +); + +-- +-- regex stores the regular expresion used by flair engine +-- + +CREATE TABLE IF NOT EXISTS regex ( + regex_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + name TEXT NOT NULL, + description TEXT NOT NULL, + match TEXT NOT NULL UNIQUE, + entity_type TEXT NOT NULL, + regex_type TEXT CHECK(regex_type in ('core', 'udef')) NOT NULL, + re_order INT, + multiword BOOLEAN +); + +CREATE TRIGGER [update_regex_updated] + AFTER UPDATE ON regex FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE regex SET updated=CURRENT_TIMESTAMP WHERE regex_id=NEW.regex_id; +END; + + +-- +-- The Files table keeps track of the files created by imgmunger +-- + +CREATE TABLE IF NOT EXISTS files ( + file_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + filename TEXT NOT NULL, + dir TEXT NOT NULL +); + +CREATE TRIGGER [update_files_updated] + AFTER UPDATE ON files FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE files SET updated=CURRENT_TIMESTAMP WHERE file_id=NEW.file_id; +END; + + +-- +-- keep track of apikeys, how to access the api +-- +CREATE TABLE IF NOT EXISTS apikeys ( + apikey_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + username TEXT NOT NULL, + key TEXT NOT NULL, + lastaccess TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + flairjob BOOLEAN, -- create a flair job and query and read results + regex_ro BOOLEAN, -- read only regexes + regex_crud BOOLEAN, -- full control regexes + metrics BOOLEAN -- read metrics +); + +CREATE TRIGGER [update_apikeys_updated] + AFTER UPDATE ON apikeys FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE apikeys SET updated=CURRENT_TIMESTAMP WHERE apikey_id=NEW.apikey_id; +END; + +INSERT INTO apikeys VALUES (null, null, 'flairtest','flairtest123', null, true, true, true, true); + +-- +-- admins can access the web interface and api +-- +CREATE TABLE IF NOT EXISTS admins ( + admin_id INTEGER PRIMARY KEY AUTOINCREMENT, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + username TEXT NOT NULL, + who TEXT NOT NULL, + lastlogin TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + lastaccess TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + pwhash TEXT NOT NULL +); + +CREATE TRIGGER [update_admins_updated] + AFTER UPDATE ON admins FOR EACH ROW + WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +BEGIN + UPDATE admins SET updated=CURRENT_TIMESTAMP WHERE admin_id=NEW.admin_id; +END; + +-- CREATE TABLE IF NOT EXISTS jobs ( +-- job_id INTEGER PRIMARY KEY AUTOINCREMENT, +-- updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP, +-- duration DECIMAL(6,4) DEFAULT 0, imgduration DECIMAL(6,4) DEFAULT 0, +-- sourcelen INT DEFAULT 0, +-- images INT DEFAULT 0, +-- entities INT DEFAULT 0 +-- ); +-- +-- CREATE TRIGGER [update_jobs_updated] +-- AFTER UPDATE ON jobs FOR EACH ROW +-- WHEN OLD.updated = NEW.updated OR OLD.updated IS NULL +-- BEGIN +-- UPDATE jobs SET updated=CURRENT_TIMESTAMP WHERE job_id=NEW.job_id; +-- END; + +--1 down +DROP TABLE IF EXISTS jobs; +DROP TABLE IF EXISTS regex; +DROP TABLE IF EXISTS status; +DROP TABLE IF EXISTS metrics; +DROP TABLE IF EXISTS files; +DROP TABLE IF EXISTS apikeys; +DROP TABLE IF EXISTS admins; diff --git a/etc/testlog.conf b/etc/testlog.conf new file mode 100644 index 0000000..aa71e23 --- /dev/null +++ b/etc/testlog.conf @@ -0,0 +1,7 @@ +log4perl.category.Flair = TRACE, FlairLog +log4perl.appender.FlairLog = Log::Log4perl::Appender::File +log4perl.appender.FlairLog.mode = append +log4perl.appender.FlairLog.filename = /var/log/flair/test.log +log4perl.appender.FlairLog.layout = Log::Log4perl::Layout::PatternLayout +log4perl.appender.FlairLog.layout.ConversionPattern = %d %7p [%P] %15F{1}: %4L %m%n + diff --git a/lib/Flair.pm b/lib/Flair.pm new file mode 100644 index 0000000..60d27d8 --- /dev/null +++ b/lib/Flair.pm @@ -0,0 +1,196 @@ +package Flair; + +use Mojo::Base 'Mojolicious', -signatures; +use Mojo::File qw(curfile); +use MojoX::Log::Log4perl; +use Flair::Util::CSRFProtection; +use Flair::Db; +use Flair::Processor; +use Flair::ScotApi; +use Flair::Config qw(build_config); +use Data::Dumper::Concise; +use Carp qw(cluck longmess shortmess); + +## +## This module starts the WEB api +## + +sub startup ($self) { + + # Load configuration from config vars + my $config_href = build_config(); + $self->plugin('Config' => {default => $config_href}); + + # set up logging + my $logconf = $config_href->{log}->{config}; + $self->log(MojoX::Log::Log4perl->new(\$logconf)); + $self->log->info("Starting Flair Web ..."); + + # catch errors and put them in log + $self->catch_error_setup(); + + # Configure the application + $self->mode($config_href->{mode}); + $self->secrets($config_href->{secrets}); + $self->sessions->default_expiration($config_href->{default_expiration}); + $self->sessions->secure(1); + + # configure plugins + $self->plugin('TagHelpers'); + $self->plugin('DefaultHelpers'); + $self->plugin('RemoteAddr'); + $self->plugin('Flair::Util::CSRFProtection'); + + + my $db = Flair::Db->new(log => $self->log, config => $config_href->{database}); + $self->helper('db' => sub { $db } ); + $self->attr ('db' => sub {$db} ); + # + # Minion admin might be unprotected so add this + my $minion_auth = $self->routes->under('/minion')->to('controller-auth#check'); + + my $mindb = $db->minion_backend; + $self->plugin('Minion' => $mindb); + $self->plugin('Minion::Admin' => {route => $minion_auth}); + $self->plugin('Mojolicious::Plugin::DataTables'); + + my $cache = Mojo::Cache->new(max_keys => 100); + $self->helper('cache' => sub { $cache }); + + # server settings + $self->max_request_size($config_href->{max_request_size}); + $| = 1; + + # create a scotapi instance for interaction with the SCOT api + my $scotapi = Flair::ScotApi->new(log => $self->log, config => $self->config->{scotapi}); + + # create flair task + # (must create here and not in controller) + $self->minion->add_task("flair_it" => sub ($job, @args) { + $self->log->debug("Inside flair_it"); + my $processor = Flair::Processor->new( + log => $self->log, db => $db, scotapi => $scotapi, config => $self->config, + ); + $processor->do_flairing($job, @args); + }); + + # this is most likely behind a reverse proxy especiallly in a kubernetes/docker env + # so set the env var MOJO_REVERSE_PROXY to the path prefeix (likely /scot-flair) + # see https://mojolicious.io/blog/2019/03/18/reverse-proxy-with-path/ + + if ( my $path = $ENV{MOJO_REVERSE_PROXY} ) { + my @parts = grep /\S/, split m{/}, $path; + $self->hook(before_dispatch => sub { + my ($c) = @_; + my $url = $c->req->url; + my $base = $url->base; + push @{ $base->path }, @parts; + $base->path->trailing_slash(1); + $url->path->leading_slash(0); + }); + } + + $self->log->debug("Building Routes..."); + # Router + my $r = $self->routes; + + + # authentication routes + $r->post('/auth')->with_csrf_protection->to('controller-auth#auth')->name('auth'); + $r->any('/login')->to('controller-auth#login')->name('login'); + $r->any('/logout')->to('controller-auth#logout')->name('logout'); + + + # set up auth handler + my $auth = $r->under('/')->to('controller-auth#check'); + # allow static index.html without auth + $r->get('/')->to(cb => sub ($c) { $c->reply->static('index.html'); }); + + $self->plugin('OpenAPI' => { + # load the api specification + url => $self->home->rel_file('public/api.yaml'), + # make sure openapi paths are authenticated/authorized + route => $auth, + }); + + $self->plugin('SwaggerUI' => { + route => $self->routes->any('/swagger'), + url => 'api.yaml', + }); + + # make routes with /flair authenticated + my $flair = $auth->any('/flair'); + + $flair->any('/status') ->to('controller-metrics#status')->name('status'); + $flair->any('/stream_status')->to('controller-metrics#stream_status')->name('stream_status'); + + # datatable display routes + # /flair/dt/* + my $dt = $flair->any('/dt'); + $dt->get ('/regex') ->to('controller-regex#dt') ->name('regex_dt'); + $dt->get ('/regex/ajax') ->to('controller-regex#dt_ajax') ->name('regex_dt_ajax'); + $dt->get ('/apikeys') ->to('controller-apikeys#dt') ->name('apikeys_dt'); + $dt->get ('/apikeys/ajax')->to('controller-apikeys#dt_ajax') ->name('apikeys_dt_ajax'); + $dt->get ('/metrics') ->to('controller-metrics#dt') ->name('metrics_dt'); + $dt->get ('/metrics/ajax')->to('controller-metrics#dt_ajax') ->name('metrics_dt_ajax'); + $dt->get ('/admins') ->to('controller-admins#dt') ->name('admins_dt'); + $dt->get ('/admins/ajax') ->to('controller-admins#dt_ajax') ->name('admins_dt_ajax'); + + # editor display routes + # /flair/edit/* + my $edit = $flair->any('/edit'); + $edit->get ('/regex/:RegexId') ->to('controller-regex#edit') ->name('regex_edit'); + $edit->get ('/apikeys/:ApikeyId')->to('controller-apikeys#edit') ->name('apikeys_edit'); + $edit->get ('/metrics/:MetricId')->to('controller-metrics#edit') ->name('metrics_edit'); + $edit->get ('/admins/:AdminId') ->to('controller-admins#edit') ->name('admins_edit'); + + # new item display routes + # /flair/new/* + my $new = $flair->any('/new'); + $new->get ('/regex') ->to('controller-regex#newregex') ->name('regex_new'); + $new->get ('/apikeys') ->to('controller-apikeys#newitem') ->name('apikeys_new'); + $new->get ('/metrics') ->to('controller-metrics#newitem') ->name('metrics_new'); + $new->get ('/admins') ->to('controller-admins#newitem') ->name('admins_new'); + + + $self->log_server_startup(); +} + +sub log_server_startup ($self) { + my $spaces = " "x55; + my $pwd = `pwd`; + chomp($pwd); + my $inc_sep = "\n" . "| : "; + my $inc = join($inc_sep, @INC); + + my @clines = split("\n",Dumper($self->config)); + my $cfg = join("\n| ","| ", @clines); + my $message = join("\n","", + "==========================================================", + "| SCOT FLAIR Server", + "| FLAIR : ".$self->config->{version}, + "| Perl : ".$], + "| MOJO : ".$Mojolicious::VERSION, + "| Mode : ".$self->mode, + "| DB : ".$self->config->{database}->{uri}, + "| INC : ".$inc, + # "| MOJO_REVERSE_PROXY : ".$ENV{MOJO_REVERSE_PROXY}, + "| CONFIG: ", $cfg, + "==========================================================", + ); + $self->log->info($message); +} + +sub catch_error_setup ($self) { + $SIG{'__DIE__'} = sub { + if ( $^S ){ + # in eval, don't log, catch later + return; + } + $Log::Log4perl::caller_depth++; + $self->log->error(longmess()); + $self->log->fatal(@_); + die @_; + }; +} +1; diff --git a/lib/Flair/Config.pm b/lib/Flair/Config.pm new file mode 100644 index 0000000..f023cf3 --- /dev/null +++ b/lib/Flair/Config.pm @@ -0,0 +1,137 @@ +package Flair::Config; + +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(build_config set_default); + +use strict; +use warnings; +use experimental 'signatures'; +use Data::Dumper::Concise; +use Mojo::File qw(path curfile); +use Mojo::Util qw(decode); + +# $ENV is perl special variable to access the shell environment +# flair expects to run in a k3s orchestrated container with helm providing +# the env vars + +sub set_default ($varname, $value) { + # set the environment var to a default value if it doesnt exist + if (! defined $ENV{$varname}) { + $ENV{$varname} = $value; + } +} + +# build a config +# based on env vars from helm charts + +sub build_config () { + set_default('S4FLAIR_VERSION', '1.1'); + # set mode to 'development' to get more descriptive errors in web results + set_default('S4FLAIR_MODE', 'production'); + # set to INFO or higher to dramatically reduce logging + set_default('S4FLAIR_LOG_LEVEL', 'DEBUG'); + set_default('S4FLAIR_LOG_DIR', '/opt/flair/var/log'); + set_default('S4FLAIR_LOG_FILE', 'flair.log'); + set_default('S4FLAIR_MOJO_LISTEN', 'http://localhost:3001?reuse=1'); + set_default('S4FLAIR_MOJO_WORKERS', 5); + # re-authentication timeout for web app + set_default('S4FLAIR_DEFAULT_EXP_TIME', 60*60*4); + # sqlite3 file for storing persistent data + set_default('S4FLAIR_DB_URI', 'file:/opt/flair/var/flair.db'); + # admin user at start + set_default('S4FLAIR_ADMIN_USER', 'flairadmin'); + set_default('S4FLAIR_ADMIN_PASS', 'flairrox!'); + set_default('S4FLAIR_ADMIN_GECOS', 'Flair Admin Entity'); + # where flair engine is installed + set_default('S4FLAIR_HOME_DIR', '/opt/flair'); + # owner and group of the flair engine files + set_default('S4FLAIR_FLAIR_USER', 'flair'); + set_default('S4FLAIR_FLAIR_GROUP', 'flair'); + # setup the sqlite db + set_default('S4FLAIR_DB_MIGRATION', '/opt/flair/etc/flair.sqlite.sql'); + set_default('S4FLAIR_DB_FILE', '/opt/flair/var/flair.db'); + # loads the core regexes + set_default('S4FLAIR_CORE_REGEXES', '/opt/flair/etc/core_regexes.pl'); + # loads user defined regexes + set_default('S4FLAIR_UDEF_REGEXES', '/opt/flair/etc/udef_regexes.pl'); + # if your scot api server is using an expired or invalid cert + set_default('S4FLAIR_SCOT_API_INSECURE_SSL', 0); + # the key to submit back to the scot api + set_default('S4FLAIR_SCOT_API_KEY', 'xxxxxx'); + # uri base + set_default('S4FLAIR_SCOT_API_URI_ROOT', 'https://scot4/api/v1'); + + my $logfile = join('/', $ENV{S4FLAIR_LOG_DIR}, $ENV{S4FLAIR_LOG_FILE}); + + my $config = { + version => $ENV{S4FLAIR_VERSION}, + mode => $ENV{S4FLAIR_MODE}, + install => { + dbfile => $ENV{S4FLAIR_DB_FILE}, + admin_user => $ENV{S4FLAIR_ADMIN_USER}, + admin_pass => $ENV{S4FLAIR_ADMIN_PASS}, + admin_gecos => $ENV{S4FLAIR_ADMIN_GECOS}, + instdir => $ENV{S4FLAIR_HOME_DIR}, + flair_user => $ENV{S4FLAIR_FLAIR_USER}, + flair_group => $ENV{S4FLAIR_FLAIR_GROUP}, + db_migration => $ENV{S4FLAIR_DB_MIGRATION}, + core_regexes_file => $ENV{S4FLAIR_CORE_REGEXES}, + udef_regexes_file => $ENV{S4FLAIR_UDEF_REGEXES}, + }, + log => { + name => 'Flair', + config => qq{ +log4perl.category.Flair = $ENV{S4FLAIR_LOG_LEVEL}, FlairLog +log4perl.appender.FlairLog = Log::Log4perl::Appender::File +log4perl.appender.FlairLog.mode = append +log4perl.appender.FlairLog.filename = $logfile +log4perl.appender.FlairLog.layout = Log::Log4perl::Layout::PatternLayout +log4perl.appender.FlairLog.layout.ConversionPattern = %d %7p [%P] %15F{1}: %4L %m%n + }, + }, + hypnotoad => { + listen => [ $ENV{S4FLAIR_MOJO_LISTEN} ], + workers => $ENV{S4FLAIR_MOJO_WORKERS}, + clients => 1, + proxy => 1, + pidfile => '/opt/flair/var/run/flair.hypno.pid', + heartbeat_timeout => 90, + }, + secrets => [qw(l1nd@ 5yd3ny m@dd0x br00ke)], + default_expiration => $ENV{S4FLAIR_DEFAULT_EXP_TIME}, + flair_api_key => $ENV{S4FLAIR_FLAIR_API_KEY}, + database => { + dbtype => 'sqlite', + uri => $ENV{S4FLAIR_DB_URI}, + backend => 'sqlite:'.$ENV{S4FLAIR_DB_FILE}, + migration => $ENV{S4FLAIR_DB_MIGRATION}, + model => { + regex => { + default_list_options => { + fields => ['*'], + where => [], + order => [ '-id' ], + limit => 50, + offset => 0, + }, + default_fetch_options => { + fields => ['*'], + where => [], + order => [ '-id' ], + limit => 1, + offset => 0, + }, + }, + }, + }, + scotapi => { + insecure => $ENV{S4FLAIR_SCOT_API_INSECURE_SSL}, + api_key => $ENV{S4FLAIR_SCOT_API_KEY}, + uri_root => $ENV{S4FLAIR_SCOT_API_URI_ROOT}, + }, + }; + return $config; +} + +1; diff --git a/lib/Flair/Controller/Admins.pm b/lib/Flair/Controller/Admins.pm new file mode 100644 index 0000000..0d8c4f1 --- /dev/null +++ b/lib/Flair/Controller/Admins.pm @@ -0,0 +1,118 @@ +package Flair::Controller::Admins; + +use lib '../../../lib'; +use strict; +use warnings; +use Data::Dumper::Concise; +use Crypt::PBKDF2; +use Mojo::Base 'Flair::Controller::Api', -signatures; +use Flair::Util::Crypt qw(hash_pass); + +sub default_sort { + return { -desc => 'admin_id' }; +} + +sub create ($self) { + $self->log->debug("Create"); + $self->openapi->valid_input or return $self->invalid_input; + my $json = $self->req->json; + my $raw = $json->{pwhash}; + $json->{pwhash} = hash_pass($raw); + my $href = $self->db->admins->create($json); + $self->render(status => 201, openapi => $href); +} + +sub list ($self) { + $self->openapi->valid_input or return; + my $result = $self->get_list(); + $self->render(status => 200, openapi => $result); +} + +sub get_list ($self) { + my $json = $self->req->json; + my $params = $self->req->params->to_hash; + my $opts = $self->build_list_opts($params); + return $self->db->admins->list($opts); +} + +sub fetch ($self) { + $self->openapi->valid_input or return; + my $result = $self->get_one; + $self->render(status => 200, openapi => $result); +} + +sub get_one ($self) { + my $json = $self->req->json; + my $id = $self->stash('AdminId'); + return $self->db->admins->fetch($id); +} + +sub update ($self) { + $self->openapi->valid_input or return; + my $json = $self->req->json; + my $id = $self->stash('AdminId'); + my $result = $self->db->admins->update($id, $json); + $self->render(status => 200, openapi => $result); +} + +sub patch ($self) { + $self->openapi->valid_input or return; + my $json = $self->req->json; + my $id = $self->stash('AdminId'); + my $result = $self->db->admins->patch($id, $json); + $self->render(status => 200, openapi => $result); +} + +sub delete ($self) { + $self->openapi->valid_input or return; + my $json = $self->req->json; + my $id = $self->stash('AdminId'); + my $result = $self->db->admins->delete($id); + $self->render(status => 200, openapi => $result); +} + +sub count ($self) { + $self->openapi->valid_input or return; + my $json = $self->req->json; + my $result = $self->db->admins->count($json); + $self->render(status => 200, openapi => $result); +} + +sub display ($self) { + return $self->render(result => $self->get_list); +} + +sub edit ($self) { + return $self->render(result => $self->get_one); +} + +sub newitem ($self) { + return $self->render(); +} + +sub dt ($self) { + return $self->render(); +} + +sub dt_ajax ($self) { + my $id_formatter = sub ($value, $column) { + return ''.$value.''; + }; + my $db = $self->db->dbh; + my $ssp = $self->datatable->ssp( + table => 'admins', + sql => $db, + columns => [qw(admin_id updated username who lastlogin lastaccess pwhash)], + options => [ + {label => 'AdminId', db => 'admin_id', dt => 0, formatter => $id_formatter }, + {label => 'Updated', db => 'updated', dt => 1, }, + {label => 'Username', db => 'username', dt => 2, }, + {label => 'Who', db => 'who', dt => 3, }, + {label => 'LastLogin', db => 'lastlogin', dt => 4, }, + {label => 'LastAccess', db => 'lastaccess', dt => 5, }, + {label => 'PWHash', db => 'pwhash', dt => 6, }, + ], + ); + return $self->render(json => $ssp); +} +1; diff --git a/lib/Flair/Controller/Api.pm b/lib/Flair/Controller/Api.pm new file mode 100644 index 0000000..b05c878 --- /dev/null +++ b/lib/Flair/Controller/Api.pm @@ -0,0 +1,84 @@ +package Flair::Controller::Api; + +use strict; +use warnings; +use Try::Tiny; +use Data::Dumper::Concise; +use Mojo::JSON qw(decode_json); +use Mojo::Base 'Mojolicious::Controller', -signatures; + +sub invalid_input ($self) { + $self->log->error("INVALID INPUT"); + $self->flash(error => "Invalid Input"); +} + + +sub get_request_params ($self) { + + my $mojo_req = $self->req; + my $params = $mojo_req->params->to_hash; + my $json = $mojo_req->json; + + if ( $params ) { + foreach my $key (keys %$params) { + my $parsed = try { + # if this param is encoded json, get it and decode + decode_json($params->{$key}); + } + catch { + # otherwise, keep the value as is + return $params->{$key}; + }; + $params->{$key} = $parsed; + } + } + + my $r = { + id => $self->stash('id'), + user => $self->session('user'), + data => { + params => $params, + json => $json, + }, + }; + $self->log->trace("REQUEST =",{filter=>\&Dumper, value => $r}); + return $r; +} + +sub build_list_opts ($self, $href) { + my $opts = {}; + + $opts->{fields} = $self->build_fields($href->{fields}); + $opts->{where} = $self->build_where($href->{where}); + $opts->{order} = $self->build_order($href->{order}); + $opts->{limit} = $self->build_limit($href->{limit}); + $opts->{offset} = $self->build_offset($href->{offset}); + return $opts; +} + +sub build_fields ($self, $fields) { + return $fields if (defined $fields and ref($fields) eq "ARRAY"); + return ['*']; +} + +sub build_where ($self, $where) { + return $where if (defined $where and ref($where) eq "HASH"); + return {}; +} + +sub build_order ($self, $order) { + return $order if (defined $order and ref($order) eq "ARRAY"); + return $self->default_sort; +} + +sub build_limit ($self, $limit) { + return $limit if (defined $limit); + return 50; +} + +sub build_offset ($self, $offset) { + return $offset if (defined $offset); + return 0; +} + +1; diff --git a/lib/Flair/Controller/Apikeys.pm b/lib/Flair/Controller/Apikeys.pm new file mode 100644 index 0000000..a389d2c --- /dev/null +++ b/lib/Flair/Controller/Apikeys.pm @@ -0,0 +1,208 @@ +package Flair::Controller::Apikeys; + +use lib '../../../lib'; +use strict; +use warnings; +use Data::Dumper::Concise; +use Data::GUID; +use Try::Tiny; +use Mojo::Base 'Flair::Controller::Api', -signatures; +use Carp; + +sub default_sort { + return { -desc => 'apikey_id' }; +} + +sub create ($self) { + + $self->log->debug("in controller create"); + + my $db = $self->db; + + # validate input, return error if not valid + $self->openapi->valid_input or return $self->invalid_input; + + my $json = $self->req->json; + if (! defined $json->{apikey}) { + # generate an apikey + my $guid = Data::GUID->new; + my $apikey = $guid->as_string; + $json->{apikey} = $apikey; + } + + $self->log->debug({filter=>\&Dumper, value => $json}); + + my $href = $self->db->apikeys->create($json); + + # set status and allow openapi to validate output + $self->render( + status => 201, + openapi => $href + ); +} + +sub list ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $result = $self->get_list(); + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub get_list ($self) { + my $json = $self->req->json; + my $params = $self->req->params->to_hash; + my $opts = $self->build_list_opts($params); + + return $self->db->apikeys->list($opts); +} + +sub fetch ($self) { + # validate input, return error if not valid + $self->openapi->valid_input or return; + my $result = $self->get_one(); + $self->render( + status => 200, + openapi => $result, + ); +} + +sub get_one ($self) { + my $json = $self->req->json; + my $id = $self->stash('ApikeyId'); + return $self->db->apikeys->fetch($id); +} + +sub update ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('ApikeyId'); + + my $result = try { + $self->db->apikeys->update($id, $json); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); + +} + +sub patch ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('ApikeyId'); + + my $result = try { + $self->db->apikeys->patch($id, $json); + } + catch { + die "ERROR: $_"; + }; + $self->render( + status => 200, + openapi => $result, + ); +} + +sub delete ($self) { + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('ApikeyId'); + + my $result = try { + $self->db->apikeys->delete($id); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub count ($self) { + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $result = try { + $self->db->apikeys->count($json); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub display ($self) { + return $self->render(result => $self->get_list); +} + +sub edit ($self) { + return $self->render(result => $self->get_one); +} + +sub newitem ($self) { + return $self->render(); +} + +sub dt ($self) { + $self->log->debug("dt for apikeys"); + return $self->render(); +} + +sub dt_ajax ($self) { + $self->log->debug("dt_ajax for apikeys"); + my $id_formatter = sub ($value, $column) { + return ''.$value.''; + }; + my $bool_formatter = sub ($value, $columns) { + return ($value) ? 'Allowed' : 'Disallowed'; + }; + my $db = $self->db->dbh; + my $ssp = $self->datatable->ssp( + table => 'apikeys', + sql => $db, + columns => [ + qw(apikey_id updated username apikey lastaccess flairjob regex_ro regex_crud metrics) + ], + options => [ + { label => 'ApikeyId', db => 'apikey_id', dt => 0, formatter => $id_formatter }, + { label => 'Updated', db => 'updated', dt => 1 }, + { label => 'Username', db => 'username', dt => 2 }, + { label => 'Apikey', db => 'apikey', dt => 3 }, + { label => 'LastAccess', db => 'lastaccess', dt => 4 }, + { label => 'flairjob', db => 'flairjob', dt => 5, formatter => $bool_formatter }, + { label => 'regex_ro', db => 'regex_ro', dt => 6, formatter => $bool_formatter }, + { label => 'regex_crud', db => 'regex_crud', dt => 7, formatter => $bool_formatter }, + { label => 'metrics', db => 'metrics', dt => 8, formatter => $bool_formatter }, + ], + ); + return $self->render(json => $ssp); +} + + +1; diff --git a/lib/Flair/Controller/Auth.pm b/lib/Flair/Controller/Auth.pm new file mode 100644 index 0000000..cbcf40d --- /dev/null +++ b/lib/Flair/Controller/Auth.pm @@ -0,0 +1,184 @@ +package Flair::Controller::Auth; +use Mojo::Base 'Mojolicious::Controller', -signatures; +use Crypt::PBKDF2; +use Data::Dumper::Concise; +use lib '../../../lib'; +use Flair::Util::Crypt qw(compare_pass); +use Storable qw(dclone); + +sub check ($self) { + # testing + # return 1; + $self->log->debug("Checking Authentication..."); + # look for mojolicious session + if ( my $user = $self->valid_mojo_session ) { + return $self->sucessful_session($user); + } + + # look for APIKEY + if ( my $apiuser = $self->valid_header ) { + return $self->sucessful_header($apiuser); + } + + # everything failed, no auth for your + return $self->failed_auth('invalid session or apikey'); +} + +sub sucessful_session ($self, $user) { + $self->db->admins->set_lastaccess($user); + $self->log_request($user); + return 1; +} + +sub sucessful_header ($self, $apiuser) { + $self->db->apikeys->set_lastaccess($apiuser); + $self->log_request($apiuser); + return 1; +} + +sub login ($self) { + # generate the login form + $self->render(orig_url => $self->session('orig_url')); +} + +sub logout ($self) { + $self->session(user => ''); + $self->render( + status => 200, + orig_url => '/', + ); +} + +sub auth ($self) { + + my $user = $self->param('user'); + my $pass = $self->param('pass'); + + $self->log->debug("User $user is attempting to auth"); + + # delete leading and trailing spaces from both + $user =~ s/^\s+(\w+)\s+$/$1/; + $pass =~ s/^\s+(\w+)\s+$/$1/; + + my $admin = $self->db->admins->get_admin($user); + return $self->failed_auth("User $user not found") unless $admin; + + if ( $self->hash_match($admin->{pwhash}, $pass) ) { + return $self->sucessful_auth($user, $self->session('orig_url')); + } + $self->failed_auth("$user password mismatch"); +} + +sub hash_match ($self, $hash, $attempt) { + return compare_pass($hash, $attempt); +} + +sub valid_mojo_session ($self) { + my $user = $self->session('user'); + if (defined $user) { + $self->log->info("User: $user , Method: valid session cookie"); + return $user; + } + $self->log->info("missing session cookie"); + return undef; +} + +sub valid_header ($self) { + my $headers = $self->req->headers; + my $header = $headers->header('authorization'); + $self->log->debug("Authorization header = $header"); + + unless (defined $header) { + $self->log->info("No authorization header present"); + return undef; + } + + my ($type, $value) = split(/ /, $header, 2); + return $self->validate_header($type, $value); +} + +sub validate_header ($self, $type, $value) { + return $self->validate_basic($value) if ( $type eq "basic" ); + $self->log->debug("not basic"); + return $self->validate_apikey($value) if ( $type eq "apikey" ); + $self->log->debug("not apikey"); + return undef; +} + +sub validate_basic ($self, $value) { + my $decoded = decode_base64($value); + my ($user, $pass) = split(/:/, $decoded, 2); + my $attempted_pass = $self->generate_pbkdf($pass); + my $userpwhash = $self->get_user_pwhash($user); + + if ($self->hash_match($userpwhash, $attempted_pass)) { + $self->log->info("User: $user Method: basic auth"); + return $user; + } + $self->log->debug("Failed basic auth"); + return undef; +} + +sub get_user_pwhash ($self, $user) { + my $record = $self->db->admins->get_admin($user); + if ( defined $record ) { + return $record->{pwhash}; + } + $self->log->info("User: $user is invalid"); + return undef; +} + +sub validate_apikey ($self, $value) { + my $apikey = $self->db->apikeys->get_key($value); + if (defined $apikey) { + return $apikey->{username}; + } + $self->log->debug("Failed apikey validation"); + return undef; +} + +sub sucessful_auth ($self, $user, $orig_url=undef) { + $self->log->info("User $user sucessfully authenticated from: ". $self->remote_addr); + $self->db->admins->set_lastlogin($user); + $self->session( + user => $user, + secure => 1, + expiration => 3600 * 4, # must re-auth in four hours + ); + $orig_url = "/" unless $orig_url; + $self->redirect_to($orig_url); + return 1; +} + +sub failed_auth ($self, $reason) { + $self->log_request("Failed Auth"); + $self->log->error("Failed Auth Attempt from ".$self->remote_addr.". $reason"); + $self->redirect_to("/login"); + return undef; +} + +sub log_request ($self, $user=undef) { + my @plines = map { " "x20 . qq|--- |. $_ } split("\n",Dumper($self->req->params->to_hash)); + my $json = $self->req->json; + my $json_no_data = (! defined $json or ! ref($json)) ? + { data => {} } : + dclone($json); + + delete $json_no_data->{data}; + my @jlines = map { " "x20 . qq|--- |. $_ } split("\n",Dumper($json_no_data)); + my $msg = join("\n", + qq|----------- REQUEST ----------|, + " "x20 . qq|--- Route: |.$self->url_for, + " "x20 . qq|--- Name: |.$self->current_route, + " "x20 . qq|--- Params|, + @plines, + " "x20 . qq|--- JSON|, + @jlines, + " "x20 . qq|--- user : $user|, + " "x20 . qq|--- ipaddr: |.$self->remote_addr, + " "x20 . qq|---------------------|, + ); + $self->log->debug($msg); +} + +1; diff --git a/lib/Flair/Controller/FlairJob.pm b/lib/Flair/Controller/FlairJob.pm new file mode 100644 index 0000000..86da1c2 --- /dev/null +++ b/lib/Flair/Controller/FlairJob.pm @@ -0,0 +1,72 @@ +package Flair::Controller::FlairJob; + +use Data::Dumper::Concise; +use lib '../../../lib'; +use Mojo::Base 'Flair::Controller::Api', -signatures; +use Flair::Processor; + + +sub default_sort { + return { -desc => 'flairjob_id' }; +} + +sub create ($self) { + + # create a minion flair job and return the job id + + # validate input, return error if not valid + # $self->openapi->valid_input or return; + if ( ! $self->openapi->valid_input ) { + $self->log->error("Input from API is invalid"); + return; + } + my $input = $self->req->json; + $self->log->trace("create flair job ", { filter => \&Dumper, value =>$input}); + + $self->log->debug("creating processor..."); + #$self->minion->add_task("flair_it" => sub ($job, @args) { + # $job->app->log->debug("INSIDE FLAIR_IT TASK"); + # $self->log->debug("inside flair_it task"); + # my $proc = Flair::Processor->new(log => $self->log, db => $self->db); + # $proc->do_flairing($job, @args); + #}); + my $id = $self->minion->enqueue('flair_it', [$input]); + $self->log->debug("Minion Job id = $id enqueued"); + + # set status and allow openapi to validate output + $self->render( + status => 202, + json => { job_id => $id }, + ); +} + +sub list ($self) { + $self->log->debug("listing jobs"); + # TODO: define filters ids=>[], notes=>[], queues=>[], states=>[], etc. + my $jobs = $self->minion->jobs(); + my @results = (); + while (my $job = $jobs->next) { + push @results, $job; + } + # $self->log->debug("Results ", {filter=>\&Dumper, value => \@results}); + $self->render( + status => 200, + json => \@results, + ); +} + +sub fetch ($self) { + $self->openapi->valid_input or return; + my $id = $self->stash('FlairJobId'); + my $job = $self->minion->job($id); + return $self->reply->not_found unless $job; + + $self->log->debug("job $id results ",{filter=>\&Dumper, value=>$job->info}); + + $self->render( + status => 200, + json => $job->info->{result} + ); +} + +1; diff --git a/lib/Flair/Controller/Metrics.pm b/lib/Flair/Controller/Metrics.pm new file mode 100644 index 0000000..1f54761 --- /dev/null +++ b/lib/Flair/Controller/Metrics.pm @@ -0,0 +1,199 @@ +package Flair::Controller::Metrics; + +use lib '../../../lib'; +use strict; +use warnings; +use Data::Dumper::Concise; +use Try::Tiny; +use Mojo::Base 'Flair::Controller::Api', -signatures; + +sub default_sort { + return { -desc => 'metric_id' }; +} + +sub create ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $href = $self->db->metrics->create($json); + + # set status and allow openapi to validate output + $self->render( + status => 201, + openapi => $href + ); +} + +sub list ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + my $result = $self->get_list(); + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub get_list ($self) { + my $json = $self->req->json; + my $params = $self->req->params->to_hash; + my $opts = $self->build_list_opts($params); + return $self->db->metrics->list($opts); +} + +sub fetch ($self) { + # validate input, return error if not valid + $self->openapi->valid_input or return; + my $result = $self->get_one(); + $self->render( + status => 200, + openapi => $result, + ); +} + +sub get_one ($self) { + my $json = $self->req->json; + my $id = $self->stash('MetricId'); + return $self->db->metrics->fetch($id); +} + +sub update ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('MetricId'); + + my $result = try { + $self->db->metrics->update($id, $json); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); + +} + +sub patch ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('MetricId'); + + my $result = try { + $self->db->metrics->patch($id, $json); + } + catch { + die "ERROR: $_"; + }; + $self->render( + status => 200, + openapi => $result, + ); +} + +sub delete ($self) { + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('MetricId'); + + my $result = try { + $self->db->metrics->delete($id); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub count ($self) { + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $result = try { + $self->db->metrics->count($json); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub display ($self) { + return $self->render(result => $self->get_list); +} + +sub edit ($self) { + return $self->render(result => $self->get_one); +} + +sub newitem ($self) { + return $self->render(); +} + +sub dt ($self) { + return $self->render(); +} + +sub dt_ajax ($self) { + my $id_formatter = sub ($value, $column) { + return ''.$value.''; + }; + my $db = $self->db->dbh; + my $ssp = $self->datatable->ssp( + table => 'metrics', + sql => $db, + columns => [qw(metric_id year month day hour metric value)], + options => [ + {label => 'MetricId', db => 'metric_id', dt => 0, formatter => $id_formatter }, + {label => 'Year', db => 'year', dt => 1, }, + {label => 'Month', db => 'month', dt => 2, }, + {label => 'Day', db => 'day', dt => 3, }, + {label => 'Hour', db => 'hour', dt => 4, }, + {label => 'Metric', db => 'metric', dt => 5, }, + {label => 'Value', db => 'value', dt => 6, }, + ], + ); + return $self->render(json => $ssp); +} + +sub status ($self) { + $self->render(); +} + +sub stream_status ($self) { + my $tx = $self->render_later->tx; + $self->inactivity_timeout(300); + $self->res->headers->content_type('text/event-stream'); + $self->write("event:metrics\ndata:".encode_json($self->get_metrics)."\n\n"); + + no warnings; + my $id = Mojo::IOLoop->recurring(60 => sub { + $tx; + my $json = encode_json($self->get_metrics); + $self->write("event:metrics\ndata:$json\n\n"); + }); +} + +1; diff --git a/lib/Flair/Controller/Regex.pm b/lib/Flair/Controller/Regex.pm new file mode 100644 index 0000000..764cf29 --- /dev/null +++ b/lib/Flair/Controller/Regex.pm @@ -0,0 +1,227 @@ +package Flair::Controller::Regex; + +use lib '../../../lib'; +use strict; +use warnings; +use Data::Dumper::Concise; +use HTML::Entities; +use Try::Tiny; +use Mojo::Base 'Flair::Controller::Api', -signatures; +use Carp qw(longmess); + +sub default_sort { + return { -desc => 'regex_id' }; +} + +sub create ($self) { + $self->log->debug("create!"); + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + try { + my $json = $self->req->json; + my $match = $json->{match}; + my $id = $self->db->regex->regex_exists($match); + + if ( ! $id ) { + my $href = $self->db->regex->create($json); + $self->log->debug("href = ",{filter=>\&Dumper, value => $href}); + + # set status and allow openapi to validate output + $self->render( + status => 201, + openapi => $href + ); + } + else { + $self->log->debug("Regex exists: $id"); + $self->render( + status => 409, + json => {regex_id => $id}, + ); + } + } + catch { + $self->log->error(longmess); + }; + return; +} + +sub list ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $result = $self->get_list(); + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub get_list ($self) { + my $json = $self->req->json; + my $params = $self->req->params->to_hash; + my $opts = $self->build_list_opts($params); + + return $self->db->regex->list($opts); +} + +sub fetch ($self) { + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $result = $self->get_one(); + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub get_one ($self) { + my $json = $self->req->json; + my $id = $self->stash('RegexId'); + $self->log->debug("GET ONE $id"); + return $self->db->regex->fetch($id); +}; + +sub update ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('RegexId'); + + my $result = try { + $self->db->regex->update($id, $json); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); + +} + +sub patch ($self) { + + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('RegexId'); + + my $result = try { + $self->db->regex->patch($id, $json); + } + catch { + die "ERROR: $_"; + }; + $self->render( + status => 200, + openapi => $result, + ); +} + +sub delete ($self) { + # validate input, return error if not valid + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $id = $self->stash('RegexId'); + + my $result = try { + $self->db->regex->delete($id); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub count ($self) { + $self->openapi->valid_input or return; + + my $json = $self->req->json; + my $result = try { + $self->db->regex->count($json); + } + catch { + die "ERROR: $_"; + }; + + $self->render( + status => 200, + openapi => $result, + ); +} + +sub display ($self) { + return $self->render(result => $self->get_list); +} + +sub edit ($self) { + $self->log->debug("edit!"); + return $self->render(result => $self->get_one); +} + +sub newregex ($self) { + $self->log->debug('new!'); + return $self->render(); +} + +sub dt ($self) { + $self->log->debug("datatable sux"); + return $self->render(); +} + +sub dt_ajax ($self) { + $self->log->debug("dt_ajax called"); + my $db = $self->db->dbh; + my $ssp = $self->datatable->ssp( + table => 'regex', + sql => $db, + columns => [qw(regex_id name description match entity_type regex_type re_order multiword)], + debug => 1, + options => [ + { + label => 'RegexId', + db => 'regex_id', + dt => 0, + formatter => sub ($value, $column) { + return ''.$value.''; + }, + }, + { label => 'Name', db => 'name', dt => 1, }, + { label => 'Description', db => 'description', dt => 2, }, + { + label => 'Regex', + db => 'match', + dt => 3, + formatter => sub ($value, $column) { + my $v = $value; + encode_entities($v); + return '
'.$v.'
'; + }, + }, + { label => 'EntityType', db => 'entity_type', dt => 4, }, + { label => 'RegexType', db => 'regex_type', dt => 5, }, + { label => 'RegexOrder', db => 're_order', dt => 6, }, + { label => 'multiword', db => 'multiword', dt => 7, }, + ], + ); + return $self->render(json => $ssp); +} + +1; diff --git a/lib/Flair/Db.pm b/lib/Flair/Db.pm new file mode 100644 index 0000000..bf058fb --- /dev/null +++ b/lib/Flair/Db.pm @@ -0,0 +1,95 @@ +package Flair::Db; + +use lib '../../lib'; +use Flair::Model::Admins; +use Flair::Model::Regex; +use Flair::Model::Metrics; +use Flair::Model::Files; +use Flair::Model::Apikeys; +use Flair::Model::Jobs; +use Mojo::SQLite; +use Mojo::Base -base, -signatures; +use Module::Runtime qw(require_module); + +# instantiate to get handles to sqlite database +# and to models for the tables + +has 'log'; +has 'config'; + +has dbtype => sub ($self) { + return 'sqlite'; +}; + +has dbclass => sub ($self) { + return "Mojo::SQLite" if ($self->dbtype =~ /sqlite/i); + die "Unsupported dbtype : ".$self->dbtype; +}; + +has minion_backend => sub ($self) { + return { SQLite => $self->config->{backend} }; +}; + +has connstr => sub ($self) { + return $self->config->{uri}; +}; + +has dbh => sub ($self) { + my $class = $self->dbclass; + return $class->new($self->connstr); +}; + +has regex => sub ($self) { + return Flair::Model::Regex->new( + dbh => $self->dbh, + log => $self->log, + config => $self->config->{model}->{regex}, + dbtype => $self->dbtype, + ); +}; + +has metrics => sub ($self) { + return Flair::Model::Metrics->new( + dbh => $self->dbh, + dbtype => $self->dbtype, + log => $self->log, + config => $self->config->{model}->{metrics} + ); +}; + +has apikeys => sub ($self) { + return Flair::Model::Apikeys->new( + dbh => $self->dbh, + dbtype => $self->dbtype, + log => $self->log, + config => $self->config->{model}->{apikeys} + ); +}; + +has admins => sub ($self) { + return Flair::Model::Admins->new( + dbh => $self->dbh, + dbtype => $self->dbtype, + log => $self->log, + config => $self->config->{model}->{admins} + ); +}; + +has jobs => sub ($self) { + return Flair::Model::Jobs->new( + dbh => $self->dbh, + dbtype => $self->dbtype, + log => $self->log, + config => $self->config->{model}->{jobs} + ); +}; + +sub add_metric ($self, $metric, $value) { + $self->metrics->add_metric($metric, $value); +} + +sub build_flair_regexes ($self, $opts) { + return $self->regex->build_flair_regexes($opts); +} + +1; diff --git a/lib/Flair/Images.pm b/lib/Flair/Images.pm new file mode 100644 index 0000000..4081677 --- /dev/null +++ b/lib/Flair/Images.pm @@ -0,0 +1,151 @@ +package Flair::Images; + +use Mojo::Base -base, -signatures; +use Data::Dumper::Concise; +use HTML::Element; +use MIME::Base64; +use Digest::MD5 qw(md5_hex); + +has scot_version => sub { 3; }; +has insec => sub {1;}; +has download_dir => '/tmp'; +has 'log'; +has 'scotapi'; +has 'config'; + +# scan flair text for embedded images +# either data:uri or +# replace those images + +sub process ($self, $tree) { + + my @images_in_tree = @{$tree->extract_links('img')}; + my $replaced = 0; + + foreach my $image (@images_in_tree) { + # ref + # my ($link, $element, $attr, $tag) = @$image; + next if $self->already_cached($image); + next if $self->uri_whitelisted($image); + + my $new_image_file = $self->obtain_image($image); + next if (! defined $new_image_file); + + my $new_uri = $self->upload_to_scot($new_image_file); + if (ref($new_uri) and $new_uri->{error}) { + $self->log->error("Failed to replace image: ".$new_uri->{error}); + next; + } + $self->rewrite_img_element($image, $new_uri); + $replaced++; + } + return $replaced; +} + +sub already_cached ($self, $image) { + my $link = $image->[0]; + + $self->log->debug("Looking at link = $link"); + + if ($link =~ /\/api\/v1\/file\/download\//) { + $self->log->debug("Found an already uploaded scot file"); + return 1; + } + return; +} + +sub uri_whitelisted ($self, $image) { + # TODO: implement a whitelist + return undef; +} + +sub obtain_image ($self, $image) { + my ($link, $element, $attr, $tag) = @{$image}; + if ($link eq "") { + # src was stripped by a sanitizer? + $self->log->debug("img src sanitized, unable to further process"); + return undef; + } + return ($link =~ m/^data:image/ ) ? $self->convert_data_uri($image) + : $self->download_image($image); +} + +sub convert_data_uri ($self, $image) { + my $uri = $image->[0]; + my ($mime, $encoding, $data) = ($uri =~ m/^data:(.*);(.*),(.*)$/); + my ($type, $ext) = split('/', $mime); + my $decoded = decode_base64($data); + my $md5 = md5_hex($decoded); + my $newfile = $self->download_dir . "/$md5.$ext"; + if ( $self->file_exists($newfile) ) { + return $newfile; + } + open my $fh, ">", "$newfile" or die $!; + binmode $fh; + print $fh $decoded; + close $fh; + $self->log->debug("Converted DataURI into file $newfile"); + return $newfile; +} + +sub download_image ($self, $image) { + my $link = $image->[0]; + my $ua = Mojo::UserAgent->new(); + $ua->proxy->detect; + my $insec = $self->insec; + my $asset = $ua->insecure($insec)->max_redirects(5)->get($link)->result->content->asset; + my $md5 = md5_hex($asset->slurp); + my $ext = $self->get_extension($link); + my $newfile = $self->download_dir . "/$md5.$ext"; + if ( $self->file_exists($newfile) ) { + return $newfile; + } + $asset->move_to($newfile); + $self->log->debug("Downloaded $link to file $newfile"); + return $newfile; +} + +sub get_extension ($self, $link) { + my $last = (split('/', $link))[-1]; # get everything after last / + my $strip = (split(/\?/, $last))[0]; # strip ?foo=bar&boom=baz from end + my $ext = (split(/\./, $strip))[-1]; # get file extention + return $ext; +} + +sub file_exists ($self, $filename) { + return -r $filename; +} + +sub upload_to_scot ($self, $image) { + my $id = $self->scotapi->upload_file_scot4($image); + if ( ref($id) and defined $id->{error} ) { + return $id; # bubble the error up + } + my $baseurl = $self->config->{scotapi}->{frontend_accessible_root_uri}; + my $fullurl = "/api/v1/file/download/$id"; + $self->log->debug("uploaded $image new url is $fullurl"); + return $fullurl; +} + + +sub rewrite_img_element($self, $image, $new_uri) { + my $link = $image->[0]; + my $element = $image->[1]; + my $new_alt = $self->get_alt($link, $element); + my $new_element = HTML::Element->new('img', + 'src' => $new_uri, + 'alt' => $new_alt, + ); + $element->replace_with($new_element); +} + +sub get_alt ($self, $link, $element) { + my $orig = $element->attr('alt'); + my $new = ( $link =~ /^data:/ ) ? 'Cached copy of embedded data uri' + : "Locally cached copy of $link"; + # $new .= "-$orig-" if defined $orig; + my $new_alt = "$orig-$new"; + return $new_alt; +} + +1; diff --git a/lib/Flair/Model.pm b/lib/Flair/Model.pm new file mode 100644 index 0000000..ea71512 --- /dev/null +++ b/lib/Flair/Model.pm @@ -0,0 +1,145 @@ +package Flair::Model; + +use strict; +use warnings; + +use SQL::Abstract::Limit; +use Data::Dumper::Concise; +use Try::Tiny; +use Mojo::Base -base, -signatures; + +# base (parent) object for models in Models directory + +has 'dbh'; # Mojo::SQLite instance +has 'dbtype'; # sqlite +has 'log'; # MojoX::Log::Log4perl instance +has 'config'; # Model defaults + +sub extract_kv ($self, $href) { + my (@keys, @values); + + while (my($k,$v) = each %$href) { + push @keys, $k, + push @values, $v; + } + return \@keys, \@values; +} + +sub do_query ($self, $sql, @bind) { + my $result = try { + my $tx = $self->dbh->db->begin; + my $res = $self->dbh->db->query($sql, @bind); + $tx->commit; + return $res; + } + catch { + $self->log->error("DB Error: $_"); + return undef; + }; + return $result; +} + +sub getSAL ($self) { + if ($self->dbtype eq "mysql") { + return SQL::Abstract::Limit->new(limit_dialect => 'LimitXY'); + } + return SQL::Abstract::Limit->new(limit_dialect => 'LimitOffset'); +} + +sub merge_options ($self, $opts, $default) { + return $default unless defined $opts; + my $merged = {}; + + foreach my $key (keys %$default) { + $merged->{$key} = (defined $opts->{$key}) ? $opts->{$key} + : $default->{$key}; + } + return $merged; +} + +sub log_sql ($self, $model, $sql, @bind) { + + my $msg = join( + "\n", + '-'x20, + ref($self->dbh), + " SQL GENERATED in $model", + " ".$sql, + " BindVars: ".join(', ', @bind), + '-'x78 + ); + + $self->log->trace($msg); +} + +sub log_result ($self, $result) { + my $msg = join( + "\n", + "-"x20, + ref($result), + Dumper($result), + "-"x78 + ); + $self->log->trace($msg); +} + +1; +__END__ + +=head1 Name + +Flair::Model - base class for Flair Models + +=head1 Attributes + +=over 4 + +=item I + +The Mojo::x database wrapper instance created in Flair.pm + +=item I + +The MojoX::Log::Log4perl instance created in Flair.pm + +=back + +=head1 Methods + +=over 4 + +=item B + +Given a hash reference, this method returns two array references. The first +a list of the hash keys, and the second contains the corresponding values. + + my ($keys, $values) = $model->extract_kv({ foo => "bar", boom => "baz" }); + say join(',',@$keys); # foo, boom + say join(',',@$values); # bar, baz + +=item B + +Start a transaction, execute $sql query using @bind values. Returns Result object + +=item B + +return an instance of a SQL::Abstract::Limit object with dialect for Postgresql + +=item B + +Create a hash reference where the missing items of $opts are filled in with +the values in $default. + + + my $default = { one => 1, two => 2, three => 3 }; + my $opts = { one => 2, three => 6 }; + my $merge = $self->merge_options($opts, $default); + say Dumper($merge); + # { + # one => 2, + # two => 2, + # three => 6, + # } + +=back + diff --git a/lib/Flair/Model/Admins.pm b/lib/Flair/Model/Admins.pm new file mode 100644 index 0000000..47780c8 --- /dev/null +++ b/lib/Flair/Model/Admins.pm @@ -0,0 +1,282 @@ +package Flair::Model::Admins; + +use lib '../../../lib'; +use DateTime; +use Data::Dumper::Concise; +use SQL::Abstract::Limit; +use Try::Tiny; +use Statistics::Descriptive; +use Mojo::Base 'Flair::Model', -signatures; + +has 'tablename' => 'admins'; + +sub create ($self, $admin_href) { + if ($self->dbtype eq "pg") { + return $self->create_pg($admin_href); + } + if ($self->dbtype eq "mysql") { + return $self->create_mysql($admin_href); + } + # sqlite + $self->create_sqlite($admin_href); +} + +sub create_pg ($self, $admin_href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $admin_href, + { returning => 'admin_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + $self->log_result($result); + my $id = $result->hash->{admin_id}; + return $self->fetch($id); +} + +sub create_mysql ($self, $admin_href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $admin_href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($result); +} + +sub create_sqlite ($self, $admin_href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $admin_href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($result); +} + +sub list ($self, $opts) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + $opts->{fields}, + $opts->{where}, + $opts->{order}, + $opts->{offset}, + $opts->{limit}, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + + my $collection = $result->hashes; + my $res = $collection->to_array; + return $res; +} + +sub get_admin ($self, $username, $logsql=undef) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { username => $username }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind) if $logsql; + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + + +sub fetch ($self, $id) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { admin_id => $id }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub get_key ($self, $key) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { key => $key }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub update ($self, $id, $update_href) { + return $self->update_pg($id, $update_href) if ($self->dbtype eq "pg"); + return $self->update_mysql($id, $update_href) if ($self->dbtype eq "mysql"); + $self->update_sqlite($id, $update_href); +} + +sub update_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { admin_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'admin_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{admin_id}; + return $self->fetch($newid); + +} + +sub update_mysql ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { admin_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub update_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { admin_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch ($self, $id, $update_href) { + if ($self->dbtype eq "pg") { + return $self->patch_pg($id, $update_href); + } + if ($self->dbtype eq "mysql") { + return $self->patch_mysql($id, $update_href); + } + return $self->patch_sqlite($id, $update_href); +} + +sub patch_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { admin_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'admin_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{admin_id}; + return $self->fetch($newid); +} + +sub patch_mysql ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { admin_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { admin_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub set_lastlogin ($self, $username) { + my $stmt = "UPDATE admins SET lastlogin = CURRENT_TIMESTAMP WHERE username = ? "; + $stmt .= " RETURNING admin_id" if ($self->dbtype eq 'pg'); + my @bind = ($username); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + if ( $self->dbtype eq "pg" ) { + my $id = $result->hash->{admin_id}; + my $updated = $self->fetch($id); + return $updated; + } + # mysql for more fun! + return $self->get_admin($username); +} + +sub set_lastaccess ($self, $username) { + return $self->set_lastaccess_pg($username) if ($self->dbtype eq "pg"); + return $self->set_lastaccess_mysql($username) if ($self->dbtype eq "mysql"); + return $self->set_lastaccess_sqlite($username); +} + +sub set_lastaccess_pg ($self, $username) { + my $stmt = "UPDATE admins SET lastaccess = CURRENT_TIMESTAMP WHERE username = ? RETURNING admin_id"; + my @bind = ($username); + # $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + my $id = $result->hash->{admin_id}; + my $updated = $self->fetch($id); + return $updated; +} + +sub set_lastaccess_mysql ($self, $username) { + my $stmt = "UPDATE admins SET lastaccess = CURRENT_TIMESTAMP WHERE username = ?"; + my @bind = ($username); + # $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + my $updated = $self->get_admin($username); + return $updated; +} + +sub set_lastaccess_sqlite ($self, $username) { + my $stmt = "UPDATE admins SET lastaccess = CURRENT_TIMESTAMP WHERE username = ?"; + my @bind = ($username); + # $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + my $updated = $self->get_admin($username); + return $updated; +} + +sub delete ($self, $id) { + $self->log->debug("delete $id"); + my $orig = $self->fetch($id); + my $sql = $self->getSAL; + my $where = { admin_id => $id }; + my ($stmt, @bind) = $sql->delete($self->tablename, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $orig; +} + +sub count ($self, $where) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + 'COUNT(*) as count', + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $href = $result->hash; + return $href; +} + +1; diff --git a/lib/Flair/Model/Apikeys.pm b/lib/Flair/Model/Apikeys.pm new file mode 100644 index 0000000..cd1275e --- /dev/null +++ b/lib/Flair/Model/Apikeys.pm @@ -0,0 +1,161 @@ +package Flair::Model::Apikeys; + +use lib '../../../lib'; +use DateTime; +use Data::Dumper::Concise; +use SQL::Abstract::Limit; +use Try::Tiny; +use Statistics::Descriptive; +use Storable qw(dclone); +use Mojo::Base 'Flair::Model', -signatures; + +has 'tablename' => 'apikeys'; + +sub create ($self, $apikey_href) { + $self->log->debug("Creating APIKEY"); + return $self->create_sqlite($apikey_href); +} + +sub create_sqlite ($self, $apikey_href) { + my $sql = $self->getSAL; + my $href = dclone($apikey_href); + my ($stmt, @bind) = $sql->insert($self->tablename, + $href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $id = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($id); +} + +sub list ($self, $opts) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + $opts->{fields}, + $opts->{where}, + $opts->{order}, + $opts->{offset}, + $opts->{limit}, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + + my $collection = $result->hashes; + my $res = $collection->to_array; + $self->log->debug("res = ",{filter => \&Dumper, value => $res}); + return $res; +} + +sub fetch ($self, $id) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { apikey_id => $id }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub update ($self, $id, $update_href) { + $self->log->debug("performing update"); + return $self->update_sqlite($id, $update_href); +} + +sub update_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { apikey_id => $id }; + my $href = dclone($update_href); + $href->{'`key`'} = delete $href->{key}; + my ($stmt, @bind) = $sql->update($self->tablename, $href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch ($self, $id, $update_href) { + return $self->patch_sqlite($id, $update_href); +} + +sub patch_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { apikey_id => $id }; + my $href = dclone($update_href); + $href->{'`key`'} = delete $href->{key}; + my ($stmt, @bind) = $sql->update($self->tablename, + $href, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub delete ($self, $id) { + $self->log->debug("delete $id"); + my $orig = $self->fetch($id); + my $sql = $self->getSAL; + my $where = { apikey_id => $id }; + my ($stmt, @bind) = $sql->delete($self->tablename, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $orig; +} + +sub count ($self, $where) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + 'COUNT(*) as count', + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $href = $result->hash; + return $href; +} + +sub set_lastaccess ($self, $apiuser) { + my $stmt = "UPDATE apikeys SET lastaccess = CURRENT_TIMESTAMP ". + "WHERE username = ? "; + my @bind = ($apiuser); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->get_by_user($apiuser); +} + +sub get_key ($self, $key) { + my $sql = $self->getSAL; + my $fieldname = 'apikey'; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { $fieldname => $key }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub get_by_user ($self, $username) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { username => $username }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +1; diff --git a/lib/Flair/Model/Files.pm b/lib/Flair/Model/Files.pm new file mode 100644 index 0000000..df8c6d5 --- /dev/null +++ b/lib/Flair/Model/Files.pm @@ -0,0 +1,123 @@ +package Flair::Model::Files; + +use lib '../../../lib'; +use DateTime; +use Data::Dumper::Concise; +use SQL::Abstract::Limit; +use Try::Tiny; +use Statistics::Descriptive; +use Mojo::Base 'Flair::Model', -signatures; + +has 'tablename' => 'files'; + +sub list ($self, $opts) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + $opts->{fields}, + $opts->{where}, + $opts->{order}, + $opts->{offset}, + $opts->{limit}, + ); + + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + my $collection = $result->hashes; + my $res = $collection->to_array; + $self->log->trace("list result ",{filter=>\&Dumper, value=>$res}); + return $res; + +} + +sub fetch ($self, $id) { + + my $sql = $self->getSAL; + my $cols = [qw(*)]; + my $where = { file_id => $id }; + my ($stmt, @bind) = $sql->select($self->tablename, $cols, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $hash = $result->hash; + $self->log->trace("fetch result ",{filter=>\&Dumper, value=>$hash}); + return $hash; +} + +sub create ($self, $file_href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $file_href, + { returning => 'file_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + my $id = $result->hash->{file_id}; + return $self->fetch($id); +} + +sub update ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { file_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'file_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{file_id}; + return $self->fetch($newid); +} + +sub patch ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { file_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'file_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{file_id}; + return $self->fetch($newid); + +} + +sub delete ($self, $id) { + my $orig = $self->fetch($id); + my $sql = $self->getSAL; + my $where = { file_id => $id }; + + my ($stmt, @bind) = $sql->delete($self->tablename, $where); + + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $orig; +} + +sub upsert ($self, $href) { + my $results = $self->list({ + fields => [ 'file_id' ], + where => { filename => $href->{filename} }, + }); + if (defined $results and scalar @$results > 0 ) { + return $self->update($results->[0]->{file_id}, $href); + } + return $self->create($href); +} + +sub count ($self, $where) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + 'COUNT(*) as count', + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $href = $result->hash; + return $href; +} + +1; diff --git a/lib/Flair/Model/Jobs.pm b/lib/Flair/Model/Jobs.pm new file mode 100644 index 0000000..522492e --- /dev/null +++ b/lib/Flair/Model/Jobs.pm @@ -0,0 +1,241 @@ +package Flair::Model::Jobs; + +use lib '../../../lib'; +use DateTime; +use Data::Dumper::Concise; +use SQL::Abstract::Limit; +use Try::Tiny; +use Statistics::Descriptive; +use Mojo::Base 'Flair::Model', -signatures; + +has 'tablename' => 'jobs'; + +sub get_job ($self, $where) { + + my $sql = $self->getSAL; + my $cols = [ qw(job_id year month day hour job value)]; + my ($stmt, @bind) = $sql->select($self->tablename, $cols, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $collection = $result->hashes; + my $res = $collection->to_array; + $self->log->trace("list result ",{filter=>\&Dumper, value=>$res}); + return $res; +} + +sub add_job ($self, $job, $value, $dt=undef) { + $dt = DateTime->now() if not defined $dt; + my $y = $dt->year; + my $m = $dt->month; + my $d = $dt->day; + my $h = $dt->hour; + + my $sql = 'SELECT add_job(?, ?, ?, ?, ?, ?)'; + my @bind = ($y, $m, $d, $h, $job, $value); + $self->log_sql(__PACKAGE__, $sql, @bind); + my $result = $self->do_query($sql, @bind); + return $result; +} + +sub get_stats ($self, $where) { + my $rows = $self->get_job($where); + my @data = map {$_->{value} + 0} @$rows; + my $stat = Statistics::Descriptive::Full->new(); + $stat->add_data(@data); + + return { + count => $stat->count, + mean => $stat->mean, + sum => $stat->sum, + stddev => $stat->standard_deviation, + min => $stat->min, + max => $stat->max, + median => $stat->median, + }; +} + +sub create ($self, $href) { + return $self->create_pg($href) if ($self->dbtype eq "pg"); + return $self->create_mysql($href) if ($self->dbtype eq "mysql"); + return $self->create_sqlite($href); +} + +sub create_pg ($self, $href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $href, + { returning => 'job_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $id = $result->hash->{job_id}; + return $self->fetch($id); +} + +sub create_mysql ($self, $href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, $href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $id = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($id); +} + +sub create_sqlite ($self, $href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, $href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $id = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($id); +} + +sub list ($self, $opts) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + $opts->{fields}, + $opts->{where}, + $opts->{order}, + $opts->{offset}, + $opts->{limit}, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + + my $collection = $result->hashes; + my $res = $collection->to_array; + return $res; +} + +sub fetch ($self, $id) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { job_id => $id }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub get_key ($self, $key) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { key => $key }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub update ($self, $id, $update_href) { + return $self->update_pg($id, $update_href) if ($self->dbtype eq "pg"); + return $self->update_mysql($id, $update_href) if ($self->dbtype eq "mysql"); + return $self->update_sqlite($id, $update_href); +} + +sub update_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { job_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'job_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{job_id}; + return $self->fetch($newid); + +} + +sub update_mysql ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { job_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, $update_href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub update_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { job_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, $update_href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch ($self, $id, $patch_href) { + return $self->patch_pg($id, $patch_href) if ($self->dbtype eq "pg"); + return $self->patch_mysql($id, $patch_href) if ($self->dbtype eq "mysql"); + return $self->patch_sqlite($id, $patch_href); +} + +sub patch_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { job_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'job_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{job_id}; + return $self->fetch($newid); +} + +sub patch_mysql ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { job_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, $update_href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { job_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, $update_href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub delete ($self, $id) { + $self->log->debug("delete $id"); + my $orig = $self->fetch($id); + my $sql = $self->getSAL; + my $where = { job_id => $id }; + my ($stmt, @bind) = $sql->delete($self->tablename, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $orig; +} + +sub count ($self, $where) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + 'COUNT(*) as count', + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $href = $result->hash; + return $href; +} + +1; diff --git a/lib/Flair/Model/Metrics.pm b/lib/Flair/Model/Metrics.pm new file mode 100644 index 0000000..dc7e6e2 --- /dev/null +++ b/lib/Flair/Model/Metrics.pm @@ -0,0 +1,320 @@ +package Flair::Model::Metrics; + +use lib '../../../lib'; +use DateTime; +use Data::Dumper::Concise; +use SQL::Abstract::Limit; +use Try::Tiny; +use Statistics::Descriptive; +use Storable qw(dclone); +use Mojo::Base 'Flair::Model', -signatures; + +has 'tablename' => 'metrics'; + +sub get_metric ($self, $where) { + + my $sql = $self->getSAL; + my $cols = [ qw(metric_id year month day hour metric value)]; + my ($stmt, @bind) = $sql->select($self->tablename, $cols, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $collection = $result->hashes; + my $res = $collection->to_array; + $self->log->trace("list result ",{filter=>\&Dumper, value=>$res}); + return $res; +} + +sub get_hourly_metrics ($self) { + my $dt = DateTime->now; + my $where = { + year => $dt->year, + month => $dt->month, + day => $dt->day, + }; + my %stats = (); + my $results = $self->get_metric($where); + foreach my $row (@$results) { + my $date_key = join('-', $row->{year}, $row->{month}, $row->{day})." ".$row->{hour}; + my $metric = $row->{metric}; + push @{$stats{$metric}{label}}, $date_key; + push @{$stats{$metric}{label}}, $row->{value}; + } + return wantarray ? %stats : \%stats; +} + +sub add_metric ($self, $metric, $value, $dt=undef) { + $self->log->debug("adding metric $metric $value"); + return $self->add_metric_pg($metric, $value, $dt) if ($self->dbtype eq "pg"); + return $self->add_metric_mysql($metric, $value, $dt) if ($self->dbtype eq "mysql"); + return $self->add_metric_sqlite($metric, $value, $dt); +} + +sub get_date_params ($self, $dt=undef) { + $dt = DateTime->now() if not defined $dt; + return $dt->year, $dt->month, $dt->day, $dt->hour; +} + +sub add_metric_pg ($self, $metric, $value, $dt=undef) { + my ($y, $m, $d, $h) = $self->get_date_params($dt); + my $sql = 'SELECT add_metric(?, ?, ?, ?, ?, ?)'; + my @bind = ($y, $m, $d, $h, $metric, $value); + $self->log_sql(__PACKAGE__, $sql, @bind); + my $result = $self->do_query($sql, @bind); + return $result; +} + +sub add_metric_mysql ($self, $metric, $value, $dt=undef) { + # cant figure out how to write this as a function or otherwise in mysql's poor sql + my ($y, $m, $d, $h) = $self->get_date_params($dt); + my $where = { year => $y, month => $m, day => $d, hour => $h, metric => $metric }; + my $mrecs = $self->get_metric($where); + my $mrec = $mrecs->[0]; + if ( ! defined $mrec ) { + $where->{value} = $value; + return $self->create($where); + } + my $newval = $mrec->{value} + $value; + return $self->patch($mrec->{metric_id}, {value =>$newval}); +} + +sub add_metric_sqlite ($self, $metric, $value, $dt=undef) { + # cant figure out how to write this as a function or otherwise in mysql's poor sql + my ($y, $m, $d, $h) = $self->get_date_params($dt); + my $where = { year => $y, month => $m, day => $d, hour => $h, metric => $metric }; + my $mrecs = $self->get_metric($where); + my $mrec = $mrecs->[0]; + if ( ! defined $mrec ) { + $where->{value} = $value; + return $self->create($where); + } + my $newval = $mrec->{value} + $value; + return $self->patch($mrec->{metric_id}, {value =>$newval}); +} + +sub get_stats ($self, $where) { + my $rows = $self->get_metric($where); + my @data = map {$_->{value} + 0} @$rows; + my $stat = Statistics::Descriptive::Full->new(); + $stat->add_data(@data); + + return { + count => $stat->count, + mean => $stat->mean, + sum => $stat->sum, + stddev => $stat->standard_deviation, + min => $stat->min, + max => $stat->max, + median => $stat->median, + }; +} + +sub create ($self, $href) { + return $self->create_pg($href) if ($self->dbtype eq "pg"); + return $self->create_mysql($href) if ($self->dbtype eq "mysql"); + return $self->create_sqlite($href); +} + +sub create_pg ($self, $href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $href, + { returning => 'metric_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $id = $result->hash->{metric_id}; + return $self->fetch($id); +} + +sub create_mysql ($self, $href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($result); +} + +sub create_sqlite ($self, $href) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($result); +} + +sub list ($self, $opts) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + $opts->{fields}, + $opts->{where}, + $opts->{order}, + $opts->{offset}, + $opts->{limit}, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + + my $collection = $result->hashes; + my $res = $collection->to_array; + return $res; +} + +sub fetch ($self, $id) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { metric_id => $id }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub get_key ($self, $key) { + my $sql = $self->getSAL; + my ($stmt, + @bind) = $sql->select($self->tablename, + ['*'], + { key => $key }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + return $result->hash; +} + +sub update ($self, $id, $update_href) { + return $self->update_pg($id, $update_href) if ($self->dbtype eq "pg"); + return $self->update_mysql($id, $update_href) if ($self->dbtype eq "mysql"); + return $self->update_sqlite($id, $update_href); +} + +sub update_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { metric_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'metric_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{metric_id}; + return $self->fetch($newid); +} + +sub update_mysql ($self, $id, $update_href) { + $self->log->debug("attempting update: ", {filter=>\&Dumper, value=>$update_href}); + my $sql = $self->getSAL; + $self->log->debug("after sal"); + my $where = { metric_id => $id }; + my $href = dclone($update_href); + $self->log->debug("after clone"); + delete $href->{metric_id}; + $self->log->debug("attempting update: ", {filter=>\&Dumper, value=>$href}); + my ($stmt, @bind) = $sql->update($self->tablename, $href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); + +} + +sub update_sqlite ($self, $id, $update_href) { + $self->log->debug("attempting update: ", {filter=>\&Dumper, value=>$update_href}); + my $sql = $self->getSAL; + $self->log->debug("after sal"); + my $where = { metric_id => $id }; + my $href = dclone($update_href); + $self->log->debug("after clone"); + delete $href->{metric_id}; + $self->log->debug("attempting update: ", {filter=>\&Dumper, value=>$href}); + my ($stmt, @bind) = $sql->update($self->tablename, $href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); + +} + +sub patch ($self, $id, $update_href) { + return $self->patch_pg($id, $update_href) if ($self->dbtype eq "pg"); + return $self->patch_mysql($id, $update_href) if ($self->dbtype eq "mysql"); + return $self->patch_sqlite($id, $update_href); +} + +sub patch_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { metric_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'metric_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{metric_id}; + return $self->fetch($newid); +} + +sub patch_mysql ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { metric_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { metric_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub delete ($self, $id) { + $self->log->debug("delete $id"); + my $orig = $self->fetch($id); + my $sql = $self->getSAL; + my $where = { metric_id => $id }; + my ($stmt, @bind) = $sql->delete($self->tablename, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $orig; +} + +sub count ($self, $where) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + 'COUNT(*) as count', + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $href = $result->hash; + return $href; +} + +1; diff --git a/lib/Flair/Model/Regex.pm b/lib/Flair/Model/Regex.pm new file mode 100644 index 0000000..7010621 --- /dev/null +++ b/lib/Flair/Model/Regex.pm @@ -0,0 +1,300 @@ +package Flair::Model::Regex; + +use lib '../../../lib'; +use Flair::Regex; +use Data::Dumper::Concise; +use SQL::Abstract::Limit; +use Try::Tiny; +use Storable qw(dclone); +use Mojo::Base 'Flair::Model', -signatures; + +has 'tablename' => 'regex'; + +sub list ($self, $opts) { + + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + $opts->{fields}, + $opts->{where}, + $opts->{order}, + $opts->{offset}, + $opts->{limit}, + ); + + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return undef if not defined $result; + my $collection = $result->hashes; + my $res = $collection->to_array; + # $self->log->trace("list result ",{filter=>\&Dumper, value=>$res}); + return $res; +} + +sub fetch ($self, $id) { + + $self->log->debug("fetch = $id"); + # my $stmt = "SELECT * FROM ".$self->tablename." WHERE id = ?"; + # my @bind = ($id); + + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + ['*'], + { regex_id => $id }, + ); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $rhash = $result->hash; + return $rhash; +} + +sub create ($self, $regex_href) { + return $self->create_pg($regex_href) if ($self->dbtype eq "pg"); + return $self->create_mysql($regex_href) if ($self->dbtype eq "mysql"); + return $self->create_sqlite($regex_href); +} + +sub create_pg ($self, $regex_href) { + $self->log->debug("REGEX Create"); + $self->log->debug("regex = ", { filter => \&Dumper, value => $regex_href}); + + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->insert($self->tablename, + $regex_href, + { returning => 'regex_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $id = $result->hash->{regex_id}; + return $self->fetch($id); +} + +sub create_mysql ($self, $regex_href) { + my $sql = $self->getSAL; + my $href = dclone($regex_href); + $href->{'`match`'} = delete $href->{match}; + my ($stmt, @bind) = $sql->insert($self->tablename, $href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $id = $self->do_query($stmt, @bind)->last_insert_id; + return $self->fetch($id); +} + +sub create_sqlite ($self, $regex_href) { + my $sql = $self->getSAL; + my $href = dclone($regex_href); + $href->{'`match`'} = delete $href->{match}; + my ($stmt, @bind) = $sql->insert($self->tablename, $href); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $q = $self->do_query($stmt, @bind); + if ( ! defined $q ) { + die "Error: $_\n stmt = $stmt \nbind = ",Dumper(\@bind); + } + my $id = $q->last_insert_id; + return $self->fetch($id); +} + +sub update ($self, $id, $update_href) { + return $self->update_re($id, $update_href) if ($self->dbtype eq "pg"); + return $self->update_mysql($id, $update_href) if ($self->dbtype eq "mysql"); + return $self->update_sqlite($id, $update_href); +} + +sub update_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { regex_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'regex_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{regex_id}; + return $self->fetch($newid); +} + +sub update_mysql ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { regex_id => $id }; + my $href = dclone($update_href); + $href->{'`match`'} = delete $href->{match}; + my ($stmt, @bind) = $sql->update($self->tablename, $href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub update_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { regex_id => $id }; + my $href = dclone($update_href); + $href->{'`match`'} = delete $href->{match}; + my ($stmt, @bind) = $sql->update($self->tablename, $href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch ($self, $id, $update_href) { + return $self->patch_pg($id, $update_href) if ($self->dbtype eq "pg"); + return $self->patch_mysql($id, $update_href) if ($self->dbtype eq "mysql"); + return $self->patch_sqlite($id, $update_href); +} + +sub patch_pg ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { regex_id => $id }; + my ($stmt, @bind) = $sql->update($self->tablename, + $update_href, + $where, + { returning => 'regex_id' }); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $newid = $result->hash->{regex_id}; + return $self->fetch($newid); +} + +sub patch_mysql ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { regex_id => $id }; + my $href = dclone($update_href); + $href->{'`match`'} = delete $href->{match} if (defined $href->{match}); + my ($stmt, @bind) = $sql->update($self->tablename, $href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + +sub patch_sqlite ($self, $id, $update_href) { + my $sql = $self->getSAL; + my $where = { regex_id => $id }; + my $href = dclone($update_href); + $href->{'`match`'} = delete $href->{match} if (defined $href->{match}); + my ($stmt, @bind) = $sql->update($self->tablename, $href, $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $self->fetch($id); +} + + +sub delete ($self, $id) { + $self->log->debug("delete $id"); + my $orig = $self->fetch($id); + my $sql = $self->getSAL; + my $where = { regex_id => $id }; + my ($stmt, @bind) = $sql->delete($self->tablename, + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + return $orig; +} + +sub count ($self, $where) { + my $sql = $self->getSAL; + my ($stmt, @bind) = $sql->select($self->tablename, + 'COUNT(*) as count', + $where); + $self->log_sql(__PACKAGE__, $stmt, @bind); + + my $result = $self->do_query($stmt, @bind); + my $href = $result->hash; + return $href; +} + +sub load_core_re ($self) { + my @re = (); + my $core = Flair::Regex->new(); + my @corere = $core->core_regex_names; + + foreach my $re_name (@corere) { + my $href = $core->$re_name; + push @re, $href; + } + my @sorted = sort { $a->{re_order} <=> $b->{re_order} } @re; + return wantarray ? @sorted : \@sorted; + +} + +sub load_user_def_re ($self, $opts=undef) { + my @cooked = (); + if (not defined $opts) { + $opts = { + fields => ['*'], + order => { -asc => 're_order' }, + }; + } + + my $raw = $self->list($opts); + + foreach my $href (@$raw) { + if ( ! defined $href->{regex} or ref($href->{regex}) ne "Rexexp" ) { + $self->create_re($href); + } + push @cooked, $href; + } + return wantarray ? @cooked : \@cooked; +} + + +sub build_flair_regexes ($self, $opts=undef) { + my @re = $self->load_core_re(); + push @re, $self->load_user_def_re($opts); + return wantarray ? @re : \@re; +} + +sub create_re ($self, $href) { + my $match = $href->{match}; + die "Must provide match value in RE record." unless defined $match; + $href->{regex} = ($href->{multiword}) ? qr/($match)/xims + : qr/\b($match)\b/xims; +} + +sub upsert_re ($self, $href) { + my $results = $self->list({ + fields => [ 'regex_id' ], + where => { + name => $href->{name}, + match => $href->{match}, + }, + }); + $self->log->debug("list result = ",{filter=>\&Dumper, value => $results}); + + if (defined $results and scalar @$results > 0) { + return $self->update($results->[0]->{regex_id}, $href); + } + return $self->create($href); +} + +sub regex_by_name ($self, $name) { + my $opts = { + fields => [ '*' ], + where => { name => $name }, + order => { -asc => 're_order' }, + }; + return $self->build_flair_regexes($opts); +} + +sub regex_exists ($self, $regex) { + $self->log->debug("Checking for existing Regex: $regex"); + my $match = '`match`'; + my $opts = { + fields => [ '*' ], + where => { $match => $regex }, + }; + my $regexes = $self->list($opts); + if (defined $regexes and scalar(@$regexes) > 0) { + my $existing = $regexes->[0]->{regex_id}; + $self->log->debug("Exists with id = $existing"); + return $existing; + } + $self->log->debug("no matching regex"); + return undef; +} + +1; diff --git a/lib/Flair/Parser.pm b/lib/Flair/Parser.pm new file mode 100644 index 0000000..b16c2be --- /dev/null +++ b/lib/Flair/Parser.pm @@ -0,0 +1,364 @@ +package Flair::Parser; + +use lib '../../lib'; +use Data::Dumper::Concise; +use Flair::Util::Timer; +use Domain::PublicSuffix; +use Net::IPv6Addr; +use HTML::Element; +use Mojo::Base -base, -signatures; +use Try::Tiny; + +# this module parses the text in a leaf node +# and returns the entities and flaired text + +has 'db'; +has 'log'; + +# used to detect false positive matches for domains +has 'public_suffix' => sub { + return Domain::PublicSuffix->new({ + data_file => '../../etc/public_suffix_list.dat' + }); +}; + +sub parse ($self, $input, $edb, $falsepos, $hint=undef) { + my $clean = $self->clean_input($input); + # load the current set of regexes. This can be updated async + # so we want to pay the price of database fetch to make sure we + # have any new flair regexes included + my $regexes = $self->db->regex->build_flair_regexes(); + # begin the parsing of the text, which is a recursive process + my @new = $self->descend($edb, $input, $falsepos, $regexes, $hint); + return @new; +} + +sub clean_input($self, $text) { + # shim if we should need to some cleaning, like encoding utf8 + my $clean = $text; + return $clean; +} + +sub descend ($self, $edb, $input, $falsepos, $re_aref, $hint=undef) { + # suppress deep recursion warning + no warnings 'recursion'; + + # recursion end condition + return if $input eq ''; + + my @new = (); + my @regexes = @{$re_aref}; + + # if we have a hint, then only provide that re for parsing + # e.g. message_id columns, that way email does not match on it. + if (defined $hint) { + @regexes = grep { $_->{entity_type} eq $hint } @regexes; + } + + # look for each regex. first found regex wins and we move on. + REGEX: + foreach my $re_href (@regexes) { + + my $re = $re_href->{regex}; + my $et = $re_href->{entity_type}; + + # get text before, the flair, and text after match + my ($pre, $flair, $post) = $self->find_flairable($input, + $re, + $et, + $edb, + $falsepos); + + # this regex didn't match, move to next + next REGEX if (! defined $flair); + + # recurse into the text in the $pre (the text before the match) + push @new, $self->descend($edb, $pre, $falsepos, $re_aref, $hint); + + # add flairable to stack + push @new, $flair; + + # recurse into the text in the $post, pushing results onto stack + push @new, $self->descend($edb, $post, $falsepos, $re_aref, $hint); + + # since we matched at this level, stop search + last REGEX; + } + + # no flairable found, so put input item on stack + push @new, $input if scalar(@new) < 1; + + # return the stack + return wantarray ? @new : \@new; +} + +sub find_flairable ($self, $text, $re, $et, $edb, $falsepos) { + # false positives occur, PRE store those + my $PRE = ''; + my $fp; + + # while we have matches to the re + MATCH: + while ( $text =~ m/$re/g ) { + + $self->log->trace("Text = ".$text); + # use perl special vars to get pre, match and post strings + # $-[0] = index of start of match, $+[0] = index of end of match + my $pre = substr($text, 0, $-[0]); + my $match = substr($text, $-[0], $+[0] - $-[0]); + my $post = substr($text, $+[0]); + + $self->log->debug("we have a match for $et => $match"); + + # verify match and take special actions + my $flairable = $self->post_match_actions($match, $et, $edb, $falsepos); + + # check for false positive + if (! defined $flairable) { + $fp++; + $self->log->trace("no flairable: "); + $self->log->trace("PRE was $PRE"); + # append $pre and $match to the PRE buffer + $PRE .= $pre.$match; + $self->log->trace("PRE now $PRE"); + next MATCH; + } + # return the flairable + $self->log->trace("PRE.pre = ".$PRE.$pre); + return $pre, $flairable, $post if $fp; + return $PRE.$pre, $flairable, $post; + } + # nothing found + return undef, undef, undef; +} + +sub post_match_actions ($self, $match, $et, $edb, $falsepos) { + + # special cases + return $self->cidr_action($match, $edb, $falsepos) if $et eq "cidr"; + return $self->domain_action($match, $edb, $falsepos) if $et eq "domain"; + return $self->ipaddr_action($match, $edb, $falsepos) if $et eq "ipaddr"; + return $self->ipv6_action($match, $edb, $falsepos) if $et eq "ipv6"; + return $self->suricata_ipv6_action($match, $edb, $falsepos) if $et eq "suricata_ipv6"; + return $self->email_action($match, $edb, $falsepos) if $et eq "email"; + return $self->message_id_action($match, $edb, $falsepos) if $et eq "message_id"; + return $self->cve_action($match, $edb, $falsepos) if $et eq "cve"; + + # default case + my $span = $self->create_span($match, $et); + $self->add_entity($edb, $match, $et); + return $span; +} + +sub cve_action ($self, $match, $edb, $falsepos) { + my $span = $self->create_span_preserve_case($match, 'cve'); + $self->add_entity_preserve_case($edb, $match, 'cve'); + return $span; +} + +sub cidr_action ($self, $match, $edb, $falsepos) { + my $cidr = $self->deobsfucate_ipdomain($match); + $self->add_entity($edb, $cidr, 'cidr'); + return $self->create_span($match, 'cidr'); +} + +sub domain_action ($self, $match, $edb, $falsepos) { + my $log = $self->log; + my $domain = $self->deobsfucate_ipdomain($match); + + # if we have seen this false postive before, short circuit out of here + if ($self->previous_false_positive_domain($edb, $domain, $falsepos)) { + $self->log->debug("Previously seen false positive domain detected, skip"); + return undef; + } + + return try { + # check validity + my $root = $self->get_root_domain($domain, $falsepos); + if ( ! defined $root ) { + # invalid, so mark it and bail + $falsepos->{domain}->{$domain}++; + $log->warn("Domain $domain marked as false positive"); + return undef; + } + if ( $domain =~ m/.*\.zip$/ ) { + # assume .zip is a file extension because that is more common + $log->warn("Domain $domain assumed to be a file, not a domain"); + return undef; + } + + # we have what appears to be a domain + $self->add_entity($edb, $domain, 'domain'); + return $self->create_span($domain, 'domain'); # this returns out of the try + } + catch { + # get root domain failed utterly + $edb->{cache}->{domain_fp}->{$domain}++; + $log->warn("Domain $domain marked false positive due to failure: $_"); + return undef; + }; +} + +sub previous_false_positive_domain ($self, $edb, $domain, $falsepos) { + # $self->log->trace("domain = $domain FP = ", {filter=>\&Dumper, value => $falsepos}); + return defined $falsepos->{domain}->{$domain}; +} + +sub get_root_domain ($self, $domain,$falsepos) { + # validate that domain "could" be a real domain + my $pds = $self->public_suffix; + my $root = $pds->get_root_domain($domain, $falsepos); + my $error = $pds->error() // ''; + if ($error eq "Domain not valid") { + $root = $pds->get_root_domain('x.'.$domain, $falsepos); + $error = $pds->error(); + return undef if ! defined $root; + } + return $root; +} + +sub ipaddr_action ($self, $match, $edb,$falsepos) { + my $ipaddr = $self->deobsfucate_ipdomain($match); + $self->add_entity($edb, $ipaddr, 'ipaddr'); + return $self->create_span($ipaddr, 'ipaddr'); +} + +sub ipv6_action ($self, $match, $edb, $falsepos) { + # see if Net::IPv6Addr things this is a valid ipv6 address + $self->log->debug("Checking $match for IPv6-ness"); + my $ipobj = try { + return Net::IPv6Addr->new($match); + } + catch { + $self->log->warn("invalid IPv6"); + return undef; + }; + + if (not defined $ipobj or ref($ipobj) ne "Net::IPv6Addr") { + $self->log->warn("failed to validate potential ipv6: $match: $_"); + return undef; + } + + # it is! + my $standardized = $ipobj->to_string_preferred(); + $self->add_entity($edb, $standardized, 'ipv6'); + return $self->create_span($standardized, 'ipv6'); +} + +sub suricata_ipv6_action ($self, $match, $edb, $falsepos) { + # suricata puts a :portnum on the end of the ipv6 addr + my @parts = split(/:/, $match); + my $port = pop @parts; + my $ipv6 = join(':', @parts); + my $ipobj = Net::IPv6Addr->new($ipv6); + + if (not defined $ipobj or ref($ipobj) ne "Net::IPv6Addr") { + $self->log->warn("failed to validate a suricata ipv6: $match: $_"); + return undef; + } + my $standardized = $ipobj->to_string_preferred(); + $self->add_entity($edb, $standardized, 'ipv6'); + my $span = $self->create_span($standardized, 'ipv6'); + my $pspan = HTML::Element->new('span'); + $pspan->push_content($span); + $pspan->push_content(":$port"); + return $pspan; +} + +sub email_action ($self, $match, $edb,$falsepos) { + # create a nested entity span > + my ($user, $domain) = split(/\@/, $match); + $domain = $self->deobsfucate_ipdomain($domain); + + # create entity span for domain + my $dspan = $self->create_span($domain, 'domain'); + $self->add_entity($edb, $domain, 'domain'); + + # create span for the entire email address + my $email = lc($user . '@' . $domain); + my $espan = HTML::Element->new( + 'span', + 'class' => 'entity email', + 'data-entity-type' => 'email', + 'data-entity-value' => $email, + ); + $espan->push_content($user, '@', $dspan); + $self->add_entity($edb, $email, 'email'); + return $espan; +} + +sub message_id_action ($self, $match, $edb,$falsepos) { + # msg_id might be wrapped in < >, so make it an html entity + my $msg_id = $match; + if ( $match !~ m/^<.*>$/ ) { + $msg_id =~ s/^<//; + } + $self->add_entity($edb, $msg_id, 'message_id'); + return $self->create_span($msg_id, 'message_id'); +} + +sub create_span ($self, $match, $et) { + # wrap match string in a span + my $element = HTML::Element->new( + 'span', + 'class' => "entity $et", + 'data-entity-type' => $et, + 'data-entity-value' => lc($match), + ); + $element->push_content($match); + return $element; +} + +sub create_span_preserve_case ($self, $match, $et) { + # wrap match string in a span + my $element = HTML::Element->new( + 'span', + 'class' => "entity $et", + 'data-entity-type' => $et, + 'data-entity-value' => $match, + ); + $element->push_content($match); + return $element; +} + +sub add_entity ($self, $edb, $match, $et) { + # add entity to the entity_db interim storage + $edb->{$et}->{lc($match)}++; +} + +sub add_entity_preserve_case ($self, $edb, $match, $et) { + # add entity to the entity_db interim storage + $edb->{$et}->{$match}++; +} + +sub deobsfucate_ipdomain ($self, $text) { + # remove things that obsfucate ipaddrs and domains + my @parts = split(/[\[\(\{]*\.[\]\)\}]*/, $text); + my $clear = join('.',@parts); + $self->log->debug("deobsfucating $text"); + $self->log->debug(" result= $clear"); + return $clear; +} + +sub parse_stringified ($self, $item, $edb, $falsepos) { + my @new = $self->parse($item, $edb, $falsepos); + my $result = $self->merge_elements(@new); +} + +sub parse_with_hint ($self, $hint, $item, $edb, $falsepos) { + my @new = $self->parse($item, $edb, $falsepos, $hint); + my $result = $self->merge_elements(@new); +} + +sub merge_elements ($self, @elements) { + my $result; + foreach my $element (@elements) { + $result .= (ref($element) eq "HTML::Element") + ? $element->as_HTML('') + : $element; + } + return $result; +} + +1; diff --git a/lib/Flair/Processor.pm b/lib/Flair/Processor.pm new file mode 100644 index 0000000..ff6ba0e --- /dev/null +++ b/lib/Flair/Processor.pm @@ -0,0 +1,869 @@ +package Flair::Processor; + +use lib '../../lib'; +use Mojo::Base -base, -signatures; +use Mojo::JSON qw(encode_json); +use Data::Dumper::Concise; +use Flair::Db; +use Flair::Parser; +use Flair::Util::Models; +use Flair::Images; +use Flair::Util::Timer; # qw(get_timer); +use Flair::Util::HTML; # qw(build_html_tree output_tree_html generate_span create_sentinel_flair); +use Flair::Util::Sparkline; # qw(contains_sparkline normalize_sparkline data_to_sparkline_svg); +use Mojo::Pg; +use File::Basename; +use DateTime; +use HTML::Element; +use HTML::TreeBuilder; +use File::Path qw(make_path); +use MIME::Base64; +use Digest::MD5 qw(md5_hex); + +# hold handle to logger +has 'log'; + +# get the db object from caller +has 'db'; + +# get the scotapi helper from caller +has 'scotapi'; + +has 'config'; + +# the parser class necessary to apply regexes against text +has 'parser' => sub ($self) { + $self->log->trace("Initializing Flair Parser"); + + # build parser and store in the parser slot + my $parser = Flair::Parser->new( + log => $self->log, + db => $self->db, + ); + return $parser; +}; + +sub do_flairing ($self, $job, @args) { + # main entry point to Flair Processing + # called by minion task "flairjob" + $self->log->info("Starting Flairing"); + + my $parser = $self->parser; # init the parser + $self->db->add_metric("flairjobs_requested", 1); + + my $request_data = $self->validate_data(@args); + if ( ! defined $request_data ) { + $self->log->error("Received JSON was incorrect.", {filter=>\&Dumper, value=>\@args}); + $self->db->add_metric("invalid_flairjob_request", 1); + return; + } + + my $timer = get_timer(); + my $results = $self->process_request($job, $request_data); + my $elapsed = &$timer; + + $self->store_job_metrics($job, $elapsed, $request_data, $results); + + my $send_data = $self->build_results($job, $request_data, $results); + $self->send_results($send_data); + $job->finish($send_data); + + my $found = $self->count_found_entities($results); + $self->db->add_metric("entities_found", $found); + $self->db->add_metric("completed_flairjobs", 1); + $self->db->add_metric("elapsed_flair_time", $elapsed); + $self->log->info("Completed Flairing"); +} + +sub store_job_metrics ($self, $job, $elapsed, $request_data, $results) { + my $size = $self->calculate_size($request_data); + my $ecount = $self->count_entities($results); + my $jobrec = { + job_id => $job->id, + duration => $elapsed, + imgduration => $results->{image_duration} // 0, + sourcelen => $size, + images => $results->{images_replaced}// 0, + entities => $ecount, + }; + my $jobrow = $self->db->jobs->create($jobrec); + $self->db->add_metric("parsed_data_size", $size); + $self->db->add_metric("images_replaced", $jobrec->{images}); +} + +sub build_results ($self, $job, $request_data, $results) { + my $type = $request_data->{type}; + my $id = $request_data->{id}; + my $send_data = { + target => { type => $type, id => $id }, + entities => $results->{entities}, + }; + + if ( $type eq "entry" ) { + $send_data->{text_flaired} = $results->{flair}; + $send_data->{text} = $results->{data}; + $send_data->{text_plain} = $results->{text}; + return $send_data; + } + $send_data->{alerts} = $results->{alerts}; + return $send_data; +} + +sub send_results ($self, $send_data) { + $self->log->trace("send data: ",{filter => \&Dumper, value =>$send_data}); + my $response = $self->scotapi->flair_update_scot4($send_data); + $self->log->trace("Update Response from SCOT: ",{filter=>\&Dumper, value => $response}); +} + +sub process_request ($self, $job, $request_data) { + $self->log->trace("Processing request: ",{filter=>\&Dumper, value => $request_data}); + my $type = $request_data->{type}; + $self->log->debug("Processing $type request"); + my $timer = get_timer(); + my $results = {}; + if ( $type eq 'entry' ) { + $results = $self->process_entry($job, $request_data); + } + elsif ( $type eq 'alertgroup' ) { + $results = $self->process_alertgroup($job, $request_data); + } + elsif ( $type eq "remoteflair") { + $results = $self->process_remoteflair($job, $request_data); + } + else { + return { error => 'unsupported flair_type' }; + } + $results->{elapsed_time} = &$timer; + $self->db->add_metric($type."_processed", 1); + return $results; +} + +sub count_found_entities ($self, $results) { + my $entities = $results->{entities}; + my $count = 0; + foreach my $t (keys %$entities) { + foreach my $v (keys %{$entities->{$t}}) { + $count++; + } + } + return $count; +} + + + +sub validate_data ($self, @args) { + # + ## make sure the input to the process is good + # expecting + # + # alertgroup + # { + # "id": 123, + # "type": "alertgroup", + # "data": { + # "alerts": [ + # { + # "id": 456, + # "row": { + # "col1": "data1", ... + # "colx": "datax" + # } + # }, + # ... + # ] + # } + # } + + # event + # { + # "id": 123, + # "type": "entry", + # "data": "string data to flair", + # } + + + $self->log->trace("validating:", {filter=>\&Dumper, value=>\@args}); + + my $href = $args[0]; + my $type = $href->{type}; + my $id = $href->{id}; + my $data = $href->{data}; + + if ( $type ne "alertgroup" + and $type ne "entry" + and $type ne "remoteflair") { + $self->log->warn("Invalid type in received json. type = $type"); + return undef; + } + + if ($id < 1) { + $self->log->warn("target id is invalid. id = $id"); + return undef; + } + + + # data should always be an object for alertgroups + if ($type eq "alertgroup" and ref($data) ne "HASH") { + $self->log->warn("type is alertgroup, but data is not a hash!"); + return undef; + } + + # data.alerts should be an array of hashes + if ($type eq "alertgroup" and ref($data->{alerts}) ne "ARRAY") { + $self->log->warn("type is alertgroup, but data.alerts is not an array"); + return undef; + } + if ($type eq "alertgroup" and ref($data->{alerts}->[0]) ne "HASH") { + $self->log->warn("type is alertgroup, but data.alerts.0 is not a hash"); + return undef; + } + + # entries should be not null + if ($type eq "entry" and $data eq ''){ + $self->log->warn("type is entry, but data is null string"); + return undef; + } + + # entries should not be scalars + if ($type eq "entry" and ref($data) ne '') { + $self->log->warn("type is entry, but data is a reference, not a string"); + return undef; + } + + return { + type => $type, + id => $id, + data => $data, + }; +} + +sub calculate_size ($self, $href) { + if ($href->{type} eq "entry" || $href->{type} eq "remoteflair") { + return length($href->{data}); + } + my $size = 0; + foreach my $alert (@{$href->{data}->{alerts}}) { + foreach my $cell (keys %{$alert->{row}}) { + # cell is a stringified json array, so just count chars + # as a rough approximation + $size += length($cell); + } + } + return $size; +} + +sub count_entities ($self, $results) { + my $edb = $results->{entities}; + my $count = 0; + foreach my $type (keys %$edb) { + foreach my $value (keys %{$edb->{$type}}) { + $count += $edb->{$type}->{$value}; + } + } + return $count; +} + +sub process_alertgroup ($self, $job, $request_data) { + + # given an alertgroup, split the data into alerts + # and process each alert + + my @results = (); + my $edb = {}; + my $falsepos= {}; + + foreach my $alert (@{$request_data->{data}->{alerts}}) { + $alert->{alertgroup} = $request_data->{id}; + my $alert_result = $self->flair_alert($alert, $falsepos); + push @results, $alert_result; + $self->merge_edb($edb, $alert_result->{entities}); + } + + $self->log->debug("Flaired Alerts Result ",{filter=>\&Dumper, value => \@results}); + + return { + type => 'alertgroup', + id => $request_data->{id}, + entities => $edb, + alerts => \@results, + job_id => $job->id, + }; +} + +sub process_entry ($self, $job, $request_data) { + + my $edb = {}; + my $falsepos = {}; + + my $img_timer = get_timer(); + + $self->log->debug("ORIG DATA is ",{filter=>\&Dumper, value=>$request_data->{data}}); + + # build "tree" structure of html + my $tree = build_html_tree($request_data->{data}); + + # descend through tree looking for images and cacheing them + my $imgmunger = Flair::Images->new(log => $self->log, + scotapi => $self->scotapi, + config => $self->config); + my $replacements = $imgmunger->process($tree); + + # if images were replaced, store the current state of the tree + # as the new "original" data. Otherwise, use the submitted + # text as the original. + my $orig_data = ($replacements) ? $tree->as_HTML + : $request_data->{data}; + + # walk the tree looking for flairables + $self->walk_tree($tree, $edb, $falsepos); + + # write the modified tree to html + my ($flaired_data, $plain_data) = output_tree_html($tree); + + $self->log->debug("FLAIRED DATA is $flaired_data"); + $self->log->debug("PLAIN DATA is $plain_data"); + + # trees can cause memory leaks if not deleted explicitly + $tree->delete; + + return { + type => 'entry', + id => $request_data->{id}, + data => $orig_data, + flair => $flaired_data, + text => $plain_data, + entities => $edb, + images_replaced => $replacements, + image_duration => &$img_timer, + }; +} + +sub process_remoteflair ($self, $job, $request_data) { + # assume that browser extension is procxied through scot api + my $edb = {}; + my $falsepos = {}; + my $orig_data = $request_data->{data}; + + # build "tree" structure of html + my $tree = build_html_tree($orig_data); + + # walk the tree looking for flairables + $self->walk_tree($tree, $edb, $falsepos); + + # write the modified tree to html + my ($flaired_data, $plain_data) = output_tree_html($tree); + + # trees can cause memory leaks if not deleted explicitly + $tree->delete; + + return { + type => 'remoteflair', + id => $request_data->{id}, + data => $orig_data, + flair => $flaired_data, + text => $plain_data, + entities => $edb, + }; +} + +sub walk_tree ($self, $element, $edb, $falsepos) { + # recursively descend into html tree, look for flair when leaf node is found + return if $element->is_empty; + + # concatenate adjacent text nodes + $element->normalize_content; + + # get the list of content nodes + my @content = $element->content_list; + my @new = (); # hold updated elements + + # examine each element in @content list + for (my $index = 0; $index < scalar(@content); $index++) { + + my $child = $content[$index]; + + if ($self->is_leaf_node($child)) { + # we are ready to parse for flair + push @new, $self->parser->parse($child, $edb, $falsepos); + next; + } + + if ($self->is_predefined_entity($child, $edb)) { + # add unmodified element, entity was added as side effect in the check + push @new, $child; + next; + } + + if ($self->is_no_flair_span($child)) { + $self->log->debug("No Flair Span found, skipping over"); + push @new, $child; + next; + } + + if ($self->is_flair_override_span($child)) { + $self->log->debug("Found a flair override span"); + push @new, $self->extract_flair_override($child, $edb); + next; + } + + # splunk used to do a weird thing with ipaddresses + # were the html looked like: 10.10.10.1 + # (but I dont think it does it anymore). If we encounder this again + # we would write something here to detect and re-write it. See + # SCOT3 flair for example + + # recurse into any "grandchilderen" + $self->walk_tree($child, $edb, $falsepos); + + # push any changes made below onto the new stack + push @new, $child; + } + # replace tree branches with changes held in @new + $element->splice_content(0, scalar(@content), @new); +} + +sub is_no_flair_span ($self, $node) { + # idea is to detect stuff + # or + # and skip over it + + if ( $self->node_is_span($node) ) { + return ($self->class_is($node, "noflair")); + } + return $self->node_is_noflair($node); + +} + +sub class_is ($self, $node, $match) { + my $class = $node->attr('class'); + return ($class eq $match); +} + +sub is_flair_override_span ($self, $node) { + # ideas is to detect sting + # and create an entity of type "type" without actually doing the parsing + if ($self->node_is_span($node)) { + my $class = $node->attr('class'); + return ($class eq "flairoverride"); + } + return undef; + +} + +sub extract_flair_override($self, $node, $edb) { + # get node text + my @content = $node->content_list(); + if (scalar(@content) > 1) { + $self->log->error("Nested content in flair override not supported!"); + return $node; + } + my $text = pop @content; + # get type + my $type = $node->attr('data-entity-type'); + # add it to edb + $edb->{$type}->{$text}++; + # rewrite to be a standard flair span + my $span = HTML::Element->new( + 'span', + 'class' => "entity $type", + 'data-entity-type' => $type, + 'data-entity-value' => lc($text), + ); + $span->push_content($text); + # return the element + return $span; +} + +sub is_leaf_node ($self, $child) { + return ref($child) eq ''; +} + +sub node_is_span ($self, $node) { + my $tag = $node->tag; + # $self->log->trace("node tag is $tag"); + return ($tag eq "span"); +} + +sub node_is_noflair ($self, $node) { + my $tag = $node->tag; + return ($tag eq "noflair"); +} + +sub is_predefined_entity ($self, $node, $edb) { + # entities could come in pre-defined from other services, or a copy + # of a flaired entry + # currently they must come in as + # bar + + my $tag = $node->tag; + return undef if $tag ne "span"; + + my $class = $node->attr('class'); + return undef if $class !~ /entity/i; + + my $type = $node->attr('data-entity-type'); + return undef if not defined $type; + + my $value = $node->attr('data-entity-value'); + return undef if not defined $value; + + # add to edb, but no transformation of child is necessary + # since it is already in "flair" form + $edb->{$type}->{$value}++; + return 1; +} + +sub flair_alert ($self, $alert, $falsepos) { + $self->log->trace("Alert data = ", {filter=>\&Dumper, value=>$alert}); + # process_alertgrop calls this + my $data = $alert->{row}; + my $flair = {}; # flaired text of alert by column + my $text = {}; # plain text of alert by column + my $alert_edb = {}; # entity found cache + + + # examine each column in the alert row + COLUMN: + foreach my $column (keys %$data) { + + $self->log->trace("Column = $column"); + + # skip over columns we do not want to process + next COLUMN if $self->is_skippable_column($column); + + # cell is an array ref of 1 or more text items + my $cell = $data->{$column}; + $self->log->trace("looking at column $column cell:", {filter=>\&Dumper, value=>$cell}); + + # detect sparkline data and draw a sparkline svg if present + # some alertgroups could have multiple sparklines + if (contains_multi_row_sparklines($cell)) { + $self->log->debug("Multi-row sparklines detected in columne $column: $cell"); + my $table = $self->build_multi_sparkline_table($cell); + $self->log->debug("---- TABLE built ----"); + $self->log->debug({filter => \&Dumper, value => $table}); + my $tresult = $self->flair_table($alert, $column, $table, $falsepos); + $flair->{$column} = $tresult->{flair}; + $text->{$column} = $tresult->{text}; + $self->merge_edb($alert_edb, $tresult->{entities}); + next COLUMN; + } + elsif (contains_sparkline($cell)) { + $self->log->debug("Sparkline detected in cell with column $column: $cell"); + my $sparkline = data_to_sparkline_svg($cell); + $self->log->debug("converted to $sparkline"); + $flair->{$column} = $sparkline; + # single sparkline data cells need no additional flairing + next COLUMN; + } + else { + $self->log->trace("NO SPARKLINES"); + } + + # descend into the cell and flair + my $cell_flair_result = $self->flair_cell($alert, $column, $cell, $falsepos); + + # build up data for this alert + $flair->{$column} = $cell_flair_result->{flair}; + $text->{$column} = $cell_flair_result->{text}; + + # Add found entities in cell to the alert edb + $self->merge_edb($alert_edb, $cell_flair_result->{entities}); + } + + # add this alert's results to alertgroup results + return { + id => $alert->{id}, + flair_data => $flair, + text_data => $text, + entities => $alert_edb, + }; +} + +sub build_multi_sparkline_table ($self, $data) { + my $table = HTML::Element->new('table'); + my $style = "border: 1px solid black; border-collapse: collapse;"; + $table->push_content( + ['tr'], + ['th', 'IOC'], + ['th', 'Indices'], + ['th', 'Hits'], + ['th', 'Sparkline'], + ); + foreach my $row (split(/\n/,$data)) { + next if ($row =~ /MULTILINE/); + my ($ioc, $hits, $sparkdata, $indices) = split(',',$row); + my $sparkline = data_to_sparkline_svg($sparkdata); + my $tr = HTML::Element->new('tr'); + $tr->push_content(['td', { style => $style }, $ioc]); + $tr->push_content(['td', { style => $style }, join('
', split(/:/,$indices))]); + $tr->push_content(['td', { style => $style }, $hits]); + $tr->push_content(['td', { style => $style }, $sparkline]); + $table->push_content($tr); + } + return $table->as_HTML(''); +} + +sub is_skippable_column ($self, $column) { + # add skippable column check here + return 1 if $column eq 'columns'; + return 1 if $column eq '_raw'; + return 1 if $column eq 'search'; + return undef; +} + +sub flair_cell ($self, $alert, $column, $cell, $falsepos) { + my $cell_type = $self->get_column_type($column); + my $alert_id = $alert->{id}; + my $ag_id = $alert->{alertgroup}; + + # hold the flairables + my $edb = {}; + my @flair = (); + my @text = (); + + # expect input in one of two ways: + # SCOT3 => array ref + # SCOT4 => string of format: '["item1",..."itemx"]' + + # if we get a SCOT4 input, change into an array + if ( ref($cell) ne "ARRAY" ) { + $cell = $self->arrayify_string($cell); + } + + foreach my $item (@$cell) { + next if $self->item_is_empty($item); + # process normal columns + if ( $cell_type eq "normal" ) { + my $item_result = $self->flair_normal_item($self->clean_html($item), $falsepos); + $self->merge_edb($edb, $item_result->{edb}); + push @flair, $item_result->{flair}; + push @text, $item_result->{text}; + next; + } + + # OK, we have a "special" cell type + my $item_result = $self->flair_special_item($alert, $cell_type, $column, $item, $falsepos); + # add the results to the cell + $self->merge_edb($edb, $item_result->{edb}); + # return the cell results to flair_alert + push @flair, $item_result->{flair}; + push @text, $item_result->{text}; + } + + # testing json array + return { + flair => \@flair, + text => \@text, + entities=> $edb, + }; + # stringify array return + return { + flair => $self->stringify_array(\@flair), + text => $self->stringify_array(\@text), + entities=> $edb, + }; +} + +sub flair_table ($self, $alert, $column, $table, $falsepos) { + # CTI submitted an array of sparkline data, that has been converted + # to a html table earlier in this process. we now flair it. + my $alert_id = $alert->{id}; + my $ag_id = $alert->{alertgroup}; + + my $edb = {}; + my @flair = (); + my @text = (); + + # table is an HTML::Element + my $result = $self->flair_table_item($self->clean_html($table), $falsepos); + push @flair, $result->{flair}; + push @text, $result->{text}; + + my $fref = { + flair => \@flair, + text => \@text, + entities => $result->{edb}, + }; + # $self->log->trace("flair_table result = ",{filter=>\&Dumper, value => $fref}); + return $fref; +} + +sub contains_table_html ($self, $item) { + # grep for table tag, return true if found + return grep { // } ($item); +} + +sub flair_normal_item ($self, $input, $falsepos) { + # send "normal" text to the parser to iterate through regular + # expression set. + my $edb = {}; + # special case, there may be an anchor tag present. if so, assume + # that it is there because they want to have a hyperlink therefore + # do not flair the hyperlink (which will "break" it) + if ($self->is_anchor($input)) { + $self->log->debug("Anchor detected: not parsing for flair"); + return { + flair => $self->process_anchor($input), + text => $input, + edb => $edb, + }; + } + my $flair = $self->parser->parse_stringified($input, $edb, $falsepos); + return { + flair => $flair, + text => $input, + edb => $edb, + }; +} + +sub flair_table_item ($self, $input, $falsepos) { + # we have encountered a sub-table that has an html table and will need to + # be parsed more like an entry + my $edb = {}; + my $tree = build_html_tree($input); + + $self->log->debug("flair_table_time with input = ".$tree->as_HTML('')); + + $self->log->debug("Walking tree of multi-row sparkline table"); + # descend into html tree to find entities + $self->walk_tree($tree, $edb, $falsepos); + + #$self->log->debug("Walked the tree"); + + # get the flaired data, in this case not really using plain_data + my ($flaired_cell_table, $plain_data) = output_tree_html($tree); + #$self->log->debug("EDB = ", {filter=>\&Dumper, value => $edb}); + #$self->log->debug("FALIR = ", {filter=>\&Dumper, value => $flaired_cell_table}); + + return { + flair => $flaired_cell_table, + text => $input, + edb => $edb, + }; +} + + +sub flair_special_item ($self, $alert, $cell_type, $column, $item, $falsepos) { + my $edb = {}; + my $text = $item; # assume we have text + my $flair; + + if ( $cell_type eq 'sentinel' ) { + return $self->flair_sentinel_type($item); + } + + if ( $cell_type eq "message_id" ) { + return $self->flair_message_id_type($item, $falsepos); + } + + # add future special types here like this: + # if ( $cell_type eq 'special_type' ) { + # $flair = $self->flair_whatever($item, $edb) + # return { flair => $flair, text => $text, edb => $edb } + # } + # or + # return $self->flair_whatever_type($item) if ($cell_type eq "whatever_type"); + + # default case: assume that the "type" of the cell === entity type + # and create a to mark the item + $edb->{$cell_type}->{lc($item)}++; + $flair = generate_span($cell_type, $item); + + return { flair => $flair, text => $text, edb => $edb }; +} + +sub flair_message_id_type ($self, $item, $falsepos) { + my $edb = {}; + my $flair = $self->parser->parse_with_hint('message_id', $item, $edb, $falsepos); + return { flair => $flair, text => $item, edb => $edb }; +} + +sub is_anchor ($self, $data) { + if ($data =~ //) { + return 1; + } + return undef; +} +sub process_anchor ($self, $data) { + # for now, just return the anchor string + return $data; + # later, we can do some validation, or other post processing + # on the anchor in this function +} + + +sub flair_sentinel_type ($self, $item) { + my $edb = { sentinel => { $item => 1}}; + my $flair = create_sentinel_flair($item); + return { flair => $flair, text => $item, edb => $edb }; +} + + +sub get_column_type ($self, $column) { + return 'message_id' if $column =~ /message[_-]id/i; + return 'uuid1' if $column =~ /^(lb){0,1}scanid$/i; + return 'filename' if $column =~ /^attachment[_-]name/i; + return 'filename' if $column =~ /^attachments$/i; + return 'sentinel' if $column =~ /^sentinel_incident_url$/i; + return 'normal'; +} + +sub merge_edb ($self, $existing, $new) { + # add newly found entities to an existing edb + # updated $existing as a side effect + foreach my $type (keys %{$new}) { + foreach my $entity_string (keys %{$new->{$type}}) { + $existing->{$type}->{$entity_string} += $new->{$type}->{$entity_string}; + } + } +} + +sub arrayify_string ($self, $string) { + # SCOT provides stringified arrays for alert cells + # of the format '["foo", "bar", "boom"]' + # this will turn that into an array with elements ("foo", "bar", "boom" ) + + my @a = split(/",[ ]*"/, $string); # ["foo , bar, boom"] + $a[0] =~ s/^\[\"//g; # foo, bar, boom"] + $a[-1] =~ s/\"\]$//g; # foo, bar, boom + return wantarray ? @a : \@a; +} + +sub stringify_array ($self, $arrayref) { + # create a string: ["item1", "item2", "itemX"] from an array ['item1', 'item2', 'item3'] + my $string = '[' + . join(', ', map { $self->wrapq($_); } @$arrayref) + . ']'; + return $string; +} + +sub wrapq ($self, $item) { + # wrap item in "" + my $q = "\""; + return join('', $q, $item, $q); +} + +sub get_html_tree ($self, $html) { + return build_html_tree($html); +} + +sub ensure_array ($self, $cell) { + # if cell is an array reference, return an array + # if not create an array and return it with cell as first element + return @$cell if (ref($cell) eq "ARRAY"); + return ( $cell ); +} + +sub item_is_empty ($self, $item) { + return 1 if ($item eq ''); + return 1 if not defined $item; + return undef; +} + +sub clean_html ($self, $item) { +# TODO! + return $item; +} + +1; diff --git a/lib/Flair/Regex.pm b/lib/Flair/Regex.pm new file mode 100644 index 0000000..3a571d7 --- /dev/null +++ b/lib/Flair/Regex.pm @@ -0,0 +1,528 @@ +package Flair::Regex; + +# core regular exporessions + +use Mojo::Base -base, -strict, -signatures; + +sub core_regex_names ($self) { + my @list = (qw( + cve + cidr + ipv6_suricata + ipv6_standard + ipv6_compressed + ipv6_cmp8colons + ipv6_mixed + ipv4 + uuid1 + clsid + md5 + sha1 + sha256 + message_id + email + lbsig + winregistry + files + domain_name + appkey + angle_bracket_msgid + jarm_hash + )); + return wantarray ? @list : \@list; +}; + +sub cve ($self ) { + return { + name => 'cve', + description => 'Find CVE-YYYYMMDD references', + regex => qr{ + \b + (CVE-(\d{4})-(\d{4,})) + \b + }xims, + entity_type => 'cve', + regex_type => 'core', + re_order => 10, + multiword => 0, + }; +} + +sub cidr ($self) { + return { + name => 'cidr', + description => 'Find CIDR blocks', + regex => qr{ + \b # word boundary + (? 'cidr', + regex_type => 'core', + re_order => 20, + multiword => 0, + }; +} + +sub ipv6_suricata ($self) { + return { + name => 'ipv6_suricata', + description => 'Find IPv6 adresses', + regex => qr{ + (?: + (?: + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + ) + (?::[0-9]+) + ) + }xims, + entity_type => 'suricata_ipv6', + regex_type => 'core', + re_order => 31, + multiword => 0, + }; +} + +sub ipv6_standard ($self) { + return { + name => 'ipv6_standard', + description => 'Find IPv6 adresses', + regex => qr{ + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + }xims, + entity_type => 'ipv6', + regex_type => 'core', + re_order => 32, + multiword => 0, + }; +} + +sub ipv6_compressed ($self) { + return { + name => 'ipv6_compressed', + description => 'Find IPv6 adresses', + regex => qr{ + (?= (?:[A-Z0-9]{0,4}:){0,7}[A-F0-9]{0,4}(?![:\w]) ) + ( ([0-9A-F]{1,4}:){1,7}|: )( (:[0-9A-F]{1,4}){1,7}|: ) + }xims, + entity_type => 'ipv6', + regex_type => 'core', + re_order => 33, + multiword => 0, + }; +} + +sub ipv6_cmp8colons ($self ) { + return { + name => 'ipv6_8colons', + description => 'Find IPv6 adresses', + regex => qr{ + (?:[A-F0-9]{1,4}:){7}:|:(:[A-F0-9]{1,4}){7}(?![:\w]) + }xims, + entity_type => 'ipv6', + regex_type => 'core', + re_order => 33, + multiword => 0, + }; +} + +sub ipv6_mixed ($self ) { + return { + name => 'ipv6_mixed', + description => 'Find IPv6 adresses', + regex => qr{ + (?: + # Non-compressed + (?:[A-F0-9]{1,4}:){6} + # Compressed with at most 6 colons + |(?=(?:[A-F0-9]{0,4}:){0,6} + (?:[0-9]{1,3}\.){3}[0-9]{1,3} # and 4 bytes + (?![:.\w]) + ) + # and at most 1 double colon + (([0-9A-F]{1,4}:){0,5}|:)((:[0-9A-F]{1,4}){1,5}:|:) + # Compressed with 7 colons and 5 numbers + |::(?:[A-F0-9]{1,4}:){5} + ) + # 255.255.255. + (?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3} + # 255 + (?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]) + }xims, + entity_type => 'ipv6', + regex_type => 'core', + re_order => 33, + multiword => 0, + }; +} + +sub ipv4 ($self) { + return { + name => 'ipv4', + description => 'Find IPv4 addresses', + regex => qr{ + \b # word boundary + (? 'ipaddr', + regex_type => 'core', + re_order => 50, + multiword => 0, + }; +} + +sub uuid1 ($self) { + return { + name => 'uuid1', + description => 'Find UUID1s', + regex => qr{ + [0-9a-f]{8} + \- + [0-9a-f]{4} + \- + 11[ef][0-9a-f] + \- + [89ab][0-9a-f]{3} + \- + [0-9a-f]{12} + }xims, + entity_type => 'uuid1', + regex_type => 'core', + re_order => 60, + multiword => 0, + }; +} + +sub clsid ($self) { + return { + name => 'clsid', + description => 'Find CLSIDs', + regex => qr{ + [a-fA-F0-9]{8} + \- + [a-fA-F0-9]{4} + \- + [a-fA-F0-9]{4} + \- + [a-fA-F0-9]{4} + \- + [a-fA-F0-9]{12} + }xims, + entity_type => 'clsid', + regex_type => 'core', + re_order => 70, + multiword => 0, + }; +} + +sub md5 ($self) { + return { + name => 'md5', + description => 'Find hex MD5 hashes', + regex => qr{ + \b + (?!.*\@\b)([0-9a-fA-F]{32}) + \b + }xims, + entity_type => 'md5', + regex_type => 'core', + re_order => 80, + multiword => 0, + }; +} + +sub sha1 ($self) { + return { + name => 'sha1', + description => 'Find hex SHA1 hashes', + regex => qr{ + \b + (?!.*\@\b)([0-9a-fA-F]{40}) + \b + }xims, + entity_type => 'sha1', + regex_type => 'core', + re_order => 90, + multiword => 0, + }; +} + +sub sha256 ($self) { + return { + name => 'sha256', + description => 'Find hex SHA256 hashes', + regex => qr{ + \b + (?!.*\@\b)([0-9a-fA-F]{64}) + \b + }xims, + entity_type => 'sha256', + regex_type => 'core', + re_order => 100, + multiword => 0, + }; +} + +sub message_id ($self) { + return { + name => 'message_id', + description => 'Find Email addresses', + regex => qr{ + (<|<) # starts with < or < + (?:[^\s]*?) # some nonblank chars + @ # an @ seperator + (?:[^\s]*?) # some nonblank chars + (>|>) # ends with > or > + }xims, + entity_type => 'message_id', + regex_type => 'core', + re_order => 109, + multiword => 1, + }; +} + +sub email ($self) { + return { + name => 'email', + description => 'Find Email addresses', + regex => qr{ + \b # word boundary + ( + (?: + # one or more of these + [\=a-z0-9!#$%&'*+/?^_`{|}~-]+ + # zero or more of these + (?:\.[\=a-z0-9!#$%&'*+/?^_`{|}~-]+)* + ) + @ + (?: + (?!\d+\.\d+) + (?=.{4,255}) + (?: + (?:[a-zA-Z0-9-]{1,63}(? 'email', + regex_type => 'core', + re_order => 110, + multiword => 0, + }; +} + +sub email_in_header ($self) { + return { + name => 'email', + description => 'email address from a message header', + regex => qr{ + \b + (?<=".*"\ <) + ( + (?: + # one or more of these + [\=a-z0-9!#$%&'*+/?^_`{|}~-]+ + # zero or more of these + (?:\.[\=a-z0-9!#$%&'*+/?^_`{|}~-]+)* + ) + @ + (?: + (?!\d+\.\d+) + (?=.{4,255}) + (?: + (?:[a-zA-Z0-9-]{1,63}(?) + }xims, + entity_type => 'email', + re_order => 108, + multiword => 1, + }; +} + + +sub lbsig ($self) { + return { + name => 'lbsig', + description => 'Find LaikaBoss Signatures', + regex => qr{ + \b # word boundary + (yr:[a-z\_]+_s[0-9]+)_[0-9]+ + \b + }xims, + entity_type => 'lbsig', + regex_type => 'core', + re_order => 120, + multiword => 0, + }; +} + +sub winregistry ($self) { + return { + name => 'winregistry', + description => 'Find Windows Registry Keys', + regex => qr{ + \b # word boundary + ( + (hklm|hkcu|hkey)[\\\w]+ + ) + \b + }xims, + entity_type => 'winregistry', + regex_type => 'core', + re_order => 130, + multiword => 0, + }; +} + +sub files ($self) { + return { + name => 'common_file_extensions', + description => 'Find Filenames with common extensions', + regex => qr{ + \b( + [0-9a-zA-Z_\-\.]+ + \. + ( + 7z|arg|deb|pkg|rar|rpm|tar|tgz|gz|z|zip| # compressed + aif|mid|midi|mp3|ogg|wav|wma| # audio + bin|dmg|iso|exe|bat| # executables + csv|dat|log|mdb|sql|xml| # db/data + eml|ost|oft|pst|vcf| # email + apk|bat|bin|cgi|exe|jar| # executable + fnt|fon|otf|ttf| # fonts + ai|bmp|gif|ico|jpeg|jpg|ps|png|psd|svg|tif|tiff| # images + asp|aspx|cer|cfm|css|htm|html|js|jsp|part|php|rss|xhtml| # web serving + key|odp|pps|ppt|pptx| # presentation + c|class|cpp|h|vb|swift|py|rb| # source code + ods|xls|xlsm|xlsx| #spreadsheats + cab|cfg|cpl|dll|ini|lnk|msi|sys| # misc sys files + 3g2|3gp|avi|flv|h264|m4v|mkv|mov|mp4|mpg|mpeg|vob|wmv| # video + doc|docx|odt|pdf|rtf|tex|txt|wpd| # word processing + jse|jar| + ipt| + hta| + mht| + ps1| + sct| + scr| + vbe|vbs| + wsf|wsh|wsc + ) + )\b + }xims, + entity_type => 'file', + regex_type => 'core', + re_order => 150, + multiword => 0, + }; +} + +sub domain_name ($self) { + return { + name => 'Domain_name', + description => 'Find Domain Names', + regex => qr{ + \b + ( + ( + (?= [a-z0-9-]{1,63} [\(\{\[]* \. [\]\}\)]*) + (xn--)? + [a-z0-9]+ + (-[a-z0-9]+)* + [\(\{\[]* + \. + [\]\}\)]* + )+ + ( + [a-z0-9-]{2,63} + ) + (?<=[a-z]) # prevent foo.12 from being a match + ) + \b + }xims, + entity_type => 'domain', + regex_type => 'core', + re_order => 160, + multiword => 0, + }; + +} + +sub appkey ($self) { + return { + name => 'Appkey', + description => 'Find Appkeys', + regex => qr{ + \b + ( + AppKey=([0-9a-f]){28} + ) + \b + }xims, + entity_type => 'appkey', + regex_type => 'core', + re_order => 170, + multiword => 0, + }; +} + +sub angle_bracket_msgid ($self) { + return { + name => 'Angle Bracket MessageId', + description => 'Find MessageIds wrapped in <>', + regex => qr{ + ( + (<|<) # starts with < + (?:[^\s]*?) # has some non blank chars + @ # followed by an @ + (?:[^\s]*?) # followed by more not blank chars + (>|>) # ends with > + ) + }xims, + entity_type => '', + regex_type => 'core', + re_order => 180, + multiword => 0, + }; +} + +sub jarm_hash ($self) { + return { + name => 'jarm_hash', + description => 'Find JARM hashes', + regex => qr{ + \b + (?!.*\@\b) + ([0-9a-fA-F]{62}) + \b + }xims, + entity_type => 'jarm_hash', + regex_type => 'core', + re_order => 190, + multiword => 0, + }; +} + +1; + diff --git a/lib/Flair/ScotApi.pm b/lib/Flair/ScotApi.pm new file mode 100644 index 0000000..b8eef27 --- /dev/null +++ b/lib/Flair/ScotApi.pm @@ -0,0 +1,146 @@ +package Flair::ScotApi; + +use lib '../../lib'; +use Data::Dumper::Concise; +use Mojo::UserAgent; +use Mojo::JSON qw(decode_json encode_json); +use Mojo::Base -base, -signatures; +use MIME::Base64; +use Try::Tiny::Retry ':all'; + +has 'log'; +has 'config'; + +# create the auth header +has auth_header => sub ($self) { + if (defined $self->config->{api_key}) { + return "apikey ".$self->config->{api_key}; + } + chomp( + my $enc = encode_base64(join(':', $self->config->{user}, $self->config->{pass})) + ); + return "basic $enc"; +}; + +# create the user agent +has ua => sub ($self) { + my $ua = Mojo::UserAgent->new(); + $ua->connect_timeout(30); + $ua->inactivity_timeout(30); + $ua->proxy->detect; + $ua->on(start => sub ($ua, $tx) { + $tx->req->headers->header( + Authorization => $self->auth_header + ) + }); + return $ua; +}; + +sub fetch ($self, $type, $id) { + my $uri = $self->build_uri_from_msg($type, $id); + return $self->get($uri); +} + +sub build_uri_from_msg ($self, $type, $id) { + return join('/', $self->config->{uri_root}, + $type, + $id); +} + +sub get ($self, $uri) { + my $tx = $self->ua->insecure($self->config->{insecure})->get($uri); + my $res = $tx->result; + my $code = $res->code; + + if ($code != 200) { + $self->log->error("Error GET $uri. Code = $code"); + die $code; + } + return decode_json($res->body); +} + +sub post ($self, $uri, $post_data) { + my $tx = $self->ua->insecure($self->config->{insecure})->post( + $uri => {Accept => '*/*'} => json => $post_data + ); + + my $res = $tx->result; + my $code= $res->code; + + if ( $code != 200 and $code != 202 ) { + $self->log->error("Error POST $uri. Code = $code"); + die $code." ".$uri; + } + return $res; +} + +sub put ($self, $uri, $put_data) { + my $tx = $self->ua->insecure($self->config->{insecure})->put( + $uri => {Accept => '*/*'} => json => $put_data + ); + my $res = $tx->result; + my $code = $res->code; + if ( $code != 200 and $code != 202 ) { + $self->log->error("Error PUT $uri. Code = $code"); + die $code; + } + return $res->body; +} + +sub patch ($self, $uri, $put_data) { + my $tx = $self->ua->insecure($self->config->{insecure})->patch( + $uri => {Accept => '*/*'} => json => $put_data + ); + my $res = $tx->result; + my $code = $res->code; + if ( $code != 200 and $code != 202 ) { + $self->log->error("Error PATCH $uri. Code = $code"); + die $code; + } + + return $res->body; +} + +sub delete ($self, $uri) { + my $tx = $self->ua->insecure($self->config->{insecure})->delete($uri); + my $res = $tx->result; + my $code = $res->code; + if ( $code != 200 and $code != 202 ) { + $self->log->error("Error PATCH $uri. Code = $code"); + die $code; + } + return $res->body; +} + +sub flair_update_scot4 ($self, $data) { + my $uri = $self->config->{uri_root}."/flair/flairupdate"; + + $self->log->debug("----> Posting to $uri data => ",{filter=>\&Dumper, value=>$data}); + + return retry { $self->post($uri, $data); } + on_retry { $self->log->warn("Exception caught, retrying flair update. $_"); } + delay_exp { 4, 5e6 } + catch { + $self->log->logdie("Failed to update flair! $_"); + return { error => "failed to update scot with flair" }; + }; +} + +sub upload_file_scot4 ($self, $filename) { + my $uri = $self->config->{uri_root}."/file/"; + my $data= { file => { file => $filename } }; + my $tx = $self->ua->insecure($self->config->{insecure})->post( + $uri => form => $data + ); + my $res = $tx->result; + my $code = $res->code; + if ($code != 200) { + $self->log->error("Error ($code) Uploading $filename"); + return { error => "failed to upload $filename to $uri" }; + } + my $id = $res->json->{id}; + return $id; +} + + +1; diff --git a/lib/Flair/Util/CSRFProtection.pm b/lib/Flair/Util/CSRFProtection.pm new file mode 100644 index 0000000..1cbd2e2 --- /dev/null +++ b/lib/Flair/Util/CSRFProtection.pm @@ -0,0 +1,53 @@ +package Flair::Util::CSRFProtection; + +# plugin to do CSRF Protection for MOJO ui/api + +use Mojo::Base 'Mojolicious::Plugin'; + +sub register { + my ( $self, $app ) = @_; + + my $routes = $app->routes; + + $app->helper( + 'reply.bad_csrf' => sub { + my ($c) = @_; + $c->res->code(403); + $c->render_maybe('bad_csrf') + or $c->render( text => 'Failed CSRF check' ); + return; + } + ); + + $routes->add_condition( + with_csrf_protection => sub { + my ( $route, $c ) = @_; + + my $csrf = $c->req->headers->header('X-CSRF-Token') + || $c->param('csrf_token'); + + $c->log->debug("csrf = $csrf"); + $c->log->debug("csrf_token = ".$c->csrf_token); + + unless ( $csrf && $csrf eq $c->csrf_token ) { + $c->reply->bad_csrf; + return; + } + + return 1; + } + ); + + $routes->add_shortcut( + with_csrf_protection => sub { + my ($route) = @_; + # mojo 9 + # return $route->requires( with_csrf_protection => 1 ); + return $route->requires( with_csrf_protection => 1 ); + } + ); + + return; +} + +1; diff --git a/lib/Flair/Util/Crypt.pm b/lib/Flair/Util/Crypt.pm new file mode 100644 index 0000000..546d513 --- /dev/null +++ b/lib/Flair/Util/Crypt.pm @@ -0,0 +1,66 @@ +package Flair::Util::Crypt; + +# export useful functions regarding crypto + +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw( + init_pbkdf + hash_pass + compare_pass + ); + +use strict; +use warnings; +use feature qw(say signatures); +no warnings qw(experimental::signatures); + +use Crypt::PBKDF2; +use Data::Dumper::Concise; + +sub init_pbkdf () { + my $pbkdf = Crypt::PBKDF2->new( + hash_class => 'HMACSHA2', + hash_args => { sha_size => 512 }, + iterations => 10000, + salt_len => 15, + ); + return $pbkdf; +} + +sub hash_pass ($string) { + my $p = init_pbkdf(); + return $p->generate($string); +} + +sub compare_pass ($hash, $string) { + my $p = init_pbkdf(); + return $p->validate($hash, $string); +} + +1; +__END__ +=head1 Name + +Flair::Util::Crypt + +=head1 Description + +Package of convenience functions for working with PBKDF2 hashes + +=head1 Synopsis + + use Flair::Util::Crypt qw(hash_pass compare_pass); + + my $hashed_password = hash_pass($input); + my $guess = "foo"; + if ( compare_pass($hashed_password, $guess) ) { + say "Good guess"; + } + +=head1 Author + +Todd Bruner (tbruner@sandia.gov) + +=cut + diff --git a/lib/Flair/Util/HTML.pm b/lib/Flair/Util/HTML.pm new file mode 100644 index 0000000..5bf5f71 --- /dev/null +++ b/lib/Flair/Util/HTML.pm @@ -0,0 +1,119 @@ +package Flair::Util::HTML; + +# export useful functions to do with html parsing and creation + +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(build_html_tree + output_tree_html + generate_span + create_sentinel_flair + create_message_id_flair + ); + +use strict; +use warnings; +use feature qw(say signatures); +no warnings qw(experimental::signatures); + +use HTML::TreeBuilder; +use HTML::Element; +use HTML::Entities; +use Data::Dumper::Concise; + +sub build_html_tree ($text) { + if ( $text !~ /^<.*>/ ) { # does not start with an html tag looking thing + $text = '
'.$text.'
'; # voila, html + } + + # TreeBuilder parses the "HTML" passed in and creates a tree structure + # for easy traversal + + my $tree = HTML::TreeBuilder->new; + $tree ->implicit_tags(1); + $tree ->p_strict(1); + $tree ->no_space_compacting(1); + $tree ->no_expand_entities(1); # prevent decodeing of html entities + $tree ->ignore_unknown(0); # allow stuff like
+ $tree ->parse_content($text); + $tree ->elementify; + return $tree; +} + +sub output_tree_html ($tree) { + # a $tree is full document, we really only want body + my $body = $tree->look_down('_tag', 'body'); + return undef if not defined $body; # no body, nothing to do + + my @content = $body->detach_content; + return undef if scalar(@content) < 1; # no content, nothing to do + + if (scalar(@content) == 1 and $content[0]->tag eq 'div') { + return $content[0]->as_HTML(''), $content[0]->as_text(); + } + + # wrap content into new div + my $div = HTML::Element->new('div'); + + # add the detatched content to new div + $div->push_content(@content); + + # return the html. + # the '' instructs function to not change anyting to html entities + # e.g. < to < + return $div->as_HTML(''), $div->as_text(); +} + +sub generate_span ($type, $item) { + # encode entities so things like internet_message_id's that have <> + # do not break the HTML display. If encode_entities is too aggressive + # we can specify limits to what encode_entities changes. (see perldoc HTML::Entities) + # e.g. encode_entities($foo, '<>&') would only replace < > and &. + my $encoded = encode_entities($item); + return qq|$encoded|; +} + +sub create_sentinel_flair ($url) { + my $image = HTML::Element->new( + 'img', + 'alt', 'View in Azure Sentinel', + 'src', '/images/azure-sentinel.png', + ); + my $anchor = HTML::Element->new( + 'a', + 'href', $url, + 'target', '_blank' + ); + $anchor->push_content($image); + return $anchor->as_HTML(''); # the '' prevents encoding of html entities +} + +sub create_message_id_flair ($item) { + return generate_span('message_id', $item); +} + + +1; +__END__ +=head1 Name + +Flair::Util::Tree + +=head1 Description + +Package of convenience functions for workign with/from HTML + +=head1 Synopsis + +my $html = "

Foo

10.10.10.1

" +my $tree = build_html_tree($html); +my $newhtml = output_tree($tree); + +=head1 Author + +Todd Bruner (tbruner@sandia.gov) + +=cut + diff --git a/lib/Flair/Util/LoadHashFile.pm b/lib/Flair/Util/LoadHashFile.pm new file mode 100644 index 0000000..ec4166b --- /dev/null +++ b/lib/Flair/Util/LoadHashFile.pm @@ -0,0 +1,37 @@ +package Flair::Util::LoadHashFile; + +# load a file containing a perl hash into memory + +use Mojo::Base -base, -signatures; +use Mojo::File qw(path curfile); +use Mojo::Util qw(decode); +use Data::Dumper::Concise; + +sub get_hash ($self, $filename) { + if (! -e $filename) { + die "$filename does not exist!"; + } + return $self->load($filename); +} + +sub load ($self, $file) { + return $self->parse( + decode('UTF-8', path($file)->slurp), $file + ); +} + +sub parse ($self, $content, $file) { + my $sandbox = qq{ +package Mojolicious::Plugin::Config::Sandbox; +no warnings; +use Mojo::Base -strict; +$content +}; + my $hash = eval $sandbox; + + die "Cant load hash from file $file: $@" if $@; + die "File did not return hash ref" unless ref $hash eq 'HASH'; + return $hash; +} + +1; diff --git a/lib/Flair/Util/Log.pm b/lib/Flair/Util/Log.pm new file mode 100644 index 0000000..3185c38 --- /dev/null +++ b/lib/Flair/Util/Log.pm @@ -0,0 +1,100 @@ +package Flair::Util::Log; + +# functions to set up logging + +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(log_init get_logger); + +use strict; +use warnings; +use feature qw(signatures say); +no warnings qw(experimental::signatures); + +use Log::Log4perl; +use Data::Dumper; + +sub get_logger ($logger="Flair") { + die "Logger not configured" unless Log::Log4perl->initialized(); + say "Returning Logger $logger"; + return Log::Log4perl->get_logger($logger); +} + +sub log_init($config=undef){ + return if Log::Log4perl->initialized(); + + # 1st, need to know if a simple string or a ref to a string is passed in. + # a simple string = a filename to look for + # a ref = string containing the log4perl config + + if (is_string_config($config)) { + Log::Log4perl->init_once($config); + return; + } + + my $fqn = find_config($config); + say "Found $fqn..."; + if (defined $fqn) { + Log::Log4perl->init_once($fqn); + return; + } + + # now we are in error situation, not a config string and log config file not found + # let's die for now, later we can think about a sane default + + die "Unable to Find Log Config File!"; + +} + +sub is_string_config ($config) { + return (ref($config) eq "SCALAR" and defined($config)); +} + +sub is_readable ($file) { + if ( -r $file ) { + return $file; + } + die "Unable to read log config file $file."; +} + +sub find_config ($filename) { + + if ( is_fully_qualified($filename) or is_relative_path($filename) ) { + return is_readable($filename); + } + + if ( is_tilde_path($filename) ) { + my $newname = glob($filename); + return is_readable($newname); + } + + my @paths = (qw( + . + ~/flair/etc + ~/Flair/etc + /opt/flair/etc + ./etc + )); + + foreach my $path (@paths) { + my $fqn = ( glob(join('/',$path, $filename)) )[0]; + next if ! defined $fqn; + return $fqn if (-r $fqn); + } + # woe is me, no config file + die "Unable to find log config file $filename in path: ".join(':',@paths); +} + +sub is_fully_qualified ($file) { + return ($file =~ /^\/.+/); +} + +sub is_tilde_path ($file) { + return ($file =~ /^~.+/); +} + +sub is_relative_path ($file) { + return ($file =~ /^\.+\/.+/); +} + +1; diff --git a/lib/Flair/Util/Models.pm b/lib/Flair/Util/Models.pm new file mode 100644 index 0000000..b9029ed --- /dev/null +++ b/lib/Flair/Util/Models.pm @@ -0,0 +1,55 @@ +package Flair::Util::Models; + +# load models into memory + +use lib '../../../lib'; +use Mojo::Pg; +use Flair::Model::Regex; +use Flair::Model::Metrics; +use Flair::Model::Files; +use Flair::Model::Apikeys; + +use Mojo::Base -base, -signatures; + +has 'log'; +has 'config'; + +has connstr => sub ($self) { + return $self->config->{pguri}; +}; + +has pg => sub ($self) { + return Mojo::Pg->new($self->connstr); +}; + +has regex => sub ($self) { + return Flair::Model::Regex->new( + pg => $self->pg, log => $self->log, config => $self->config->{model}->{regex} + ); +}; + +has metrics => sub ($self) { + return Flair::Model::Metrics->new( + pg => $self->pg, log => $self->log, config => $self->config->{model}->{metrics} + ); +}; + +has files => sub ($self) { + return Flair::Model::Files->new( + pg => $self->pg, log => $self->log, config => $self->config->{model}->{files} + ); +}; + +has apikeys => sub ($self) { + return Flair::Model::Apikeys->new( + pg => $self->pg, log => $self->log, config => $self->config->{model}->{apikeys} + ); +}; + +has admins => sub ($self) { + return Flair::Model::Admins->new( + pg => $self->pg, log => $self->log, config => $self->config->{model}->{admins} + ); +}; +1; + diff --git a/lib/Flair/Util/Sparkline.pm b/lib/Flair/Util/Sparkline.pm new file mode 100644 index 0000000..ef6575a --- /dev/null +++ b/lib/Flair/Util/Sparkline.pm @@ -0,0 +1,93 @@ +package Flair::Util::Sparkline; + +# functions to work with sparklines + +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw( + contains_sparkline + contains_multi_row_sparklines + normalize_sparkline_string + normalize_sparkline_array + normalize_new_sparkline_string + normalize_sparkline + data_to_sparkline_svg + ); + +use strict; +use warnings; +use feature qw(say signatures); +no warnings qw(experimental::signatures); + +use SVG::Sparkline; +use Data::Dumper::Concise; + +sub contains_sparkline ($cell) { + if (ref($cell) eq "ARRAY") { + my $data = $cell->[0]; + return 1 if ($data =~ /##__SPARKLINE__##/); + return undef; + } + return $cell =~ /##__SPARKLINE__##/; +} + +sub normalize_sparkline ($cell) { + # ##__SPARKLINE__##\n0\n1\n2\n0 + # this form of split will split on all whitespace + my @norm = split(' ', $cell); + shift @norm; # remove the __SPARKLINE__ + return @norm; +} + +sub normalize_sparkline_array ($cell) { + shift @$cell; + my @norm = grep { /\S+/ } @$cell; + return @norm; +} + +sub data_to_sparkline_svg ($cell) { + my @normalized = (ref($cell) eq "ARRAY") ? normalize_sparkline_array($cell) + : normalize_sparkline($cell); + my $svg = SVG::Sparkline->new( + Line => { + values => \@normalized, + color => 'blue', + height => 12, + } + ); + return $svg->to_string; +} + +sub contains_multi_row_sparklines ($cell) { + if (ref($cell) eq "ARRAY") { + my $data = $cell->[0]; + return 1 if ($data =~ /MULTILINE_SPARKLINE_TABLE/); + return undef; + } + if ($cell =~ /MULTILINE_SPARKLINE_TABLE/) { + return 1; + } + return undef; +} + + + +1; +__END__ +=head1 Name + +Flair::Util::Sparkline + +=head1 Description + +Package of convenience functions for working with Sparklines + +=head1 Synopsis + + +=head1 Author + +Todd Bruner (tbruner@sandia.gov) + +=cut + diff --git a/lib/Flair/Util/Timer.pm b/lib/Flair/Util/Timer.pm new file mode 100644 index 0000000..af88833 --- /dev/null +++ b/lib/Flair/Util/Timer.pm @@ -0,0 +1,75 @@ +package Flair::Util::Timer; + +# functions to create timers and format dates + +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(get_timer get_english_duration get_ymdhms_time); + +use strict; +use warnings; +use feature qw(say signatures); +no warnings qw(experimental::signatures); + +use DateTime; +use Time::Duration; +use Time::HiRes qw(gettimeofday tv_interval); +use Data::Dumper::Concise; + +sub get_timer ($title='', $log=undef) { + + my $start = [ gettimeofday ]; + my $msg = $title; + + return sub { + my $begin = $start; + my $elapsed = tv_interval($begin, [ gettimeofday ]); + + if ( defined $log ) { + my $m = sprintf "TIMER: %-30s => %10f seconds", $msg, $elapsed; + (ref($log) eq "Log::Log4perl::Logger") ? $log->info($m) : say $m; + } + return $elapsed; + } +} + +sub get_english_duration ($seconds) { + return duration_exact($seconds); +} + +sub get_ymdhms_time ($epoch=undef) { + my $dt = (defined $epoch) ? DateTime->from_epoch(epoch => $epoch) : DateTime->now(); + return join(" ",$dt->ymd, $dt->hms); +} + +1; +__END__ +=head1 Name + +Flair::Util::Timer + +=head1 Description + +Package of convenience functions for Time related matters in the Flair engine. + +=head1 Synopsis + + use Flair::Util::Timer; + + my $timer = get_timer("Foo Time"); + # do stuff, time passes + my $elapsed = &$timer; # elapsed is number of secondes between init and call + + my $log = Log::Log4perl->get_logger(); + my $timer = get_timer("Boom timer", $log); + # do stuff, time passes + my $elapsed = &$timer; + # log will contain line like: + # 2022/03/04 10:03:17 INFO [100998] TIMER: Boom timer => 3.3211345 seconds + +=head1 Author + +Todd Bruner (tbruner@sandia.gov) + +=cut + diff --git a/public/api.yaml b/public/api.yaml new file mode 100644 index 0000000..d788d32 --- /dev/null +++ b/public/api.yaml @@ -0,0 +1,1497 @@ +openapi: '3.0.2' +info: + title: Flair Web Api + version: '1.0' + description: "Interact with the Flair Engine" + contact: + name: "Todd Bruner" + email: "tbruner@sandia.gov" +servers: + - url: http://localhost/api/v1 +paths: + + # ########################################### + # /admins + # ########################################### + /admins: + get: + tags: + - Admins + operationId: list_admins + x-mojo-to: Admins#list + responses: + '200': + description: Retrieve a list of Admins + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Admin' + post: + tags: + - Admins + operationId: create_admins + x-mojo-to: Admins#create + requestBody: + $ref: '#/components/requestBodies/CreateAdmin' + responses: + '201': + description: Created an Admin + content: + application/json: + schema: + $ref: '#/components/schemas/Admin' + '400': + description: Error creating Admin + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + + /admins/{AdminId}: + get: + tags: + - Admins + operationId: fetch_admins + x-mojo-to: Admins#fetch + parameters: + - $ref: '#/components/parameters/AdminId' + responses: + '200': + description: Retrieve Admin by id + content: + application/json: + schema: + $ref: '#/components/schemas/Admin' + '404': + description: Admin Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + put: + tags: + - Admins + operationId: update_admins + x-mojo-to: Admins#update + requestBody: + $ref: '#/components/requestBodies/CreateAdmin' + responses: + '200': + description: Replace Admin + content: + application/json: + schema: + $ref: '#/components/schemas/Admin' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + patch: + tags: + - Admins + operationId: patch_admins + x-mojo-to: Admins#patch + requestBody: + description: Update Admins with properties to be changed + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/AdminRequiredProperties' + - type: object + properties: + Segment: + nullable: true + responses: + '200': + description: Patch Admin + content: + application/json: + schema: + $ref: '#/components/schemas/Admin' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Admin Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Admin + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + delete: + tags: + - Admins + operationId: delete_admins + x-mojo-to: Admins#delete + parameters: + - $ref: '#/components/parameters/AdminId' + responses: + '200': + description: Delete Admin + content: + application/json: + schema: + $ref: '#/components/schemas/Admin' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Admin + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + + /admins/count: + get: + tags: + - Admins + operationId: count_admins + x-mojo-to: Admins#count + parameters: + - $ref: '#/components/parameters/where' + responses: + '200': + description: Provide count of Admin Objects matching where clause + content: + application/json: + schema: + $ref: '#/components/schemas/Count' + + + # ################################################################################# + # /files routes + # ################################################################################# + # /files: + # get: + # tags: + # - Files + # operationId: list_files + # x-mojo-to: Files#list + # responses: + # '200': + # description: Rertieve a list of Files stored + # content: + # application/json: + # schema: + # type: array + # items: + # $ref: '#/components/schemas/File' + # post: + # tags: + # - Files + # operationId: create_file + # x-mojo-to: Files#create + # requestBody: + # $ref: '#/components/requestBodies/CreateFile' + # responses: + # '201': + # description: Created Files + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/File' + # '400': + # description: Error creating File + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # + # /files/{FileId}: + # get: + # tags: + # - Files + # operationId: fetch_files + # x-mojo-to: Files#fetch + # parameters: + # - $ref: '#/components/parameters/FileId' + # responses: + # '200': + # description: Retrieve File by id + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/File' + # '404': + # description: File Not Found + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # put: + # tags: + # - Files + # operationId: update_files + # x-mojo-to: Files#update + # requestBody: + # $ref: '#/components/requestBodies/CreateFile' + # responses: + # '200': + # description: Replace File + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/File' + # '403': + # description: Forbidden, insufficient privilege + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # '404': + # description: Regex Not Found + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # '400': + # description: Error Replacing Regex + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # patch: + # tags: + # - Files + # operationId: patch_files + # x-mojo-to: Files#patch + # requestBody: + # description: Update Files with properties to be changed + # content: + # application/json: + # schema: + # anyOf: + # - $ref: '#/components/schemas/FileRequiredProperties' + # - type: object + # properties: + # Segment: + # nullable: true + # responses: + # '200': + # description: Patch File + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/File' + # '403': + # description: Forbidden, insufficient privilege + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # '404': + # description: File Not Found + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # '400': + # description: Error Replacing File + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # delete: + # tags: + # - Files + # operationId: delete_files + # x-mojo-to: Files#delete + # parameters: + # - $ref: '#/components/parameters/FileId' + # responses: + # '200': + # description: Delete File + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/File' + # '403': + # description: Forbidden, insufficient privilege + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # '404': + # description: Regex Not Found + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # '400': + # description: Error Replacing File + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + # + # /files/count: + # get: + # tags: + # - Files + # operationId: count_files + # x-mojo-to: Files#count + # parameters: + # - $ref: '#/components/parameters/where' + # responses: + # '200': + # description: Provide count of File Objects matching where clause + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/Count' + # + # /download/{FileId}: + # get: + # tags: + # - Files + # operationId: download file + # x-mojo-to: Files#download + # parameters: + # - $ref: '#/components/parameters/FileId' + # responses: + # '200': + # description: Retrieve File Results by id + # + # '404': + # description: File Not Found + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/ErrorMessage' + + # ################################################################################# + # /flair routes + # ################################################################################# + /flair: + post: + tags: + - Flair + operationId: create_alertgroup_flair_job + x-mojo-to: FlairJob#create + requestBody: + $ref: '#/components/requestBodies/CreateFlairJob' + responses: + '202': + description: Initiate Flair of Object + content: + application/json: + schema: + $ref: '#/components/schemas/FlairJob' + '400': + description: Error Creating Flair Job + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + + get: + tags: + - Flair + operationId: list_flair_jobs + x-mojo-to: FlairJob#list + responses: + '200': + description: Provide List of Flair Jobs + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/FlairJob' + '404': + description: No Flair Jobs Found + content: + application/json: + schema: + $ref: '#/components/schemas/ListNotFound' + + /flair/{FlairJobId}: + get: + tags: + - Flair + operationId: retrieve_flair_job_results + x-mojo-to: FlairJob#fetch + parameters: + - $ref: '#/components/parameters/FlairJobId' + responses: + '200': + description: Retrieve FlairJob Results by id + content: + application/json: + schema: + $ref: '#/components/schemas/FlairJob' + '404': + description: FlairJob Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + + # ################################################################################# + # /apikeys routes + # ################################################################################# + /apikeys: + post: + tags: + - Apikeys + operationId: create_apikey + x-mojo-to: Apikeys#create + requestBody: + $ref: '#/components/requestBodies/CreateApikey' + responses: + '201': + description: Created Apikeys + content: + application/json: + schema: + $ref: '#/components/schemas/Apikey' + '400': + description: Error creating Apikeys + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + get: + tags: + - Apikeys + operationId: list_apikey + x-mojo-to: Apikeys#list + parameters: + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/offset' + - $ref: '#/components/parameters/fields' + - $ref: '#/components/parameters/order' + - $ref: '#/components/parameters/where' + responses: + '200': + description: Provide List of Apikey Objects + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Apikey' + '404': + description: No Regexes Found + content: + application/json: + schema: + $ref: '#/components/schemas/ListNotFound' + /apikeys/count: + get: + tags: + - Regex + operationId: count_apikeys + x-mojo-to: Apikeys#count + parameters: + - $ref: '#/components/parameters/where' + responses: + '200': + description: Provide count of Apikeys Objects matching where clause + content: + application/json: + schema: + $ref: '#/components/schemas/Count' + + /apikeys/{ApikeyId}: + get: + tags: + - Apikeys + operationId: fetch_apikeys + x-mojo-to: Apikeys#fetch + parameters: + - $ref: '#/components/parameters/ApikeyId' + responses: + '200': + description: Retrieve Apikey by id + content: + application/json: + schema: + $ref: '#/components/schemas/Apikey' + '404': + description: Apikey Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + put: + tags: + - Apikeys + operationId: update_apikey + x-mojo-to: Apikeys#update + requestBody: + $ref: '#/components/requestBodies/CreateApikey' + responses: + '200': + description: Replace Apikey + content: + application/json: + schema: + $ref: '#/components/schemas/Apikey' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + patch: + tags: + - Apikeys + operationId: patch_apikey + x-mojo-to: Apikeys#patch + requestBody: + description: Update Apikeys with properties to be changed + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/ApikeyRequiredProperties' + - type: object + properties: + Segment: + nullable: true + parameters: + - $ref: '#/components/parameters/ApikeyId' + responses: + '200': + description: Patch Apikey + content: + application/json: + schema: + $ref: '#/components/schemas/Apikey' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + delete: + tags: + - Apikeys + operationId: delete_apikeys + x-mojo-to: Apikeys#delete + parameters: + - $ref: '#/components/parameters/ApikeyId' + responses: + '200': + description: Delete Apikey + content: + application/json: + schema: + $ref: '#/components/schemas/Apikey' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + + # ################################################################################# + # /metrics routes + # ################################################################################# + /metrics: + post: + tags: + - Metrics + operationId: create_metric + x-mojo-to: Metrics#create + requestBody: + $ref: '#/components/requestBodies/CreateMetric' + responses: + '201': + description: Create Metric + content: + application/json: + schema: + $ref: '#/components/schemas/Metric' + '400': + description: Error creating Metric + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + get: + tags: + - Metrics + operationId: list_metrics + x-mojo-to: Metrics#list + parameters: + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/offset' + - $ref: '#/components/parameters/fields' + - $ref: '#/components/parameters/order' + - $ref: '#/components/parameters/where' + responses: + '200': + description: Provide List of Metric Objects + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Metric' + '404': + description: No Regexes Found + content: + application/json: + schema: + $ref: '#/components/schemas/ListNotFound' + /metrics/count: + get: + tags: + - Metrics + operationId: count_metrics + x-mojo-to: Metrics#count + parameters: + - $ref: '#/components/parameters/where' + responses: + '200': + description: Provide count of Metric Objects matching where clause + content: + application/json: + schema: + $ref: '#/components/schemas/Count' + + /metrics/{MetricId}: + get: + tags: + - Metrics + operationId: fetch_metrics + x-mojo-to: Metrics#fetch + parameters: + - $ref: '#/components/parameters/MetricId' + responses: + '200': + description: Retrieve Metric by id + content: + application/json: + schema: + $ref: '#/components/schemas/Metric' + '404': + description: Metric Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + put: + tags: + - Metrics + operationId: update_metric + x-mojo-to: Metrics#update + requestBody: + $ref: '#/components/requestBodies/CreateMetric' + responses: + '200': + description: Replace Apikey + content: + application/json: + schema: + $ref: '#/components/schemas/Metric' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Metric Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Metric + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + patch: + tags: + - Metrics + operationId: patch_metric + x-mojo-to: Metrics#patch + requestBody: + description: Update Metrics with properties to be changed + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/MetricRequiredProperties' + - type: object + properties: + Segment: + nullable: true + parameters: + - $ref: '#/components/parameters/MetricId' + responses: + '200': + description: Patch Metric + content: + application/json: + schema: + $ref: '#/components/schemas/Metric' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + delete: + tags: + - Metrics + operationId: delete_metric + x-mojo-to: Metrics#delete + parameters: + - $ref: '#/components/parameters/MetricId' + responses: + '200': + description: Delete Metric + content: + application/json: + schema: + $ref: '#/components/schemas/Metric' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Metric Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Metric + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + + # ################################################################################# + # /regex routes + # ################################################################################# + /regex: + post: + tags: + - Regex + operationId: create_regex + x-mojo-to: Regex#create + requestBody: + $ref: '#/components/requestBodies/CreateRegex' + responses: + '201': + description: Created Regex + content: + application/json: + schema: + $ref: '#/components/schemas/Regex' + '400': + description: Error Creating Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + get: + tags: + - Regex + operationId: list_regex + x-mojo-to: Regex#list + parameters: + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/offset' + - $ref: '#/components/parameters/fields' + - $ref: '#/components/parameters/order' + - $ref: '#/components/parameters/where' + responses: + '200': + description: Provide List of Regex Objects + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Regex' + '404': + description: No Regexes Found + content: + application/json: + schema: + $ref: '#/components/schemas/ListNotFound' + + /regex/count: + get: + tags: + - Regex + operationId: count_regex + x-mojo-to: Regex#count + parameters: + - $ref: '#/components/parameters/where' + responses: + '200': + description: Provide count of Regex Objects matching where clause + content: + application/json: + schema: + $ref: '#/components/schemas/Count' + + /regex/{RegexId}: + get: + tags: + - Regex + operationId: fetch_regex + x-mojo-to: Regex#fetch + parameters: + - $ref: '#/components/parameters/RegexId' + responses: + '200': + description: Retrieve Regex by id + content: + application/json: + schema: + $ref: '#/components/schemas/Regex' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + put: + tags: + - Regex + operationId: update_regex + x-mojo-to: Regex#update + requestBody: + $ref: '#/components/requestBodies/CreateRegex' + responses: + '200': + description: Replace Regex + content: + application/json: + schema: + $ref: '#/components/schemas/Regex' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + patch: + tags: + - Regex + operationId: patch_regex + x-mojo-to: Regex#patch + requestBody: + description: Update Regex with properties to be changed + content: + application/json: + schema: + anyOf: + - $ref: '#/components/schemas/RegexRequiredProperties' + - type: object + properties: + Segment: + nullable: true + parameters: + - $ref: '#/components/parameters/RegexId' + responses: + '200': + description: Patch Regex + content: + application/json: + schema: + $ref: '#/components/schemas/Regex' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + delete: + tags: + - Regex + operationId: delete_regex + x-mojo-to: Regex#delete + parameters: + - $ref: '#/components/parameters/RegexId' + responses: + '200': + description: Delete Regex + content: + application/json: + schema: + $ref: '#/components/schemas/Regex' + '403': + description: Forbidden, insufficient privilege + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '404': + description: Regex Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + '400': + description: Error Replacing Regex + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + +# ################################################################################# +# COMPONENT DEFINITIONS +# ################################################################################# +components: + schemas: + Count: + type: object + properties: + count: + type: integer + + Id: + type: integer + + ListNotFound: + type: object + properties: + code: + type: integer + format: int32 + message: + type: + string + + ErrorMessage: + type: object + properties: + code: + type: integer + format: int32 + message: + type: + string + + # ########################################################################### + # REGEX datastructures + # ########################################################################### + RegexId: + type: object + properties: + id: + type: integer + # $ref: '#/components/schemas/Id' + + RegexProperties: + type: object + properties: + name: + description: The name to help humans identify what regex we are talking about + type: string + description: + description: Describe what this Regex is trying to find + type: string + match: + description: this is the actual regex. See "perldoc perlre". + type: string + entity_type: + description: When we match this RE, assign this type to the Entity. + type: string + regex_type: + description: Udef = user defined. Core = SCOT built-in. + type: string + default: 'udef' + enum: + - core + - udef + re_order: + description: assign an integer so we can test regexes in user defined order. Lower numbers get tested first. + type: integer + multiword: + description: Multi-word RE's have spaces or other word breaking characters in them. This signal helps the flair engine efficiently search for these matches. + type: boolean + + RegexRequiredProperties: + type: object + required: + - name + - description + - match + - entity_type + - regex_type + - re_order + - multiword + + Regex: + allOf: + - $ref: '#/components/schemas/RegexId' + - $ref: '#/components/schemas/RegexProperties' + - $ref: '#/components/schemas/RegexRequiredProperties' + + # ########################################################################### + # FlairJob datastructures + # ########################################################################### + + FlairJobId: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + + FlairJobProperties: + type: object + properties: + id: + description: The SCOT ID of the item to be flaired. FLAIR will use this to update the item in SCOT. + type: integer + type: + description: The type of item being flaired. Valid types are "alertgroup", "entry", "remoteflair". + type: string + data: + description: Array of items you which to have flaired from this alertgroup. OR a string entry, or remoteflair. + oneOf: + - type: object + - type: string + + FlairJobRequiredProperties: + type: object + required: + - type + - id + - data + + FlairJob: + allOf: + - $ref: '#/components/schemas/FlairJobId' + - $ref: '#/components/schemas/FlairJobProperties' + - $ref: '#/components/schemas/FlairJobRequiredProperties' + + # ########################################################################### + # File datastructures + # ########################################################################### + + FileId: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + + FileProperties: + type: object + properties: + filename: + type: string + dir: + type: string + + FileRequiredProperties: + type: object + required: + - dir + - filename + + File: + allOf: + - $ref: '#/components/schemas/FileId' + - $ref: '#/components/schemas/FileProperties' + - $ref: '#/components/schemas/FileRequiredProperties' + + # ########################################################################### + # Admin datastructures + # ########################################################################### + + AdminId: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + + AdminProperties: + type: object + properties: + username: + description: The username for this admin. + type: string + who: + description: The full name (GECOS) for the admin. + type: string + pwhash: + description: The PBKDF2 hash of the password. + type: string + + AdminRequiredProperties: + type: object + required: + - username + - who + - pwhash + + Admin: + allOf: + - $ref: '#/components/schemas/AdminId' + - $ref: '#/components/schemas/AdminProperties' + - $ref: '#/components/schemas/AdminRequiredProperties' + + # ########################################################################### + # Apikey datastructures + # ########################################################################### + + ApikeyId: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + + ApikeyProperties: + type: object + properties: + username: + description: Tie this apikey to this username. For human convenience. + type: string + apikey: + description: the guid based api key + type: string + flairjob: + description: can this apikey submit flairjobs + type: boolean + regex_ro: + description: can this apikey read-only regex records + type: boolean + regex_crud: + description: can this apikey create, read, update and delete regex records + type: boolean + metrics: + description: can this apikey access metrics + type: boolean + + ApikeyRequiredProperties: + type: object + required: + - username + + Apikey: + allOf: + - $ref: '#/components/schemas/ApikeyId' + - $ref: '#/components/schemas/ApikeyProperties' + - $ref: '#/components/schemas/ApikeyRequiredProperties' + + # ########################################################################### + # Metric datastructures + # ########################################################################### + + MetricId: + type: object + properties: + id: + $ref: '#/components/schemas/Id' + + MetricProperties: + type: object + properties: + year: + description: the 4 digit year + type: integer + month: + description: the month where January = 1 and December = 12 + type: integer + day: + description: the numeric day of the month + type: integer + hour: + description: the hour of the day 0 = midnight + type: integer + metric: + description: The metric we are creating + type: string + value: + description: the metric value (number) + type: number + + MetricRequiredProperties: + type: object + required: + - year + - month + - day + - hour + - metric + - value + + Metric: + allOf: + - $ref: '#/components/schemas/MetricId' + - $ref: '#/components/schemas/MetricProperties' + - $ref: '#/components/schemas/MetricRequiredProperties' + + + # ################################################################################## + # Request datastructures + # ################################################################################## + + requestBodies: + + CreateRegex: + description: Create a new Regex + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/RegexProperties' + - $ref: '#/components/schemas/RegexRequiredProperties' + + CreateFlairJob: + description: Create a new Flair Job for submission + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/FlairJobProperties' + - $ref: '#/components/schemas/FlairJobRequiredProperties' + + CreateApikey: + description: Create a new Flair Apikey + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/ApikeyRequiredProperties' + + CreateFile: + description: Create a new Flair File + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/FileProperties' + - $ref: '#/components/schemas/FileRequiredProperties' + + CreateMetric: + description: Create a new Flair Metric + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/MetricProperties' + - $ref: '#/components/schemas/MetricRequiredProperties' + + CreateAdmin: + description: Create a new Flair Admin + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/AdminProperties' + - $ref: '#/components/schemas/AdminRequiredProperties' + + + # ################################################################################## + # Parameter definitions + # ################################################################################## + + parameters: + + AdminId: + name: AdminId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + + RegexId: + name: RegexId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + + FileId: + name: FileId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + + FlairJobId: + name: FlairJobId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + + ApikeyId: + name: ApikeyId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + + MetricId: + name: MetricId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + + StatusId: + name: StatusId + in: path + required: true + schema: + $ref: '#/components/schemas/Id' + + limit: + name: limit + in: query + required: false + schema: + type: integer + + offset: + name: offset + in: query + required: false + schema: + type: integer + + fields: + name: fields + in: query + required: false + schema: + type: array + items: + type: string + style: form + explode: true + + order: + name: order + in: query + required: false + schema: + type: array + items: + type: string + style: form + explode: true + + where: + name: where + in: query + required: false + schema: + type: array + items: + type: string + style: form + explode: true diff --git a/public/flair-banner.png b/public/flair-banner.png new file mode 100644 index 0000000..f503018 Binary files /dev/null and b/public/flair-banner.png differ diff --git a/public/flair-page-logo.png b/public/flair-page-logo.png new file mode 100644 index 0000000..04314b8 Binary files /dev/null and b/public/flair-page-logo.png differ diff --git a/public/flair.jpeg b/public/flair.jpeg new file mode 100644 index 0000000..95a06eb Binary files /dev/null and b/public/flair.jpeg differ diff --git a/public/functions.js b/public/functions.js new file mode 100644 index 0000000..c7078fc --- /dev/null +++ b/public/functions.js @@ -0,0 +1,66 @@ +function list(type) { + window.location.href = "/flair/dt/"+type; +} + +function create_new(type) { + window.location.href = "/flair/new/"+type; +} + +function buildJsonData(columns) { + let jsonData = {}; + for (const n of columns) { + jsonData[n] = document.getElmentById(n).value; + } + return jsonData; +} + +function put(type, element_id, columns) { + const id = document.getElementById(element_id).value; + const url = '/api/v1/'+type+'/'+id; + let jsonData = buildJsonData(columns); + fetch(url, { + method: 'PUT', + mode: 'same-origin', + cache: 'no-cache', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + redirects: 'follow', + body: JSON.stringify(jsonData) + }).then((data) => { + console.log(data); + location.reload(); + }); +} + +function del(type, element_id) { + const id = document.getElementById(element_id).value; + const url = '/api/v1/'+type+'/'+id; + fetch(url, { + method: 'DELETE', + mode: 'same-origin', + cache: 'no-cache', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + redirect: 'follow', + }).then((data) => { + console.log(data); + list(type); + }); +} + +function post(type, columns) { + const url = "/api/v1/"+type; + let jsonData = buildJsonData(columns); + fetch(url, { + method: 'POST', + mode: 'same-origin', + cache: 'no-cache', + credentials: 'same-origin', + headers: { 'Content-Type': 'application/json' }, + redirect: 'follow', + body: JSON.stringify(jsonData) + }).then((data) => { + console.log(data); + window.location.href = "/flair/dt/"+type; + }); +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..c82d60e --- /dev/null +++ b/public/index.html @@ -0,0 +1,25 @@ + + + + FLAIR! + + + +
+

Flair administriation user interface

+
+ + + + + + +
+ + diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..54fb0f1 --- /dev/null +++ b/public/style.css @@ -0,0 +1,57 @@ +body { + font-family: Arial, Helvetica, sans-serif; + background-color: GhostWhite; +} +textarea { + font-family: monospace; +} +table { + border-colapse: collapse; + width: 100%; +} + +tr:nth-child(odd) { + background-color: LightGray; +} +td { + padding: 0px; font-size: .7em; +} +th { + padding 0px; +} +.toptitle { + background-color: DodgerBlue; + text-align: center; +} +.subtitle { + background-color: Cornsilk; + text-align: center; + vertical-align: middle; +} +.navButton { + color: white; + text-decoration: none; + font-family: Helvetica, Arial, sans-serif; + font-size: 14px; + text-align: center; + padding: 0 30px; + line-height: 30px; + display: inline-block; + position: relative; + border-radius: 20px; + background-image: linear-gradient(#335b71 45%, #03324c 55%); + box-shadow: 0 2px 2px #888888; + transition: color 0.3s, background-image 0.5s, ease-in-out; +} +.navButton:hover { + background-image: linear-gradient(#b1ccda 49%, #96b4c5 51%); + color: #03324c; +} + +.pagebanner { + display: table; +} + +#pagelogo { + vertical-align: middle; +} diff --git a/script/Flair b/script/Flair new file mode 100755 index 0000000..44bf7e7 --- /dev/null +++ b/script/Flair @@ -0,0 +1,11 @@ +#!/opt/perl/bin/perl + +use strict; +use warnings; + +use Mojo::File qw(curfile); +use lib curfile->dirname->sibling('lib')->to_string; +use Mojolicious::Commands; + +# Start command line interface for application +Mojolicious::Commands->start_app('Flair'); diff --git a/setup.pl b/setup.pl new file mode 100755 index 0000000..9cb4aff --- /dev/null +++ b/setup.pl @@ -0,0 +1,186 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict, -signatures; +use Mojo::SQLite; +use Mojo::File; +use Data::Dumper::Concise; +use JSON; +use Minion; +use Getopt::Long; + +use lib './lib'; +use Flair::Config qw(build_config); +use Flair::Util::LoadHashFile; +use Flair::Db; +use Flair::Util::Log; +use Flair::Util::Crypt qw(hash_pass); + +#if ($> != 0) { +# die "You must be root. Try: sudo $0\n"; +#} + +# use the same config as the app +my $config = build_config(); # create config and set defaults in ENV vars + +my $logdir = $ENV{'S4FLAIR_LOG_DIR'} // '/opt/flair/var/log'; +if ( ! -d $logdir ) { + system("mkdir -p $logdir"); + system("chmod 0750 $logdir"); +} + +# note: this is simplistic and should work 99% of the time +# The other things that can go wrong that will not catch: +# disk full +# mounted read only +# A better test would be to try and open a test file for writing +# and examining error code. maybe next time I'll implement + +my $vardir = '/opt/flair/var'; +if (! -w $vardir) { + system("chown 7777:7777 $vardir"); +} + + +# get these from helm environment or use defaults +my $dbfile = $config->{install}->{dbfile}; +my $admin_user = $config->{install}->{admin_user}; +my $admin_pass = $config->{install}->{admin_pass}; +my $admin_gecos = $config->{install}->{admin_gecos}; +my $instdir = $config->{install}->{instdir}; +my $flair_user = $config->{install}->{flair_user}; +my $flair_group = $config->{install}->{flair_group}; +my $core_regexes = $config->{install}->{core_regexes_file}; +my $udef_regexes = $config->{install}->{udef_regexes_file}; +my $db_migration = $config->{install}->{db_migration}; +my $logfile = $config->{log}->{config}; +my $udefnow; + +my $wipedb; +my $clean; + +GetOptions( + 'clean' => \$clean, + 'wipedb' => \$wipedb, +) or die < [ + { + name => 'regex_name', + description => 'longer description of regex', + match => q{\b regex \b}, + entity_type => 'name of entity', + regex_type => 'udef', + re_order => 101, # lower orders take precedence in matching + multiword => 0, # or 1 if match spans word boundaries + }, + ... + ] + } + +EOF + +log_init(\$logfile); +my $log = get_logger('Flair'); + +die "Invalid Install Dir" if ($instdir eq '/' or $instdir eq ' ' or $instdir eq ''); + +# this should be handled in docker file +# but in case leaving it here, not expecting much from it +my $ugscript = << "EOF"; + +if grep --quiet -c $flair_group: /etc/group; then + echo "$flair_group exists, reusing..." +else + groupadd $flair_group +fi + +if grep --quiet -c $flair_user: /etc/passwd; then + echo "$flair_user exists, reusing..." +else + useradd -c "Flair User" -g $flair_group -d $instdir -M -s /bin/bash $flair_user +fi + +EOF +# system($ugscript); + +# handled in Dockerfile, I hope +#if (-d $instdir) { +# if ($clean) { +# system("rm -rf $instdir"); +# system("mkdir -p $instdir"); +# } +#} +#else { +# system("mkdir -p $instdir"); +#} + +# more Dockerfile +my $copyscript = << "EOF"; + tar -exclude-vcs -cf - . | (cd $instdir; tar xvf -) +EOF +# system($copyscript); + +if ( ! -e $dbfile ) { + system("touch $dbfile"); +} + +my $db = Flair::Db->new( + log => $log, + config => { + uri => 'file:'.$dbfile, + model => {}, + migration => $db_migration, + } +); +die "Unable to connect to $dbfile" if (! defined $db); + +if ($wipedb) { + unlink $dbfile || die "Unable to remove $dbfile"; + system("touch $dbfile"); + $db->dbh->migrations->from_file($db_migration)->migrate(0)->migrate; + $db->apikeys->create({ + username => 'scotapi', + apikey => $config->{flair_api_key}, + flairjob => 1, + regex_ro => 1, + regex_crud => 1, + metrics => 1, + }); + upsert_regexes($core_regexes); + if (-e $udef_regexes) { + upsert_regexes($udef_regexes); + } +} + +my $minion = Minion->new(SQLite => $config->{database}->{uri}); + +my $admin_model = $db->admins; +my $list_opts = {}; +my $admin_href = $admin_model->get_admin($admin_user, "log"); + +if (! defined $admin_href) { + my $record = { + pwhash => hash_pass($admin_pass), + username => $admin_user, + who => $admin_gecos, + }; + $db->admins->create($record); +} + +sub upsert_regexes ($file) { + my $regexes = Flair::Util::LoadHashFile->new->get_hash($file); + foreach my $re (@{ $regexes->{regexes} }) { + $log->debug("Loading Udef Regex: ".$re->{name}); + $log->debug(" : ".$re->{description}); + my $href = $db->regex->upsert_re($re); + $log->debug("regex_id : ".$href->{regex_id}); + } +} + diff --git a/t/api/all.t b/t/api/all.t new file mode 100755 index 0000000..de1c959 --- /dev/null +++ b/t/api/all.t @@ -0,0 +1,11 @@ +#!/usr/bin/env perl + +use TAP::Harness; +my %args = ( verbosity => 1 ); +my $harness = TAP::Harness->new(\%args); +$harness->runtests( + './regex.t', + './apikeys.t', + './metrics.t', + # './files.t', +); diff --git a/t/api/apikeys.t b/t/api/apikeys.t new file mode 100755 index 0000000..4d20a71 --- /dev/null +++ b/t/api/apikeys.t @@ -0,0 +1,187 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use Test::Mojo; +use Storable qw(dclone); +use JSON; +use Log::Log4perl::Level; + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my $auth = {authorization => 'apikey flairtest123'}; +my $config = { + database => { + dbtype => 'sqlite', + dbfile => 'file:/var/flair/test.db', + model => {}, + migration => '../../etc/test.sqlite.sql', + }, + mode => 'development', + logconf => 'testlog.conf', +}; + +my $db = Flair::Db->new( + log => $log, + config => $config->{database}, +); + +# $ENV{MOJO_MIGRATIONS_DEBUG} = 1; +my $ddf = $config->{database}->{migration}; +$log->debug("applying database migrations from $ddf"); + +$db->dbh->migrations->from_file($ddf)->migrate(0)->migrate; + +my $t = Test::Mojo->new('Flair', $config); + +## +## test the creation of an apikey via the api +## + +my $apikey_data = { + username => 'foobar', + key => '11111111111111111111111111', + flairjob => JSON::true, + regex_ro => JSON::true, + regex_crud => JSON::true, + metrics => JSON::true, +}; + +my $expected = dclone($apikey_data); +$expected->{id} = 2; + +$log->debug("INSERT TESTS ======================================"); + +# NOTE: only testing some of the returned keys +# because json_is forces the expected value into a string +# and this messes up the booleans above. Also, updated +# field is set at insert time and I do not have an easy +# way to see if we are "close" + +$t->post_ok('/api/v1/apikeys' => $auth => json => $apikey_data) + ->status_is(201) + ->json_is('/apikey_id' => 2, "Correct apikey_id") + ->json_is('/key' => $apikey_data->{key}, "Correct key") + ->json_is('/username' => $apikey_data->{username}, "Correct Username"); + +say Dumper($t->tx->res->json); +done_testing(); +exit 0; + +my $apikeydata2 = { + username => 'zoobar', + key => '2222222222222222222222222', + flairjob => JSON::true, + regex_ro => JSON::false, + regex_crud => JSON::false, + metrics => JSON::false, + +}; +my $expected2 = dclone($apikeydata2); +$expected2->{id} = 3; +$t->post_ok('/api/v1/apikeys' => json => $apikeydata2) + ->status_is(201) + ->json_is('/apikey_id' => 2, "Correct apikey_id") + ->json_is('/key' => $apikeydata2->{key}, "Correct key") + ->json_is('/username' => $apikeydata2->{username}, "Correct Username"); + +my $list_expected = [ $expected2, $expected ]; +$log->debug("LIST TESTS ======================================"); +$t->get_ok("/api/v1/apikeys?limit=0&offset=10") + ->status_is(200) + ->json_is('/0/apikey_id' => 2, "Apikey 0 id correct") + ->json_is('/0/key' => $apikeydata2->{key}, "Apikey 0 key correct") + ->json_is('/1/apikey_id' => 1, "Apikey 1 id correct") + ->json_is('/1/key' => $apikey_data->{key}, "Apikey 1 key correct"); + + +$log->debug("FETCH TESTS ======================================"); +$t->get_ok("/api/v1/apikeys/1") + ->status_is(200) + ->json_is('/apikey_id' => 1, "Correct apikey_id") + ->json_is('/key' => $apikey_data->{key}, "Correct key") + ->json_is('/username' => $apikey_data->{username}, "Correct Username"); + + +$t->get_ok("/api/v1/apikeys/2") + ->status_is(200) + ->json_is('/apikey_id' => 2, "Correct apikey_id") + ->json_is('/key' => $apikeydata2->{key}, "Correct key") + ->json_is('/username' => $apikeydata2->{username}, "Correct Username"); + +my $redata_updated = { + username => 'foobar', + key => '91111111111111111111111111', + flairjob => JSON::false, + regex_ro => JSON::true, + regex_crud => JSON::false, + metrics => JSON::false, +}; + +my $expected3 = dclone($redata_updated); +$expected3->{id} = 1; + +$log->debug("UPDATE TESTS ======================================"); +$t->put_ok("/api/v1/apikeys/1" => json => $redata_updated) + ->status_is(200) + ->json_is('/apikey_id' => 1, "Correct apikey_id") + ->json_is('/key' => $redata_updated->{key}, "Correct key"); + +#print Dumper($t->tx->res->json), "\n"; +#done_testing(); +#exit 0; + +my $redata_patch = { + username => 'boombaz', + key => '11111111111111111111111111', +}; + +my $expected4 = dclone($expected3); +$expected4->{username} = $redata_patch->{username}; +$expected4->{key} = $redata_patch->{key}; + +$log->debug("PATCH TESTS ======================================"); +$t->patch_ok("/api/v1/apikeys/1" => json => $redata_patch) + ->status_is(200) + ->json_is('/username' => $expected4->{username}, "username updated") + ->json_is('/key' => $expected4->{key}, "Key updated"); + + +$log->debug("DELETE TESTS ======================================"); +$t->delete_ok("/api/v1/apikeys/1") + ->status_is(200) + ->json_is('/apikey_id' => 1, "Got deleted id"); +$t->get_ok("/api/v1/apikeys/1") + ->status_is(200) + ->json_is('' => undef, "deleted item not found"); + +$log->debug("COUNT TESTS ======================================"); +$t->get_ok("/api/v1/apikeys/count" => json => {}) + ->status_is(200) + ->json_is('' => { count => 1 }, "Correct Number of rows left"); + + +# say Dumper($t->tx->res->json); +done_testing(); +exit 0; + + + + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} diff --git a/t/api/flairjob.t b/t/api/flairjob.t new file mode 100755 index 0000000..65127d9 --- /dev/null +++ b/t/api/flairjob.t @@ -0,0 +1,70 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use Test::Mojo; +use Storable qw(dclone); +use JSON; +use Log::Log4perl::Level; + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my $auth = { authorization => 'apikey flairtest123' }; +my $config = { + database => { + dbtype => 'sqlite', + dbfile => 'file:/var/flair/test.db', + model => {}, + migration => '../../etc/test.sqlite.sql', + }, + mode => 'development', + logconf => 'testlog.conf', +}; + +my $db = Flair::Db->new(log => $log, config => $config->{database}); +my $ddf = $config->{database}->{migration}; +$db->dbh->migrations->from_file($ddf)->migrate(0)->migrate; + +my $t = Test::Mojo->new('Flair', $config); + +# submit an Alertgroup +my $sub_ag_1 = { + type => 'alertgroup', + id => 100, + data => [ + { id => 200, columns => [ 'foo', 'boom' ], data => {foo => '["bar","baz"]', boom => '["10.10.10.1"]' } }, + { id => 201, columns => [ 'foo', 'boom', ], data => {foo => '["bir","biz"]', boom => '["20.20.20.2"]' } }, + ] +}; + +$t->post_ok('/api/v1/flair' => $auth => json => $sub_ag_1) + ->status_is(202); + +my $job_id = $t->tx->res->json->{job_id}; + +$log->debug("running perform_jobs"); +$t->app->minion->perform_jobs; +$log->debug("after perform_jobs"); + +$t->get_ok("/api/v1/flair/$job_id" => $auth)->status_is(200) + ->json_is('/alerts/0/entities/ipaddr/10.10.10.1', 1) + ->json_is('/alerts/0/flair_data/boom','["10.10.10.1"]') + ->json_is('/alerts/1/entities/ipaddr/20.20.20.2', 1) + ->json_is('/alerts/1/flair_data/foo', '["bir", "biz"]') + ->json_is('/entities/ipaddr/10.10.10.1', 1) + ->json_is('/entities/ipaddr/20.20.20.2', 1); +#say Dumper($t->tx->res->json); + + + +# say Dumper($t->tx->res->json); +done_testing(); +exit 0; diff --git a/t/api/metrics.t b/t/api/metrics.t new file mode 100755 index 0000000..9584454 --- /dev/null +++ b/t/api/metrics.t @@ -0,0 +1,144 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use Test::Mojo; +use Storable qw(dclone); +use JSON; +use Log::Log4perl::Level; +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my $auth = { authorization => 'apikey flairtest123' }; +my $config = { + database => { + dbtype => 'sqlite', + dbfile => 'file:/var/flair/test.db', + model => {}, + migration => '../../etc/test.sqlite.sql', + }, + mode => 'development', + logconf => 'testlog.conf', +}; + +my $db = Flair::Db->new(log => $log, config => $config->{database}); +my $ddf = $config->{database}->{migration}; +$db->dbh->migrations->from_file($ddf)->migrate(0)->migrate; + +my $t = Test::Mojo->new('Flair', $config); + +$log->debug("INSERT TESTS ======================================"); +my $metric_data = { + year => 2002, + month => 4, + day => 4, + hour => 8, + metric => 'hours on the job', + value => 1, +}; + +my $expected = dclone($metric_data); + $expected->{metric_id} = 1; + +$t->post_ok('/api/v1/metrics' => $auth => json => $metric_data)->status_is(201); +my $got = $t->tx->res->json; +cmp_deeply($got, $expected, "Created Status"); + +#say Dumper($got); +#done_testing(); +#exit 0; + +my $metric_data2 = { + year => 2002, + month => 4, + day => 4, + hour => 9, + metric => 'hours on the job', + value => 1, +}; +my $expected2 = dclone($metric_data2); + $expected2->{metric_id} = 2; + +$t->post_ok('/api/v1/metrics' => $auth => json => $metric_data2)->status_is(201); +my $got2 = $t->tx->res->json; +cmp_deeply($got2, $expected2, "Created another Status"); + +$log->debug("LIST TESTS ======================================"); +my $list_expected = [ $expected2, $expected ]; + +$t->get_ok("/api/v1/metrics?limit=0&offset=10" => $auth)->status_is(200); +my $gotlist =$t->tx->res->json; +cmp_deeply($gotlist, $list_expected, "List correct"); + + +$log->debug("FETCH TESTS ======================================"); +$t->get_ok("/api/v1/metrics/1" => $auth)->status_is(200); +cmp_deeply($t->tx->res->json, $got, "Fetch 1 worked"); + +$t->get_ok("/api/v1/metrics/2" => $auth)->status_is(200); +cmp_deeply($t->tx->res->json, $got2, "Fetch 2 worked"); + +$log->debug("UPDATE TESTS ======================================"); +my $metric_update = { + year => 2002, + month => 4, + day => 4, + hour => 8, + metric => 'foobars', + value => 2, +}; + +my $expected3 = dclone($metric_update); +$expected3->{metric_id} = 1; + +$t->put_ok("/api/v1/metrics/1" => $auth => json => $metric_update)->status_is(200); +cmp_deeply($t->tx->res->json, $expected3, "Update Worked"); + +$log->debug("PATCH TESTS ======================================"); +my $metric_patch = { + metric => 'boombazs', + value => 3, +}; + +my $expected4 = dclone($expected3); + $expected4->{metric} = $metric_patch->{metric}; + $expected4->{value} = $metric_patch->{value}; + +$t->patch_ok("/api/v1/metrics/1" => $auth => json => $metric_patch)->status_is(200); +cmp_deeply($t->tx->res->json, $expected4, "Patch Worked"); + + +$log->debug("DELETE TESTS ======================================"); +$t->delete_ok("/api/v1/metrics/1" => $auth)->status_is(200); +cmp_deeply($t->tx->res->json, $expected4, "Got Deleted record"); +$t->get_ok("/api/v1/metrics/1" => $auth)->status_is(200); +is($t->tx->res->json, undef, "Record was removed"); + + +$log->debug("COUNT TESTS ======================================"); +$t->get_ok("/api/v1/metrics/count" => $auth => json => {})->status_is(200) + ->json_is('/count' => 1, "Correct count"); + + +# say Dumper($t->tx->res->json); +done_testing(); +exit 0; + + + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} diff --git a/t/api/regex.t b/t/api/regex.t new file mode 100755 index 0000000..e93b4b1 --- /dev/null +++ b/t/api/regex.t @@ -0,0 +1,167 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Test::Mojo; +use Data::Dumper::Concise; +use Storable qw(dclone); +use JSON; +use Log::Log4perl::Level; + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my $auth = {authorization => 'apikey flairtest123'}; +my $config = { + database => { + dbtype => 'sqlite', + dbfile => 'file:/var/flair/test.db', + model => {}, + migration => '../../etc/test.sqlite.sql', + }, + mode => 'development', + logconf => 'testlog.conf', +}; + +my $db = Flair::Db->new(log=>$log, config=>$config->{database}); +my $ddf = $config->{database}->{migration}; +$db->dbh->migrations->from_file($ddf)->migrate(0)->migrate; + +my $t = Test::Mojo->new('Flair', $config); + +$log->debug("INSERT TESTS ======================================"); +my $regex_data = { + name => "Foobar", + description => "Find Foobar in Text", + match => '/\b foobar \b/xims', + entity_type => 'agent of chaos', + regex_type => 'core', + re_order => 1, + multiword => JSON::false, +}; + +my $expected = dclone($regex_data); + $expected->{regex_id} = 1; + $expected->{updated} = ignore(); + + +$t->post_ok('/api/v1/regex' => $auth => json => $regex_data) + ->status_is(201); +my $got = $t->tx->res->json; +$log->debug("GOT ",{filter => \&Dumper, value => $got}); +cmp_deeply($got, $expected, "Created Regex"); + +# test against duplicated inserts +$t->post_ok('/api/v1/regex' => $auth => json => $regex_data)->status_is(409); + +say Dumper($t->tx->res->json); +done_testing(); +exit 0; + + + +my $redata2 = { + name => "Boombaz", + description => "Find boombaz in Text", + match => '/\b boombaz \b/xims', + entity_type => 'agent of calm', + regex_type => 'core', + re_order => 1, + multiword => JSON::false, +}; +my $expected2 = dclone($regex_data); + $expected2->{regex_id} = 2; + $expected2->{updated} = ignore(); + +$t->post_ok('/api/v1/regex' => $auth => json => $regex_data) + ->status_is(201); +my $got2 = $t->tx->res->json; +cmp_deeply($got2, $expected2, "Created another Regex"); + +$log->debug("LIST TESTS ======================================"); +my $list_expected = [ $expected2, $expected ]; + +$t->get_ok("/api/v1/regex?limit=0&offset=10" => $auth) + ->status_is(200); + +my $gotlist =$t->tx->res->json; +cmp_deeply($gotlist, $list_expected, "List correct"); + + +$log->debug("FETCH TESTS ======================================"); +$t->get_ok("/api/v1/regex/1" => $auth)->status_is(200); +cmp_deeply($t->tx->res->json, $got, "Fetch 1 worked"); + +$t->get_ok("/api/v1/regex/2" => $auth)->status_is(200); +cmp_deeply($t->tx->res->json, $got2, "Fetch 2 worked"); + +$log->debug("UPDATE TESTS ======================================"); +my $redata_updated = { + name => "Zoobar", + description => "Find Zoobar in Text", + match => '/\b zoobar \b/xims', + entity_type => 'agent of zeus', + regex_type => 'core', + re_order => 1, + multiword => JSON::false, +}; + +my $expected3 = dclone($redata_updated); +$expected3->{regex_id} = 1; +$expected3->{updated} = ignore(); + +$t->put_ok("/api/v1/regex/1" => $auth => json => $redata_updated) + ->status_is(200); +cmp_deeply($t->tx->res->json, $expected3, "Update Worked"); + +$log->debug("PATCH TESTS ======================================"); +my $redata_patch = { + description => "Who is boombaz?", + regex_type => 'udef', +}; + +my $expected4 = dclone($expected3); +$expected4->{description} = $redata_patch->{description}; +$expected4->{regex_type} = $redata_patch->{regex_type}; + +$t->patch_ok("/api/v1/regex/1" => $auth => json => $redata_patch) + ->status_is(200); +cmp_deeply($t->tx->res->json, $expected4, "Patch Worked"); + +$log->debug("DELETE TESTS ======================================"); +$t->delete_ok("/api/v1/regex/1" => $auth) + ->status_is(200); +cmp_deeply($t->tx->res->json, $expected4, "Got Deleted record"); +$t->get_ok("/api/v1/regex/1" => $auth)->status_is(200); +is($t->tx->res->json, undef, "Record was removed"); + + +$log->debug("COUNT TESTS ======================================"); +$t->get_ok("/api/v1/regex/count" => $auth => json => {}) + ->status_is(200) + ->json_is('/count' => 1, "Correct count"); + + +# say Dumper($t->tx->res->json); +done_testing(); +exit 0; + + + + + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} diff --git a/t/basic.t b/t/basic.t new file mode 100755 index 0000000..ad6cc43 --- /dev/null +++ b/t/basic.t @@ -0,0 +1,26 @@ +use Mojo::Base -strict; + +use Test::More; +use Test::Mojo; +use lib './lib','../lib'; +use Log::Log4perl; +say `pwd`; +my $file = find_rel_dir('etc/testlog.conf'); +say $file; +Log::Log4perl::init($file); + +my $t = Test::Mojo->new('Flair'); +$t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i); +$t->get_ok('/flair/v1/regex')->status_is(200); + +done_testing(); + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} \ No newline at end of file diff --git a/t/com/imggrab.t b/t/com/imggrab.t new file mode 100755 index 0000000..a9ee931 --- /dev/null +++ b/t/com/imggrab.t @@ -0,0 +1,74 @@ +#!/usr/bin/env perl +use Mojo::Base -strict; +use strict; +use warnings; + +use Test::More; +use Data::Dumper::Concise; +use DateTime; +use Log::Log4perl; +use lib './lib','../lib'; +use Flair::ImgGrab; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); + +$log->info("$0 begins"); + +#my $file = find_rel_dir('etc/testlog.conf'); +#say $file; +#Log::Log4perl::init($file); +#my $log = Log::Log4perl->get_logger(); + +my $config = { + insecure => 0, + download_root => '/tmp/flairtest', +}; + +system("rm -rf $config->{download_root}"); + +my $ig = Flair::ImgGrab->new( + log => $log, + config => $config, +); + +my $uri = "https://www.sandia.gov/app/uploads/sites/72/2021/06/scot.png"; +my $year = DateTime->now()->year; +my $dest = "/tmp/$year/event/123"; + +my $asset = $ig->get_image($uri); + +is(ref($asset), "Mojo::Asset::File", "Got a File Asset"); + +my $new_name = $ig->build_file_hashname($asset, $uri); +is($new_name, "60a52cc8fc4cc6bf674ff5a34a69c204.png", "Got proper new hash based filename"); + + +my $new_file = $ig->build_new_filename($asset, $uri, $dest); +is ($new_file, "$dest/60a52cc8fc4cc6bf674ff5a34a69c204.png", "Got propper fqn"); + +$ig->ensure_storage_dir($new_file); + +ok(-d $dest, "Created storage dir"); + +$ig->save_asset($asset, $new_file); + +ok(-e $new_file, "New File is in new location"); + +done_testing(); +exit 0; + + + + + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} diff --git a/t/com/scotapi.t b/t/com/scotapi.t new file mode 100755 index 0000000..e9c961d --- /dev/null +++ b/t/com/scotapi.t @@ -0,0 +1,75 @@ +#!/usr/bin/env perl +use Mojo::Base -strict; +use strict; +use warnings; + +use Test::More; +use Data::Dumper::Concise; +use Log::Log4perl; +use lib '../../lib'; +use Flair::ScotApi; + +my $file = find_rel_dir('etc/testlog.conf'); +say $file; +Log::Log4perl::init($file); +my $log = Log::Log4perl->get_logger(); +my $scotbaseuri = "https://as3001snllx.sandia.gov/scot/api/v2"; +my $config = { + insecure => 1, + api_key => '532EC1B6-8968-11EA-8F10-5AA486A3F2C3', + uri_root => $scotbaseuri, +}; + + +my $api = Flair::ScotApi->new( + log => $log, + config => $config, +); + +is(ref($api), "Flair::ScotApi", "Got ScotApi object"); +is(ref($api->ua), "Mojo::UserAgent", "It has a Mojo::UA in it"); + +my $alertgroup = $api->fetch('alertgroup', 1712852); + +is($alertgroup->{id}, 1712852, "Got the correct alertgroup"); +is($alertgroup->{alerts}->[0]->{id}, 41837469, "The alert is correct"); + +my $entry = $api->fetch('entry', 10); +is($entry->{id}, 10, "Got the Entry"); +is($entry->{target}->{id}, 2, "Target Id correct"); +is($entry->{target}->{type}, "event", "Target Id correct"); + +my $body = $entry->{body} =~ s/CVA/CVA2/rg; +my $flair = $entry->{body_flair} =~ s/CVA/CVA2/rg; +my $text = $entry->{body_plain} =~ s/CVA/CVA2/rg; + + +my $patch_data = { + type => "entry", + id => 10, + data => { + body => $body, + body_flair => $flair, + body_plain => $text, + }, +}; +my $entryupdate = $api->flair_update($patch_data); + +$entry = $api->fetch('entry', 10); +ok($entry->{body} =~ /CVA2/, "Body updated"); +ok($entry->{body_flair} =~ /CVA2/, "Flair updated"); +ok($entry->{body_plain} =~ /CVA2/, "Plain updated"); + +say Dumper($entry); +done_testing(); +exit 0; + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} diff --git a/t/db.t b/t/db.t new file mode 100755 index 0000000..d1bf4c5 --- /dev/null +++ b/t/db.t @@ -0,0 +1,65 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use JSON; +use Storable qw(dclone); +use Log::Log4perl::Level; +use Digest::MD5 qw(md5_hex); + +use lib '../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my @configs = ( + { + dbtype => 'sqlite', + dbfile => 'file:/var/flair/test.db', + model => { + }, + migration => '../etc/flair.sqlite.sql', + }, +); + +my $target_type = { + sqlite => 'Mojo::SQLite', + pg => 'Mojo::Pg', + mysql => 'Mojo::mysql', +}; + +my $db; + +foreach my $config (@configs) { + + $log->debug("Testing Config: ", {filter => \&Dumper, value => $config}); + say "Testing with Config: ".$config->{dbtype}; + + $db = Flair::Db->new( + log => $log, + config => $config, + ); + is (ref($db), "Flair::Db", "Got a Db object"); + $db->dbh->migrations->from_file($config->{migration})->migrate(0)->migrate; + is (ref($db->dbh), $target_type->{$config->{dbtype}}, "Got right type of DBH"); + + +} + +print $db->dbh->db->query('select * from apikeys'); + +my $apikeys = $db->apikeys; +my $result = $apikeys->list({ fields => ['*'], where => undef }); + +print Dumper($result); + +done_testing(); +exit 0; + + + diff --git a/t/edb.t b/t/edb.t new file mode 100755 index 0000000..6eba772 --- /dev/null +++ b/t/edb.t @@ -0,0 +1,20 @@ +#!/opt/perl/bin/perl + +use Test::Most; + +use lib '../lib'; +use Flair::Edb; + +my $e = Flair::Edb->new(); +my $x = Flair::Edb->new(); + +$e->add('foo', 'bar'); +$e->add('foo', 'bar'); +$e->dump; + +$x->add('boom', 'baz'); +$x->dump; + +$e->merge($x); +$e->dump; + diff --git a/t/experiments/figure.pl b/t/experiments/figure.pl new file mode 100755 index 0000000..3e411a6 --- /dev/null +++ b/t/experiments/figure.pl @@ -0,0 +1,27 @@ +#!/opt/perl/bin/perl + +use lib '../../lib'; +use Flair::Util::HTML qw(build_html_tree); + +my $html =<<'EOF'; + + +

Sample

+
+ + + + + +
FooBar
+
+ + +EOF + +my $tree = build_html_tree($html); + +my $foo = $tree->as_HTML; + +print $foo."\n"; + diff --git a/t/falsepos.t b/t/falsepos.t new file mode 100755 index 0000000..2bb5e32 --- /dev/null +++ b/t/falsepos.t @@ -0,0 +1,26 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; + +use lib '../lib'; +use Flair::Falsepos; + +my $fp = Flair::Falsepos->new; +is (ref($fp), "Flair::Falsepos", "instantiated object") +or die "failed to create false pos object"; + +$fp->add("foo.com"); +my $href = $fp->as_hash; +cmp_deeply($href, { "foo.com" => 1 }, "Added foo.comf") +or die "failed to add foo.com"; + +ok($fp->is_false_positive("foo.com"), "found false pos") +or die "failed to find false positive"; + +ok(! $fp->is_false_positive("bar.com"), "correctly did not identify falsepos") +or die "failed by identifying an ok domain as false positive"; + +done_testing(); +exit 0; diff --git a/t/ipv6playground/ip6.t b/t/ipv6playground/ip6.t new file mode 100755 index 0000000..a0c8e58 --- /dev/null +++ b/t/ipv6playground/ip6.t @@ -0,0 +1,200 @@ +#!/usr/bin/env perl + +use lib '../../lib'; +use Mojo::Base -strict; +use Net::IPv6Addr; +use Data::Dumper::Concise; + +my @regexes = ( + { + name => 'suricata', + regex => qr{ + (?: + (?: + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + ) + (?::[0-9]+) + ) + }xims, + }, + { + name => 'standard', + regex => qr{ + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + }xims, + }, + { + name => 'compressed', + regex => qr{ + (?= + (?:[A-Z0-9]{0,4}:){0,7}[A-F0-9]{0,4}(?![:\w]) + ) + (([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:) + }xims, + }, + { + name => 'cmp8colo', + regex => qr{ + (?:[A-F0-9]{1,4}:){7}:|:(:[A-F0-9]{1,4}){7}(?![:\w]) + }xims, + }, + { + name => 'mixed', + regex => qr{ + (?: + # Non-compressed + (?:[A-F0-9]{1,4}:){6} + # Compressed with at most 6 colons + |(?=(?:[A-F0-9]{0,4}:){0,6} + (?:[0-9]{1,3}\.){3}[0-9]{1,3} # and 4 bytes + (?![:.\w]) + ) + # and at most 1 double colon + (([0-9A-F]{1,4}:){0,5}|:)((:[0-9A-F]{1,4}){1,5}:|:) + # Compressed with 7 colons and 5 numbers + |::(?:[A-F0-9]{1,4}:){5} + ) + # 255.255.255. + (?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3} + # 255 + (?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]) + }xims, + }, +); + +my @data = ( + { + text => 'before 1762::b03:1:af18 after', + suricata => 0, + standard => 0, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => 'before 1762::b03:1:af18. after', + suricata => 0, + standard => 0, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => '1762:0:0:0:0:B03:1:AF18', + suricata => 0, + standard => 1, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => '1762:0:0:0:0:B03:1:AF18.', + suricata => 0, + standard => 1, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => '1762:0:0:0:0:B03:1:AF18:8080', + suricata => 1, + standard => 0, + compressed => 0, + cmp8colo => 0, + mixed => 0, + }, + { + text => 'this 1762:0:0:0:0:B03:1:AF18 stinks', + suricata => 0, + standard => 1, + compressed => 0, + cmp8colo => 0, + mixed => 0, + }, + { + text => 'this 1762:0:0:0:0:B03:1:AF18. also stinks', + suricata => 0, + standard => 1, + compressed => 0, + cmp8colo => 0, + mixed => 0, + }, + { + text => '2001:41d0:2:9d17::', + suricata => 0, + standard => 0, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => '2001:41d0:2:9d17:: .', + suricata => 0, + standard => 0, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => 'Foo 2001:41d0:2:9d17:: bar', + suricata => 0, + standard => 0, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => 'Foo 2001:41d0:2:9d17::.', + suricata => 0, + standard => 0, + compressed => 1, + cmp8colo => 0, + mixed => 0, + }, + { + text => '0:0:0:0:0:ffff:192.1.56.10 looks weird', + suricata => 0, + standard => 0, + compressed => 0, + cmp8colo => 0, + mixed => 1, + }, + { + text => 'so does: ::ffff:192.1.56.10/96', + suricata => 0, + standard => 0, + compressed => 0, + cmp8colo => 0, + mixed => 1, + }, +); + +foreach my $href (@data) { + my $text = $href->{text}; + say "---- $text"; + printf " "x10 . " Expected Got\n"; + + my $worked = 0; + + foreach my $rehref (@regexes) { + my $name = $rehref->{name}; + + printf "%10s %1s ",$name, $href->{$name}; + + if ( $text =~ m/$rehref->{regex}/g ) { + $worked++; + say "1"; + } + else { + say "0"; + } + } + print "--------------- "; + if ($worked) { + say "MATCHED!"; + } + else { + say "NO MATCH"; + } + say ""; +} diff --git a/t/matching/id.t b/t/matching/id.t new file mode 100755 index 0000000..fd6c86f --- /dev/null +++ b/t/matching/id.t @@ -0,0 +1,48 @@ +#!/usr/bin/env perl +use lib '../lib'; +use Mojo::Base -strict; +use Net::IPv6Addr; +use Data::Dumper::Concise; +use Flair::Util::Log; +use Flair::Util::Config; +use Flair::Util::Pg; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $creader = Flair::Util::Config->new(); +my $config = $creader->get_config('flair.conf'); +my $models = Flair::Util::Pg->new->build_models($log, $config); +my $uuid_re = $models->{regex}->regex_by_name('uuid1'); +my $clsid_re = $models->{regex}->regex_by_name('clsid'); + +my $text = "d0229d40-1274-11e8-a427-3d01d7fc9aea"; + +say Dumper('uuid1',$uuid_re); + +if ( $text =~ m/$uuid_re/g ) { + say "MATCH UUID1"; +} + +say Dumper('clsid',$clsid_re); +if ( $text =~ m/$clsid_re/g ) { + say "MATCH CSLID"; +} + +my $re1 = qr/ + [0-9a-f]{8} + \- + [0-9a-f]{4} + \- + 11[ef][0-9a-f] + \- + [89ab][0-9a-f]{3} + \- + [0-9a-f]{12} + /umsix; + +if ( $text =~ m/$re1/g ) { + say "What!?"; +} + diff --git a/t/matching/ipv6.t b/t/matching/ipv6.t new file mode 100755 index 0000000..f44d784 --- /dev/null +++ b/t/matching/ipv6.t @@ -0,0 +1,93 @@ +#!/usr/bin/env perl +use lib '../../lib'; +use Mojo::Base -strict; +use Net::IPv6Addr; +use Data::Dumper::Concise; +use Flair::Util::Log; +use Flair::Util::Config; +use Flair::Util::Pg; + +#my $ip1 = Net::IPv6Addr->new('1762:0:0:0:0:B03:1:AF18'); +#say Dumper($ip1); + +#my $ip2 = Net::IPv6Addr->new('2001:41d0:2:9d17::'); +#say Dumper($ip2); + +my $re = qr{( + # first look for a suricata/snort format (ip:port) + (?: + # look for aaaa:bbbb:cccc:dddd:eeee:ffff:gggg:hhhh + (?: + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + ) + # look for but dont capture a trailing :\d+ + (?=:[0-9]+) + ) + # next try the rest of the crazy that is ipv6 + # thanks to autors of + # https://learning.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/c h08s17.html + |(?: + # Mixed + (?: + # Non-compressed + (?:[A-F0-9]{1,4}:){6} + # Compressed with at most 6 colons + |(?=(?:[A-F0-9]{0,4}:){0,6} + (?:[0-9]{1,3}\.){3}[0-9]{1,3} # and 4 bytes + (?![:.\w]) + ) + # and at most 1 double colon + (([0-9A-F]{1,4}:){0,5}|:)((:[0-9A-F]{1,4}){1,5}:|:) + # Compressed with 7 colons and 5 numbers + |::(?:[A-F0-9]{1,4}:){5} + ) + # 255.255.255. + (?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\.){3} + # 255 + (?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9]) + + |# Standard + (?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4} + |# Compressed with at most 7 colons + (?=(?:[A-F0-9]{0,4}:){0,7}[A-F0-9]{0,4} + (?![:.\w]) + ) # and anchored + # and at most 1 double colon + (([0-9A-F]{1,4}:){1,7}|:)((:[0-9A-F]{1,4}){1,7}|:) + # Compressed with 8 colons + |(?:[A-F0-9]{1,4}:){7}:|:(:[A-F0-9]{1,4}){7} + ) (?![:.\w]) # neg lookahead to "anchor" + \b +)}xims; + +# my $text = '2001:41d0:2:9d17:: '; +my $text = 'switch to 1762:0:0:0:0:b03:1:af18.'; + +if ($text =~ m/$re/g) { + say "matched: $1"; +} + +exit 0; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $creader = Flair::Util::Config->new(); +my $config = $creader->get_config('flair.conf'); +my $models = Flair::Util::Pg->new->build_models($log, $config); +my $regexes = $models->{regex}->build_flair_regexes(); + +my $tre; +foreach my $re (@$regexes) { + next if ($re->{name} ne "ipv6"); + $tre = $re->{regex}; + say Dumper($tre); + last; +} + +if ( $text =~ m/$tre/g ) { + say "db re matched"; +} +say "------------"; +say Dumper($re, $tre); diff --git a/t/matching/message_id.t b/t/matching/message_id.t new file mode 100755 index 0000000..4ee5834 --- /dev/null +++ b/t/matching/message_id.t @@ -0,0 +1,41 @@ +#!/usr/bin/env perl +use lib '../../lib'; +use Mojo::Base -strict; +use Net::IPv6Addr; +use Data::Dumper::Concise; +use Flair::Util::Log; +use Flair::Util::Config; +use Flair::Util::Pg; +# use Regexp::Debugger; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $creader = Flair::Util::Config->new(); +my $config = $creader->get_config('flair.conf'); +#my $models = Flair::Util::Db->new->build_models($log, $config); +#my $re = $models->{regex}->regex_by_name('message_id'); + +my $text = ' <CAEr1S5-HuU1MjnUQtqT6Ri-i2ZaYcTm_+cjf6mkmOgwGJHjPJA@mail.gmail.com> '; +my $text2 = 'CAEr1S5-HuU1MjnUQtqT6Ri-i2ZaYcTm_+cjf6mkmOgwGJHjPJA@mail.gmail.com'; +# say Dumper('message_id',$re); +# if ( $text =~ m/$re/g ) { +# say "RE MATCH message_id"; +# } + +my $xre = qr/( + (<|<)? # starts with < or < + (?:[^\s]*?) # some nonblank chars + @ # an @ seperator + (?:[^\s]*?) # some nonblank chars + (>|>)? # ends with > or > + )/umsix; + +if ( $text =~ m/$xre/g ) { + say "XRE match\n"; +} + +if ($text2 =~ m/$xre/g) { + say "text2 match\n"; +} diff --git a/t/models/admins.t b/t/models/admins.t new file mode 100755 index 0000000..ea7ff86 --- /dev/null +++ b/t/models/admins.t @@ -0,0 +1,141 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use JSON; +use Storable qw(dclone); +use Crypt::PBKDF2; +use Log::Log4perl::Level; + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my @configs = ( + { + dbtype => 'sqlite', + dbfile => '/var/flair/test.db', + model => { + }, + migration => '../../etc/test.sqlite.sql', + }, + # { + # dbtype => 'pg', + # uri => 'postgresql://flairtest:flair1234@localhost/flairtest', + # model => { + # }, + # migration => '../../etc/flair.pg.sql', + #}, + #{ + # dbtype => 'mysql', + # uri => 'mysql://flairtest:flair1234@localhost/flairtest', + # model => { + # }, + # migration => '../../etc/flair.mysql.sql', + #}, +); + +my $pbkdf2 = Crypt::PBKDF2->new( + hash_class => 'HMACSHA2', + hash_args => { sha_size => 512 }, + iterations => 10000, + salt_len => 15, +); + +my $admin1_input = { + username => 'foobar', + who => 'Mr. Foo Bar, esq.', + pwhash => $pbkdf2->generate('boombaz'), +}; +my $admin2_input = { + username => 'scotty', + who => 'Mr. Scot Scot, IV', + pwhash => $pbkdf2->generate('coolpass'), +}; + +foreach my $config (@configs) { + $log->debug("Testing Config: ", {filter => \&Dumper, value => $config}); + + my $db = Flair::Db->new( + log => $log, + config => $config, + ); + is (ref($db), "Flair::Db", "Got a Db object"); + $db->dbh->migrations->from_file($config->{migration})->migrate(0)->migrate; + my $target_type = { + pg => 'Mojo::Pg', + mysql => 'Mojo::mysql', + sqlite => 'Mojo::SQLite', + };; + is (ref($db->dbh), $target_type->{$config->{dbtype}}, "Got right type of DBH"); + + my $model = $db->admins; + my $result1 = $model->create($admin1_input); + $log->debug("result:",{filter=>\&Dumper, value=>$result1}); + my $expect1 = dclone($admin1_input); + $expect1->{admin_id} = 1; + $expect1->{updated} = ignore(); + $expect1->{lastlogin} = ignore(); + $expect1->{lastaccess} = ignore(); + cmp_deeply($result1, $expect1, "Created Admin 1"); + + my $result2 = $model->create($admin2_input); + my $expect2 = dclone($admin2_input); + $expect2->{admin_id} = 2; + $expect2->{updated} = ignore(); + $expect2->{lastlogin} = ignore(); + $expect2->{lastaccess} = ignore(); + cmp_deeply($result2, $expect2, "Created Admin 2"); + + my $fetch1_result = $model->fetch(1); + cmp_deeply($fetch1_result, $expect1, "Retrieved Admin 1"); + my $fetch2_result = $model->fetch(2); + cmp_deeply($fetch2_result, $expect2, "Retrieved Admin 2"); + + my $list_opts = { + fields => ['*'], + where => undef, + order => { -desc => 'admin_id' }, + offset => 0, + limit => undef, + }; + my $list_expect = [ $expect2, $expect1 ]; + my $list_result = $model->list($list_opts); + cmp_deeply($list_result, $list_expect, "Listed admins as expected"); + + my $patch = { pwhash => $pbkdf2->generate('changedpassword')}; + my $expect3 = dclone($expect1); + $expect3->{pwhash} = $patch->{pwhash}; + $expect3->{updated} = ignore(); + my $result3 = $model->patch(1, $patch); + cmp_deeply($result3, $expect3, "Patch works"); + + my $count = $model->count({}); + is($count->{count}, 2, "Correct count"); + + my $delete = $model->delete(2); + $count = $model->count({}); + is($count->{count}, 1, "Deleted Record"); + + my $attempt = $model->fetch(2); + is($attempt, undef, "Correctly failed to retrieve deleted row"); + + sleep 1; + + my $lastlog_update = $model->set_lastlogin('foobar'); + my $previous_ll = $result3->{lastlogin}; + my $current_ll = $lastlog_update->{lastlogin}; + ok($current_ll ne $previous_ll, "Last Login updated"); + +} +done_testing(); +exit 0; + + + diff --git a/t/models/all.t b/t/models/all.t new file mode 100755 index 0000000..9b6f161 --- /dev/null +++ b/t/models/all.t @@ -0,0 +1,12 @@ +#!/usr/bin/env perl +unlink "./flairtest.db"; +use TAP::Harness; +my %args = ( verbosity => 1 ); +my $harness = TAP::Harness->new(\%args); +$harness->runtests( + './db.t', + './admins.t', + './apikeys.t', + './metric.t', + './regex.t', +); diff --git a/t/models/apikeys.t b/t/models/apikeys.t new file mode 100755 index 0000000..f2e5995 --- /dev/null +++ b/t/models/apikeys.t @@ -0,0 +1,156 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use JSON; +use Storable qw(dclone); +use Log::Log4perl::Level; +use Digest::MD5 qw(md5_hex); + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my @configs = ( + { + dbtype => 'sqlite', + uri => 'flairtest.db', + model => { + }, + migration => '../../etc/test.sqlite.sql', + }, + #{ + # dbtype => 'pg', + # uri => 'postgresql://flairtest:flair1234@localhost/flairtest', + # model => { + # }, + # migration => '../../etc/flair.pg.sql', + #}, + #{ + # dbtype => 'mysql', + # uri => 'mysql://flairtest:flair1234@localhost/flairtest', + # model => { + # }, + # migration => '../../etc/flair.mysql.sql', + #}, +); + +my $target_type = { + sqlite => 'Mojo::SQLite', + pg => 'Mojo::Pg', + mysql => 'Mojo::mysql', +}; + +my $apikey1_input = { + username => 'foobar', + key => '11111111111111111111', + flairjob => JSON::true, + regex_ro => JSON::true, + regex_crud => JSON::true, + metrics => JSON::true, +}; +my $apikey2_input = { + username => 'boombaz', + key => '22222222222222222222', + flairjob => JSON::true, + regex_ro => JSON::false, + regex_crud => JSON::false, + metrics => JSON::false, +}; +my $testapikey = { + apikey_id => 1, + updated => ignore(), + lastaccess => ignore(), + username => 'flairtest', + key => 'flairtest123', + flairjob => 1, + regex_ro => 1, + regex_crud => 1, + metrics => 1, +}; + +foreach my $config (@configs) { + + $log->debug("Testing Config: ", {filter => \&Dumper, value => $config}); + say "Testing with Config: ".$config->{dbtype}; + + my $db = Flair::Db->new( + log => $log, + config => $config, + ); + is (ref($db), "Flair::Db", "Got a Db object"); + $db->dbh->migrations->from_file($config->{migration})->migrate(0)->migrate; + is (ref($db->dbh), $target_type->{$config->{dbtype}}, "Got right type of DBH"); + + my $model = $db->apikeys; + + my $result1 = $model->create($apikey1_input); + my $expect1 = dclone($apikey1_input); + $expect1->{apikey_id} = 2; + $expect1->{updated} = ignore(); + $expect1->{lastaccess} = ignore(); + $expect1->{flairjob} = 1; + $expect1->{regex_ro} = 1; + $expect1->{regex_crud} = 1; + $expect1->{metrics} = 1; + cmp_deeply($result1, $expect1, "Created Apikey 1") or die "Create Incorrect"; + + my $result2 = $model->create($apikey2_input); + my $expect2 = dclone($apikey2_input); + $expect2->{apikey_id} = 3; + $expect2->{updated} = ignore(); + $expect2->{lastaccess} = ignore(); + $expect2->{flairjob} = 1; + $expect2->{regex_ro} = 0; + $expect2->{regex_crud} = 0; + $expect2->{metrics} = 0; + cmp_deeply($result2, $expect2, "Created Apikey 2") or die "Create Incorrect"; + + my $fetch1_result = $model->fetch(2); + cmp_deeply($fetch1_result, $expect1, "Retrieved Apikey 1"); + my $fetch2_result = $model->fetch(3); + cmp_deeply($fetch2_result, $expect2, "Retrieved Apikey 2"); + + my $list_opts = { + fields => ['*'], + where => undef, + order => { -desc => 'apikey_id' }, + offset => 0, + limit => undef, + }; + my $list_expect = [ $expect2, $expect1, $testapikey ]; + my $list_result = $model->list($list_opts); + cmp_deeply($list_result, $list_expect, "Listed apikeys as expected") or die "List Incorrect"; + + my $patch = { metrics => JSON::false, key => '3333' }; + my $expect3 = dclone($expect1); + $expect3->{metrics} = 0; + $expect3->{key} = $patch->{key}; + my $result3 = $model->patch(2, $patch); + cmp_deeply($result3, $expect3, "Patch works") or die "Patch failed"; + + my $count = $model->count({}); + is($count->{count}, 3, "Correct count"); + + my $delete = $model->delete(3); + $count = $model->count({}); + is($count->{count}, 2, "Deleted Record") or die "delete failed"; + + my $attempt = $model->fetch(3); + is($attempt, undef, "Correctly failed to retrieve deleted row"); + + my $key = $model->get_key($expect3->{key}); + cmp_deeply($key, $expect3, "Got by Key") or die "failed to get by key"; + +} +done_testing(); +exit 0; + + + diff --git a/t/models/db.t b/t/models/db.t new file mode 100755 index 0000000..01d14cf --- /dev/null +++ b/t/models/db.t @@ -0,0 +1,57 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use Digest::MD5 qw(md5_hex); +use JSON; +use Storable qw(dclone); + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $config1 = { + uri => 'flairtest.db', + dbtype => 'sqlite', +}; + +my $db1 = Flair::Db->new(log => $log, config => $config1); +my $dbh1 = $db1->dbh; + +is(ref($dbh1), 'Mojo::SQLite', "Build correct DBH for SQLite"); +$dbh1->migrations->from_file('../../etc/flair.sqlite.sql')->migrate(0)->migrate; + +# test postgres first +my $config = { + uri => 'postgresql://flairtest:flair1234@localhost/flairtest', + dbtype => 'pg' +}; + +#my $db = Flair::Db->new(log => $log, config => $config); +#my $dbh = $db->dbh; +# +#is(ref($dbh), "Mojo::Pg", "Build correct DBH for Postgres"); +# +#$dbh->migrations->from_file('../../etc/flair.pg.sql')->migrate(0)->migrate; +# +# +#my $config2 = { +# uri => 'mysql://flairtest:flair1234@localhost/flairtest', +# dbtype => 'mysql' +#}; +# +#my $db2 = Flair::Db->new(log => $log, config => $config2); +#my $dbh2 = $db2->dbh; +# +#is(ref($dbh2), "Mojo::mysql", "Built correct DBH for Mysql"); +# +#$dbh2->migrations->from_file('../../etc/flair.mysql.sql')->migrate(0)->migrate; + +done_testing(); +exit 0; diff --git a/t/models/flairtest.db b/t/models/flairtest.db new file mode 100644 index 0000000..d76b4b2 Binary files /dev/null and b/t/models/flairtest.db differ diff --git a/t/models/jobs.t b/t/models/jobs.t new file mode 100755 index 0000000..a641b78 --- /dev/null +++ b/t/models/jobs.t @@ -0,0 +1,55 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use JSON; +use Storable qw(dclone); +use Log::Log4perl::Level; +use Digest::MD5 qw(md5_hex); +use DateTime; + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my @configs = ( + { + dbtype => 'sqlite', + uri => 'flairtest.db', + model => { + }, + migration => '../../etc/test.sqlite.sql', + }, +); + + + +foreach my $config (@configs) { + + $log->debug("Testing Config: ", {filter => \&Dumper, value => $config}); + say "Testing with Config: ".$config->{dbtype}; + + my $db = Flair::Db->new( + log => $log, + config => $config, + ); + is (ref($db), "Flair::Db", "Got a Db object"); + $db->dbh->migrations->from_file($config->{migration})->migrate(0)->migrate; + my $target_type = 'Mojo::SQLite'; + is (ref($db->dbh), $target_type, "Got right type of DBH"); + + my $model = $db->jobs; + + +} +done_testing(); +exit 0; + + + diff --git a/t/models/metric.t b/t/models/metric.t new file mode 100755 index 0000000..9472745 --- /dev/null +++ b/t/models/metric.t @@ -0,0 +1,121 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use JSON; +use Storable qw(dclone); +use Log::Log4perl::Level; +use Digest::MD5 qw(md5_hex); +use DateTime; + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my @configs = ( + { + dbtype => 'sqlite', + uri => 'file:flairtest.db', + model => { + }, + migration => '../../etc/test.sqlite.sql', + }, +# { +# dbtype => 'pg', +# uri => 'postgresql://flairtest:flair1234@localhost/flairtest', +# model => { +# }, +# migration => '../../etc/flair.pg.sql', +# }, +# { +# dbtype => 'mysql', +# uri => 'mysql://flairtest:flair1234@localhost/flairtest', +# model => { +# }, +# migration => '../../etc/flair.mysql.sql', +# }, +); + + +my $dt = DateTime->new({ + year => 1998, + month => 6, + day => 20, + hour => 14, +}); +my $dt2 = DateTime->new({ + year => 1998, + month => 6, + day => 20, + hour => 15, +}); + +foreach my $config (@configs) { + + $log->debug("Testing Config: ", {filter => \&Dumper, value => $config}); + say "Testing with Config: ".$config->{dbtype}; + + my $db = Flair::Db->new( + log => $log, + config => $config, + ); + is (ref($db), "Flair::Db", "Got a Db object"); + $db->dbh->migrations->from_file($config->{migration})->migrate(0)->migrate; + my $target_type = 'Mojo::SQLite'; + is (ref($db->dbh), $target_type, "Got right type of DBH"); + + my $model = $db->metrics; + + my $result = $model->add_metric("Foo", 1, $dt); + my $metric = $model->get_metric({ + year => $dt->year, + month => $dt->month, + day => $dt->day, + hour => $dt->hour, + metric => "Foo", + }); + say Dumper($metric); + is ($metric->[0]->{value} +0, 1, "Got correct Metric") or die "incorrect metric"; + + $result = $model->add_metric("Foo", 3, $dt); + $metric = $model->get_metric({ + year => $dt->year, + month => $dt->month, + day => $dt->day, + hour => $dt->hour, + metric => "Foo", + }); + is ($metric->[0]->{value}+0, 4, "Got correct Metric after integer addition"); + + $result = $model->add_metric("Foo", 0.4, $dt); + $metric = $model->get_metric({ + year => $dt->year, + month => $dt->month, + day => $dt->day, + hour => $dt->hour, + metric => "Foo", + }); + is ($metric->[0]->{value}+0, 4.4, "Got correct Metric after float addition") or die "incorrect metric"; + + $result = $model->add_metric("Foo", 3, $dt2); + $metric = $model->get_metric({ + year => $dt->year, + month => $dt->month, + day => $dt->day, + metric => "Foo" + }); + is ($metric->[0]->{value}+0, 4.4, "Got correct metric[0]") or die "incorrect metric"; + is ($metric->[1]->{value}+0, 3, "Got correct metric[1]") or die "incorrect metric"; + +} +done_testing(); +exit 0; + + + diff --git a/t/models/regex.t b/t/models/regex.t new file mode 100755 index 0000000..163b2cb --- /dev/null +++ b/t/models/regex.t @@ -0,0 +1,143 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use JSON; +use Storable qw(dclone); +use Log::Log4perl::Level; +use Digest::MD5 qw(md5_hex); +use DateTime; + +use lib '../../lib'; +use Flair::Db; +use Flair::Util::Log; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); +$log->level($TRACE); + +my @configs = ( + # { + # dbtype => 'pg', + # uri => 'postgresql://flairtest:flair1234@localhost/flairtest', + # model => { + # }, + # migration => '../../etc/flair.pg.sql', + #}, + #{ + # dbtype => 'mysql', + # uri => 'mysql://flairtest:flair1234@localhost/flairtest', + # model => { + # }, + # migration => '../../etc/flair.mysql.sql', + #}, + { + dbtype => 'sqlite', + uri => 'flairtest.db', + model => { + }, + migration => '../../etc/test.sqlite.sql', + }, +); + +my $regex1_input = { + name => "foobar", + description => "Find Foobar", + match => '/foobar/', + entity_type => "foo_actor", + regex_type => 'core', + re_order => 1, + multiword => JSON::false, +}; + +my $regex2_input = { + name => "boombaz", + description => "Find BoomBaz", + match => '/boombaz/', + entity_type => "baz_actor", + regex_type => 'core', + re_order => 1, + multiword => JSON::false, +}; + + + +foreach my $config (@configs) { + + $log->debug("Testing Config: ", {filter => \&Dumper, value => $config}); + say "Testing with Config: ".$config->{dbtype}; + + my $db = Flair::Db->new( + log => $log, + config => $config, + ); + is (ref($db), "Flair::Db", "Got a Db object"); + $db->dbh->migrations->from_file($config->{migration})->migrate(0)->migrate; + my $target_type = 'Mojo::SQLite'; + is (ref($db->dbh), $target_type, "Got right type of DBH"); + + my $model = $db->regex; + + my $result1 = $model->create($regex1_input); + my $expect1 = dclone($regex1_input); + $expect1->{regex_id} = 1; + $expect1->{multiword} = 0; + $expect1->{updated} = ignore(); + cmp_deeply($result1, $expect1, "Created Regex 1"); + + my $result2 = $model->create($regex2_input); + my $expect2 = dclone($regex2_input); + $expect2->{regex_id} = 2; + $expect2->{multiword} = 0; + $expect2->{updated} = ignore(); + cmp_deeply($result2, $expect2, "Created Regex 2"); + + my $list_opts = { + fields => ['*'], + where => undef, + order => { -desc => 'regex_id' }, + offset => 0, + limit => undef, + }; + my $expected_list = [ $expect2, $expect1 ]; + my $list_result = $model->list($list_opts); + + cmp_deeply($list_result, $expected_list, "listed as expected"); + + my $patch = { match => '/\b foobar \b/' }; + my $expect3 = dclone($expect1); + $expect3->{match} = $patch->{match}; + my $result3 = $model->patch(1, $patch); + cmp_deeply($result3, $expect3, "Patch Worked"); + + my $count = $model->count({}); + is($count->{count}, 2, "Correct Count"); + + my $delete = $model->delete(2); + $count = $model->count({}); + is($count->{count}, 1, "Deleted a Record"); + + my $attempt = $model->fetch(2); + is($attempt, undef, "Correctly unable to retrieve deleted regex"); + + my @core = $model->load_core_re(); + my @regexes = $model->build_flair_regexes(); + is (scalar(@regexes) -1, scalar(@core), "Got correct number of regexes"); + + # TODO: + # $model->regex_by_name("regex_name"); + # $model->upsert_re($href); + # + my $exists = $model->regex_exists('/\b foobar \b/'); + is ($exists, 1, "Correctly found existing regex using regex"); + $exists = $model->regex_exists('zippywhippy'); + is ($exists, undef, "Correctly did not find a matching regex"); + +} +done_testing(); +exit 0; + + + diff --git a/t/parser.t b/t/parser.t new file mode 100755 index 0000000..2867398 --- /dev/null +++ b/t/parser.t @@ -0,0 +1,108 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use HTML::Element; + +use lib '../lib'; +use Flair::Parser; +use Flair::Util::Log; +use Flair::Util::LoadHashFile; +use Flair::Db; +use Flair::Config; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $config = { + scotapi => { + user => '', + pass => '', + apikey => '', + uri_root => '', + insecure => 1, + }, + database => { + dbtype => 'sqlite', + uri => 'file:/var/flair/test.db', + model => { + regex => {}, + metrics => {}, + admins => {}, + jobs => {}, + }, + migration => '../etc/test.sqlite.sql', + }, +}; + +my $lhf = Flair::Util::LoadHashFile->new(); + +my $migfile = $config->{database}->{migration}; +my $db = Flair::Db->new(log => $log, config => $config->{database}); +is (ref($db), "Flair::Db", "Got DB connection") or die "unable to connect to db"; +ok ($db->dbh->migrations->from_file($migfile)->migrate(0)->migrate, + "Migrated database") or die "Unable to intialize database"; + +my $parser = Flair::Parser->new(log => $log, db => $db); +ok(defined $parser, "Parser instantiated") or die "Failed to instantiate parser"; + +my $test_data_dir = "./parser_test_data"; +opendir my $dir, $test_data_dir or die "Failed to open Directory: $!"; +my @data_files = readdir $dir; +closedir $dir; + +# if filenames are listed on command line, use them +if ($ARGV[0]) { + @data_files = @ARGV; +} + +foreach my $df (@data_files) { + + next if $df =~ /\.{1,2}/; # skipp . and .. + my $fqn = join('/',$test_data_dir, $df); + + $log->debug("== =="); + $log->debug("== Testing $fqn =="); + $log->debug("== =="); + + my $test = $lhf->get_hash($fqn); + my $edb = {}; + my $fpos = {}; + my $text = $test->{text}; + my @new = $parser->parse($text, $edb, $fpos); + my $result = join('', map { (ref $_) ? $_->as_HTML('') : $_ } @new); + delete $edb->{cache}; + + is ($result, $test->{expect}, "Flaired Text Correctly in $df") + or hdiff($result, $test->{expect}); + + cmp_deeply($edb, $test->{entities}, "EDB Correct in $df") + or die "EDB Differs: ".Dumper($edb, $test); +} + +done_testing(); +exit 0; + +sub hdiff { + my $g = shift; + my $e = shift; + for (my $i = 0; $i < length($g); $i++) { + if ( substr($g, $i, 1) eq substr($e, $i, 1) ) { + print substr($g, $i, 1); + next; + } + print "\n"; + say "[got] ".substr($g, $i); + say "[exp] ".substr($e, $i); + last; + } + die "Produced HTML differs"; +} + + + + + + diff --git a/t/parser_test_data/.template b/t/parser_test_data/.template new file mode 100644 index 0000000..de10d02 --- /dev/null +++ b/t/parser_test_data/.template @@ -0,0 +1,14 @@ +{ + text => <<~EOF, + + EOF + + expect => <<~EOF, + EOF + + entities => { + domain => { + "" => 1, + }, + }, +} diff --git a/t/parser_test_data/cidr_1 b/t/parser_test_data/cidr_1 new file mode 100644 index 0000000..ec4a470 --- /dev/null +++ b/t/parser_test_data/cidr_1 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + The cidr block is 10.10.10.0/30 and has been blocked + EOF + + expect => <<~'EOF', + The cidr block is 10.10.10.0/30 and has been blocked + EOF + + entities => { + cidr => { + "10.10.10.0/30" => 1, + }, + }, +} diff --git a/t/parser_test_data/clsid_1 b/t/parser_test_data/clsid_1 new file mode 100644 index 0000000..47a974d --- /dev/null +++ b/t/parser_test_data/clsid_1 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + "{F20DA720-C02F-11CE-927B-0800095AE340}": "OLE Package Object", + EOF + + expect => <<~'EOF', + "{F20DA720-C02F-11CE-927B-0800095AE340}": "OLE Package Object", + EOF + + entities => { + clsid => { + 'f20da720-c02f-11ce-927b-0800095ae340' => 1, + }, + }, +} diff --git a/t/parser_test_data/cve_1 b/t/parser_test_data/cve_1 new file mode 100644 index 0000000..43072a9 --- /dev/null +++ b/t/parser_test_data/cve_1 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + Foo CVE-2017-12345 Bar + EOF + + expect => <<~EOF, + Foo CVE-2017-12345 Bar + EOF + + entities => { + cve => { + "CVE-2017-12345" => 1 + }, + }, +} diff --git a/t/parser_test_data/domain_3 b/t/parser_test_data/domain_3 new file mode 100644 index 0000000..4144af7 --- /dev/null +++ b/t/parser_test_data/domain_3 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + https://scotdemo.com/journal/123 + EOF + + expect => <<~EOF, + https://scotdemo.com/journal/123 + EOF + + entities => { + domain => { + 'scotdemo.com' => 1 + }, + }, +} diff --git a/t/parser_test_data/domain_id b/t/parser_test_data/domain_id new file mode 100644 index 0000000..5d2b189 --- /dev/null +++ b/t/parser_test_data/domain_id @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + look at the paziapm.co.id domain + EOF + + expect => <<~EOF, + look at the paziapm.co.id domain + EOF + + entities => { + domain => { + "paziapm.co.id" => 1 + }, + }, +} diff --git a/t/parser_test_data/domain_num_component b/t/parser_test_data/domain_num_component new file mode 100644 index 0000000..d8cb9b5 --- /dev/null +++ b/t/parser_test_data/domain_num_component @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + look at the foo.10.com domain + EOF + + expect => <<~EOF, + look at the foo.10.com domain + EOF + + entities => { + domain => { + "foo.10.com" => 1 + }, + }, +} diff --git a/t/parser_test_data/dotted_hex_not_domain b/t/parser_test_data/dotted_hex_not_domain new file mode 100644 index 0000000..dd4fadd --- /dev/null +++ b/t/parser_test_data/dotted_hex_not_domain @@ -0,0 +1,11 @@ +{ + text => <<~EOF, + 8a.93.8c.99.8d.61.62.86.97.88.86.91.91.8e.4e.97.8a.99 + EOF + + expect => <<~EOF, + 8a.93.8c.99.8d.61.62.86.97.88.86.91.91.8e.4e.97.8a.99 + EOF + + entities => {}, +} diff --git a/t/parser_test_data/email_1 b/t/parser_test_data/email_1 new file mode 100644 index 0000000..de67aac --- /dev/null +++ b/t/parser_test_data/email_1 @@ -0,0 +1,18 @@ +{ + text => <<~'EOF', + todd@watermelon.gov + EOF + + expect => <<~'EOF', + + EOF + + entities => { + domain => { + "watermelon.gov" => 1 + }, + email => { + 'todd@watermelon.gov' => 1, + }, + }, +} diff --git a/t/parser_test_data/email_2 b/t/parser_test_data/email_2 new file mode 100644 index 0000000..996974c --- /dev/null +++ b/t/parser_test_data/email_2 @@ -0,0 +1,18 @@ +{ + text => <<~'EOF', + todd_bruner@watermelon.gov + EOF + + expect => <<~'EOF', + + EOF + + entities => { + domain => { + "watermelon.gov" => 1 + }, + email => { + 'todd_bruner@watermelon.gov' => 1, + }, + }, +} diff --git a/t/parser_test_data/email_3 b/t/parser_test_data/email_3 new file mode 100644 index 0000000..0e865d3 --- /dev/null +++ b/t/parser_test_data/email_3 @@ -0,0 +1,18 @@ +{ + text => <<~'EOF', + todd_bruner@watermelon[.]gov + EOF + + expect => <<~'EOF', + + EOF + + entities => { + domain => { + "watermelon.gov" => 1 + }, + email => { + 'todd_bruner@watermelon.gov' => 1, + }, + }, +} diff --git a/t/parser_test_data/email_4 b/t/parser_test_data/email_4 new file mode 100644 index 0000000..34afa33 --- /dev/null +++ b/t/parser_test_data/email_4 @@ -0,0 +1,18 @@ +{ + text => <<~'EOF', + bounces+182497-1c5d-xxxx=watermelon.edu@email.followmyhealth.com + EOF + + expect => <<~'EOF', + + EOF + + entities => { + domain => { + "email.followmyhealth.com" => 1 + }, + email => { + 'bounces+182497-1c5d-xxxx=watermelon.edu@email.followmyhealth.com' => 1, + }, + }, +} diff --git a/t/parser_test_data/email_header_1 b/t/parser_test_data/email_header_1 new file mode 100644 index 0000000..ada2379 --- /dev/null +++ b/t/parser_test_data/email_header_1 @@ -0,0 +1,36 @@ +{ + text => <<~'EOF', + From: "Benz, Mercedes" + To: "Qzzzz, Alll A." , "Blue, Bird" + + CC: HoneyBear + Subject: Re: Cyber Incident Reported + Thread-Topic: Cyber Incident Reported + Message-ID: + + References: + EOF + # note: these should be emails, but I'm doing message-ids because I have + # fixed this yet but need this test to pass + expect => <<~'EOF', + From: "Benz, Mercedes" + To: "Qzzzz, Alll A." , "Blue, Bird" + + CC: HoneyBear + Subject: Re: Cyber Incident Reported + Thread-Topic: Cyber Incident Reported + Message-ID: + + References: + EOF + + entities => { + message_id => { + "" => 1, + "" => 1, + "" => 1, + "" => 1, + "" => 1, + }, + }, +} diff --git a/t/parser_test_data/file_1 b/t/parser_test_data/file_1 new file mode 100644 index 0000000..b7b4ba8 --- /dev/null +++ b/t/parser_test_data/file_1 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + The file was invoice.pdf.exe + EOF + + expect => <<~'EOF', + The file was invoice.pdf.exe + EOF + + entities => { + file => { + "invoice.pdf.exe" => 1, + }, + }, +} diff --git a/t/parser_test_data/file_2 b/t/parser_test_data/file_2 new file mode 100644 index 0000000..57d9d9b --- /dev/null +++ b/t/parser_test_data/file_2 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + The file was haxor.py and its not a domain + EOF + + expect => <<~'EOF', + The file was haxor.py and its not a domain + EOF + + entities => { + file => { + "haxor.py" => 1, + }, + }, +} diff --git a/t/parser_test_data/file_3 b/t/parser_test_data/file_3 new file mode 100644 index 0000000..524b919 --- /dev/null +++ b/t/parser_test_data/file_3 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + Sep 10, 2018 07:33:38 AM Error [ajp-nio-8016-exec-6] - Error Executing Database Query.[Macromedia][SQLServer JDBC Driver][SQLServer]Incorrect syntax near '='. The specific sequence of files included or processed is: /mnt/gfs/cfdocs/eCATT/templates/pgas_rslts.cfm, line: 235 + EOF + + expect => <<~'EOF', + Sep 10, 2018 07:33:38 AM Error [ajp-nio-8016-exec-6] - Error Executing Database Query.[Macromedia][SQLServer JDBC Driver][SQLServer]Incorrect syntax near '='. The specific sequence of files included or processed is: /mnt/gfs/cfdocs/eCATT/templates/pgas_rslts.cfm, line: 235 + EOF + + entities => { + file => { + 'pgas_rslts.cfm' => 1, + }, + }, +} diff --git a/t/parser_test_data/google_1 b/t/parser_test_data/google_1 new file mode 100644 index 0000000..7125085 --- /dev/null +++ b/t/parser_test_data/google_1 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + www.google.com + EOF + + expect => <<~EOF, + www.google.com + EOF + + entities => { + domain => { + "www.google.com" => 1 + }, + }, +} diff --git a/t/parser_test_data/google_2 b/t/parser_test_data/google_2 new file mode 100644 index 0000000..25b456b --- /dev/null +++ b/t/parser_test_data/google_2 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + www{.}google{.}com + EOF + + expect => <<~EOF, + www.google.com + EOF + + entities => { + domain => { + "www.google.com" => 1 + }, + }, +} diff --git a/t/parser_test_data/google_3 b/t/parser_test_data/google_3 new file mode 100644 index 0000000..deff1b9 --- /dev/null +++ b/t/parser_test_data/google_3 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + www(.)google[.]com + EOF + + expect => <<~EOF, + www.google.com + EOF + + entities => { + domain => { + "www.google.com" => 1 + }, + }, +} diff --git a/t/parser_test_data/ipaddr_1 b/t/parser_test_data/ipaddr_1 new file mode 100644 index 0000000..4d2e516 --- /dev/null +++ b/t/parser_test_data/ipaddr_1 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + the offending ipaddr was 192.168.1.1 + EOF + + expect => <<~EOF, + the offending ipaddr was 192.168.1.1 + EOF + + entities => { + ipaddr => { + "192.168.1.1" => 1 + }, + }, +} diff --git a/t/parser_test_data/ipaddr_2 b/t/parser_test_data/ipaddr_2 new file mode 100644 index 0000000..02e169b --- /dev/null +++ b/t/parser_test_data/ipaddr_2 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + the offending ipaddr was 192[.]168(.)1{.}1 + EOF + + expect => <<~EOF, + the offending ipaddr was 192.168.1.1 + EOF + + entities => { + ipaddr => { + "192.168.1.1" => 1 + }, + }, +} diff --git a/t/parser_test_data/ipaddr_3 b/t/parser_test_data/ipaddr_3 new file mode 100644 index 0000000..8f3b9ce --- /dev/null +++ b/t/parser_test_data/ipaddr_3 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + Sometimes we see a stupid ipv4 prefix like ip4:65.38.172[.]37 + EOF + + expect => <<~EOF, + Sometimes we see a stupid ipv4 prefix like ip4:65.38.172.37 + EOF + + entities => { + ipaddr => { + "65.38.172.37" => 1, + }, + }, +} diff --git a/t/parser_test_data/ipaddr_fp b/t/parser_test_data/ipaddr_fp new file mode 100644 index 0000000..eaa70f7 --- /dev/null +++ b/t/parser_test_data/ipaddr_fp @@ -0,0 +1,11 @@ +{ + text => <<~EOF, + the offending ipaddr was 192.168.1.1.a + EOF + + expect => <<~EOF, + the offending ipaddr was 192.168.1.1.a + EOF + + entities => {}, +} diff --git a/t/parser_test_data/ipv6_0 b/t/parser_test_data/ipv6_0 new file mode 100644 index 0000000..e9f781b --- /dev/null +++ b/t/parser_test_data/ipv6_0 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + before 1762::b03:1:af18 after + EOF + + expect => <<~'EOF', + before 1762:0:0:0:0:b03:1:af18 after + EOF + + entities => { + ipv6 => { + "1762:0:0:0:0:b03:1:af18" => 1, + }, + }, +} diff --git a/t/parser_test_data/ipv6_1 b/t/parser_test_data/ipv6_1 new file mode 100644 index 0000000..77da6d0 --- /dev/null +++ b/t/parser_test_data/ipv6_1 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + 1762:0:0:0:0:B03:1:AF18 + EOF + + expect => <<~'EOF', + 1762:0:0:0:0:b03:1:af18 + EOF + + entities => { + ipv6 => { + "1762:0:0:0:0:b03:1:af18" => 1, + }, + }, +} diff --git a/t/parser_test_data/ipv6_2 b/t/parser_test_data/ipv6_2 new file mode 100644 index 0000000..792f099 --- /dev/null +++ b/t/parser_test_data/ipv6_2 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + 2001:41d0:2:9d17:: + EOF + + expect => <<~'EOF', + 2001:41d0:2:9d17:0:0:0:0 + EOF + + entities => { + ipv6 => { + "2001:41d0:2:9d17:0:0:0:0" => 1, + }, + }, +} diff --git a/t/parser_test_data/ipv6_3 b/t/parser_test_data/ipv6_3 new file mode 100644 index 0000000..4a9750d --- /dev/null +++ b/t/parser_test_data/ipv6_3 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + Text before 2001:41d0:2:9d17:: and after + EOF + + expect => <<~'EOF', + Text before 2001:41d0:2:9d17:0:0:0:0 and after + EOF + + entities => { + ipv6 => { + "2001:41d0:2:9d17:0:0:0:0" => 1, + }, + }, +} diff --git a/t/parser_test_data/ipv6_not_1 b/t/parser_test_data/ipv6_not_1 new file mode 100644 index 0000000..2c83b30 --- /dev/null +++ b/t/parser_test_data/ipv6_not_1 @@ -0,0 +1,11 @@ +{ + text => <<~'EOF', + toys::file + EOF + + expect => <<~'EOF', + toys::file + EOF + + entities => {}, +} diff --git a/t/parser_test_data/lboss_1 b/t/parser_test_data/lboss_1 new file mode 100644 index 0000000..18b6daf --- /dev/null +++ b/t/parser_test_data/lboss_1 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + Stuff yr:misc_google_amp_link_s75_1 more stuff + EOF + + expect => <<~'EOF', + Stuff yr:misc_google_amp_link_s75_1 more stuff + EOF + + entities => { + lbsig => { + "yr:misc_google_amp_link_s75_1" => 1 + }, + }, +} diff --git a/t/parser_test_data/lboss_2 b/t/parser_test_data/lboss_2 new file mode 100644 index 0000000..44b673b --- /dev/null +++ b/t/parser_test_data/lboss_2 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + Stuff yr:misc_vbaproj_codepage_foreign_s63_1 more stuff + EOF + + expect => <<~'EOF', + Stuff yr:misc_vbaproj_codepage_foreign_s63_1 more stuff + EOF + + entities => { + lbsig => { + "yr:misc_vbaproj_codepage_foreign_s63_1" => 1 + }, + }, +} diff --git a/t/parser_test_data/message_id_1 b/t/parser_test_data/message_id_1 new file mode 100644 index 0000000..bdc36a6 --- /dev/null +++ b/t/parser_test_data/message_id_1 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + + EOF + + expect => <<~'EOF', + + EOF + + entities => { + message_id => { + '' => 1 + }, + }, +} diff --git a/t/parser_test_data/message_id_2 b/t/parser_test_data/message_id_2 new file mode 100644 index 0000000..76c7267 --- /dev/null +++ b/t/parser_test_data/message_id_2 @@ -0,0 +1,15 @@ +{ + text => <<~ 'EOF', + <CAEr1S5-HuU1MjnUQtqT6Ri-i2ZaYcTm_+cjf6mkmOgwGJHjPJA@mail.gmail.com> + EOF + + expect => <<~'EOF', + + EOF + + entities => { + message_id => { + '' => 1, + }, + }, +} diff --git a/t/parser_test_data/message_id_not_1 b/t/parser_test_data/message_id_not_1 new file mode 100644 index 0000000..042dc60 --- /dev/null +++ b/t/parser_test_data/message_id_not_1 @@ -0,0 +1,33 @@ +{ + text => <<~'EOF', + function Invoke-InternalMonologue + { + <# + .SYNOPSIS + Retrieves NTLMv1 challenge-response for all available users + #> + + $Source = @" + using System.Text.RegularExpressions; + if ( foo > 0 ) { + echo "this sux"; + } + EOF + + expect => <<~'EOF', + function Invoke-InternalMonologue + { + <# + .SYNOPSIS + Retrieves NTLMv1 challenge-response for all available users + #> + + $Source = @" + using System.Text.RegularExpressions; + if ( foo > 0 ) { + echo "this sux"; + } + EOF + + entities => {}, +} diff --git a/t/parser_test_data/mix_1 b/t/parser_test_data/mix_1 new file mode 100644 index 0000000..b448a44 --- /dev/null +++ b/t/parser_test_data/mix_1 @@ -0,0 +1,31 @@ +{ + text => <<~'EOF', + We found that foo.bar.com was originally resolving to 10.10.10.1 but then saw it switch to 1762:0:0:0:0:b03:1:af18. The artifact's clsid (F20DA720-C02F-11CE-927B-0800095AE340) was consistent. Embedded in the sample exploit.exe was an email: todd@watermelon.com. + EOF + + expect => <<~'EOF', + We found that foo.bar.com was originally resolving to 10.10.10.1 but then saw it switch to 1762:0:0:0:0:b03:1:af18. The artifact's clsid (F20DA720-C02F-11CE-927B-0800095AE340) was consistent. Embedded in the sample exploit.exe was an email: . + EOF + + entities => { + domain => { + "watermelon.com" => 1, + "foo.bar.com" => 1, + }, + ipaddr => { + "10.10.10.1" => 1, + }, + ipv6 => { + '1762:0:0:0:0:b03:1:af18' => 1, + }, + file => { + 'exploit.exe' => 1, + }, + clsid => { + 'f20da720-c02f-11ce-927b-0800095ae340' => 1, + }, + email => { + 'todd@watermelon.com' => 1, + }, + }, +} diff --git a/t/parser_test_data/punycode_1 b/t/parser_test_data/punycode_1 new file mode 100644 index 0000000..8c07eab --- /dev/null +++ b/t/parser_test_data/punycode_1 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + foo.xn--p1ai + EOF + + expect => <<~EOF, + foo.xn--p1ai + EOF + + entities => { + domain => { + "foo.xn--p1ai" => 1, + }, + }, +} diff --git a/t/parser_test_data/punycode_2 b/t/parser_test_data/punycode_2 new file mode 100644 index 0000000..d8f826c --- /dev/null +++ b/t/parser_test_data/punycode_2 @@ -0,0 +1,15 @@ +{ + text => <<~EOF, + xn--clapcibic1.xn--p1ai + EOF + + expect => <<~EOF, + xn--clapcibic1.xn--p1ai + EOF + + entities => { + domain => { + "xn--clapcibic1.xn--p1ai" => 1, + }, + }, +} diff --git a/t/parser_test_data/scot_29447 b/t/parser_test_data/scot_29447 new file mode 100644 index 0000000..b1fff61 --- /dev/null +++ b/t/parser_test_data/scot_29447 @@ -0,0 +1,14 @@ +{ + text => <<~'EOF', + funyon@folksinger:/data/scot-29447/pcap$ tshark -n -d udp.port==123,snmp -r e29447_combined.pcap -T fields -e snmp.value.int | sort | uniq -c | sort -nrk1 + EOF + + expect => <<~'EOF', + funyon@folksinger:/data/scot-29447/pcap$ tshark -n -d udp.port==123,snmp -r e29447_combined.pcap -T fields -e snmp.value.int | sort | uniq -c | sort -nrk1 + EOF + entities => { + domain => { + "snmp.value.int" => 1, + }, + }, +} diff --git a/t/parser_test_data/scot_7397 b/t/parser_test_data/scot_7397 new file mode 100644 index 0000000..ab63fcc --- /dev/null +++ b/t/parser_test_data/scot_7397 @@ -0,0 +1,18 @@ +{ + text => <<~EOF, + https://cbase.som.sunysb.edu/soap/bss.cfm + EOF + + expect => <<~EOF, + https://cbase.som.sunysb.edu/soap/bss.cfm + EOF + + entities => { + domain => { + "cbase.som.sunysb.edu" => 1 + }, + file => { + "bss.cfm" => 1 + }, + }, +} diff --git a/t/parser_test_data/suricata_1 b/t/parser_test_data/suricata_1 new file mode 100644 index 0000000..54f6161 --- /dev/null +++ b/t/parser_test_data/suricata_1 @@ -0,0 +1,17 @@ +# test suricata format +{ + text => <<~'EOF', + 2001:489a:2202:2000:0000:0000:0000:0009:53 -> 2620:0106:6008:009b:00f0:0000:0000:0021:57239 + EOF + + expect => <<~'EOF', + 2001:489a:2202:2000:0:0:0:9:53 -> 2620:106:6008:9b:f0:0:0:21:57239 + EOF + + entities => { + ipv6 => { + "2001:489a:2202:2000:0:0:0:9" => 1, + "2620:106:6008:9b:f0:0:0:21" => 1, + }, + }, +} diff --git a/t/parser_test_data/uuid1_1 b/t/parser_test_data/uuid1_1 new file mode 100644 index 0000000..e0e248f --- /dev/null +++ b/t/parser_test_data/uuid1_1 @@ -0,0 +1,15 @@ +{ + text => <<~'EOF', + The file uuid1 was d0229d40-1274-11e8-a427-3d01d7fc9aea + EOF + + expect => <<~'EOF', + The file uuid1 was d0229d40-1274-11e8-a427-3d01d7fc9aea + EOF + + entities => { + uuid1 => { + "d0229d40-1274-11e8-a427-3d01d7fc9aea" => 1, + }, + }, +} diff --git a/t/processing/all.t b/t/processing/all.t new file mode 100755 index 0000000..ac57090 --- /dev/null +++ b/t/processing/all.t @@ -0,0 +1,12 @@ +#!/usr/bin/env perl + +use TAP::Harness; +my %args = ( verbosity => 1 ); +my $harness = TAP::Harness->new(\%args); +$harness->runtests( + 'imgproc.t', + 'job.t', + 'parser.t', + 'processor.t', + 'task_flair.t' +); diff --git a/t/processing/imgproc.t b/t/processing/imgproc.t new file mode 100755 index 0000000..f74395b --- /dev/null +++ b/t/processing/imgproc.t @@ -0,0 +1,42 @@ +#!/usr/bin/env perl +use Mojo::Base -strict; +use strict; +use warnings; + +use Test::More; +use Data::Dumper::Concise; +use DateTime; +use Log::Log4perl; +use lib '../../lib'; +use Flair::Images; +use Flair::Util::Log; +use File::Slurp; +use Digest::MD5 qw(md5_hex); +use HTML::Element; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $proc = Flair::Images->new(log => $log); + +my $datauri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAmVBMVEX///8KCAkKCgkAAAAKDAkHBQYKDwv39/f8/PwlKSYiJiIoKynu7u4NEg4TFxQcIB0WGxdjY2MtMC3FxcWdnZ1zdHSHh4eSkpJsbW18fXysrKxdXl1QUVA3OjhSU1JXWFc/QUBHSUi5ubnb29vw8PBDRUPj4+M7PTvV1dUmJibY2Nijo6PAwMCDg4NoaGgSEhIaGRohICE0MzRIyWS5AAAK00lEQVR4nO2di3qiOBSAa6NCKyoCQsEriEgVbOv7P9wmhEuCqCBRcDb/tx1nWpKT3+A5FFjy9sbhcDgcDofD4XA4HA6Hw+FwOBwOh8PhcDg1EQVv7/vqU/B9zxXEp+oJnuocENZTiCIFe/dZkqKkWtoh5Ql2CYH3FEc30DQkiP+kYWhFqWlJSOvxjoKvGVqDGI70UEdRsozG2T9QUdwYOoqhN0VkqAfCwwT9NFSIMZ9DmIKDWw9SFFWT1po+D1rWPDxEUfTPxL6eRs51+ohZFDfmNNXDYY/PgzJFw3DYK0r6V+qWxJ08jdT0GFt++awzqnA4pnLPEyuWxbPJuC6K/oyctVmT4AmdMc42rjlr3CxmNZvhd5lp5ReDJe69HURjWRouQ0NpOo+6XiJWy8bUovjzZfx3hslGVO35HPUdsWyQeTqE+XyrsfskCsbatlHPNn7JW84fRVEYGw/Ctrczj5mht1xvt6hne5s6NkQ8BvSyVgJWu6moKmvINgJ3b9NBH8d5HDyMLRzQwmC1mwqastvt1mus+VCh20RuEDiixURiZOiaymKxi4hnsiBuOsPb4i3KjD35KMDX4j7w5OGh7BaLBatzGt4SGi6wZLa7NkI8e5HeYjFQ2eymorc4LRKyvXVLyK4fA9H7Nts707GcPhj9EiVuBopCKCaSReyYcLX71O90+j6wOawR/E9FyTk2RuqHhvSpMTJUx4pCOWami2dQFCga0CejQ1NoOFAyniJ1GWIkythgUy6QIal4Lqk8iquBBgN2hvIgp9gG4JhkdoYfg9Y5Dpgajj4+PlqmCIfz8cHQ8BMrUpDBHsflKHBII2aGw0+keObYJHA4n59DdobjT+xIkwV7HBdjwBExNSxWbI7PT7aG/ciwbYzHfZaGJRXhhnJMtXcla1iyHWzA2HBcwnK8mxyJ60VyBT97krU73g6EYW94E8WwnIyVXLbdeGxqWctZ2XbsDIFcMuZQV72MAyjrJ2+1rKE/KNuqy85wVFJxNFMlMUHYLMpOxnDqeELc7M0q98bAjyxgOYdyKUd57BAnh1yz5O4tDww1u1i2GpbzY28YOcrj5AX/Eb2kX3I33GSnToSgj3+ctJDphumPhjNrnzbbgFyfREMUXE785BFDw5E8kksxXBOTIXr2sFyzkLifKwS3t4/82BqOSjqOAHlflmuUGu1wrfnp6QixWy6QDMfE1rCc46g79Ynd1Adl2vS/LC9t5JRqIkcjYmg4HJVlqATEbirN+rcayKMhlWfmN1tksDQsrwg0MtdY4HaL/orIM/sSDWKGQ6aGpR27s+wz9SZuxjfbDfshUWL0soZoSIwNS0oOyZIouubNEfcXRJ5xB+V2Ujwehob9YXmATpVEcHN7Os9UiNSUYd9WXaIkbm+07Y+p45kqhn2Whv3yjn2yJIqwJF7fHCwPWZ7xKgjCMbE1LC8J6JJ4vV2/bxKf21tvB9GuScP+gDxyk67veFSeEeXSMSKYG5YFaB5VEq9ue7SyxBRUDcTOsNutFJgsiYIHrjUeG8Qx0KyKIRwTU8Mqil26JE6vjBvMDz7xe1OlKOwNS4fv5kviFUM6z1QQRIqNGcKZUYkjN293ceRdRcu2FJRmDZFk9N/5S/fsG/nfEuk22d+oPIPfyOs9Ey/dBxiWB0z3xG6qXtxO1lUyz1QO06ChQpXE5YUOgE3kGalyFKaG71VjH8iSeCge/DuYEnlGq2z4ztDwvbLhhCqJw+KNTkSeEQeNGlaew+5vQJbEr8LRg0k+zzRniOawmiQwyJLogKLWPzpxPFP8JlyUQ19sDasC5uTJDE8p6AJsiTzj3RGjWcP3LlUS9SJDk8ozL2cITKoknncBBgaRZxYvaLjLlcSzDWZEnvHvCdGw4TtVEt1Dvo9eLs+8ouGRLIn7v1wn1PGMC3qvaPhD7qZufpao45mzGW7csBN/kf/O/7NzVhLJTcE3kWfe1uBiNxcisjbsde6ALonSN6B+SOeZyp2/R500bNj7oU6c6qRGD+jEYd30RQ07IMyVROJHuwNxseLnZQ3XajYC0VtmHnSesaoLtsSwA0siceR2yERQnskS7fqFDb/Ikuj/piZgReSZ/R2CcTfNG/5RR27HRKXXIS+Kmi9seFYS434AefOFC17acEX9lpiURHRRtFaeaY9hr0ed3w+xDPgziCQ7f2nDDtDpkoi/uaRuvri783YYUiVRspFiD5B5Jrx/Clth2KNKoqAhHXAi88zvixuikkhe8u5AH3Ckbr64X7AlhqcgVxKjPJN+y355ww59yRuWxNzNFzUE22JIl8QT6JnUTV6vb9gDAV0SF+RF0b9/wLBDX/JWgels2OSZ9hjSJXHmUBdF/wXDHn3J2/M9Is/U6bg1hh3qLjDRJZ4ma9SbwvYYkiXxDf0/I0nHdY5n2mRIl0SCoKZgiwzJS94ENfNMiwx7HafoQTJ180yLDOmSmFI3z7TK0C7YTYXTP2RIl8QYtbZgiwzpkhhz/LcMT2r+YZz180yrDOmTGRFa/Slsk2FBSfz+1wz/cs9SZ5Bn2mVIn99/Y5Jn2mY4p7KpxKDLlhn2QKgRqx/cf72JpFWGUJGifoedthk+Am7IDblh83BDbsgNmwewen6p/38wbKVij5nh/qeVk9jrAUZPShY3i1ZOIjRktLaV6B2hYesUoSA4P4N3H5LeQkM4ImBvGD113nWQYcsUkeGU1XP1BfXUuklEbzmwWK2NIG6mbZvESPDEaOEAiGSBlilGhlNWH0NU8+12GUaCHWY7KaoXWmTYFsVoLGDCqlYgXNVukWIvnkKWq66K3gEbtkIRGxZcsKuD4H+1RREPAwwCtovniZKzbYdiLEg92o+N4ubwFys26pgImsU3sNTB9Q3QuGIcvweOgUTcXFWf6Pm+kqqnis04pn5gRjx2mI2dKAiC6wXhb6r4dMksMAATay+5cEhCfcnETnBdSfL2TvhNKD5PkwoJ3o8H1fMkyY0ka1mmsxf5ed5etfQ1yDk+GQD+plrgbzbY0U0V73DM/CK9zd73VecQrn6bdASd7VSzVNX3oWIyj/cpitkOKiFDKKiqgWNp4XH905AjAJ3TzDQsJ0CKezyNd++oIj2F0QwGjmMdNP1rtfsDT5eEEX9O82NoHCzHiQzzk1jR8Gwn9fEcQkXD/FrZp78OuEBtk8JOfwfr5dE0NCQYxFOYzKFw/8fwXBE7htPjar4+nb4hf4jfhB8WJJ1FXcMQp9PaXk2mIfIj9lFqCqsb0qkUKUJHQlI3p8fJbIXWP0Vrgsbr9p1i8NI+32XBi8koStIcL0KIFz1drmaT49TUsR722yR+9++jlGKSbQjJyFIzDD0M0XrraL3uaMlnvGZvbtXQ6wscpgtUolbLJV7QGC+/jZZRD3XdMLTULpm+1K9e1RcJx3hnhZb7SDP2hKZQFbpCWx36hmayxPzXzWXYqYXhp8nC8CF0QlbISztAM7S0B3aDcrGdRNrVO6wRKUsJzyXyjESRqRoESDayhboILcKg0WnoH+IWWtQa31OEugyQGDJL1JBbMndM9GJHShPNZmQqxUuKINnINjGOrWPxEgQxcdu4q/0+0UrFYrUahzHXRVOEFDdGIpVTNmXJtZNiSCd61ljL5TUp3bw0ZV4FuoezCHH4R5mVkD63vvwGFBnkbB47UxwOh8PhcDgcDofD4XA4HA6Hw+FwOBwOh8N5Tf4D7VIaUvDARdEAAAAASUVORK5CYII='; +my $html = << "EOF"; + + + test data uri + + +EOF +my $data_uri_md5 = '2cb29e4d4c5472bd14428c87608c6a35'; + +my $newfile = $proc->convert_data_uri($image); + +is ($newfile, "/tmp/$data_uri_md5.png", "Correct Filename"); +ok (-e $newfile, "File exists"); + + + +done_testing(); +exit 0; + diff --git a/t/processing/job.t b/t/processing/job.t new file mode 100755 index 0000000..b9f1a20 --- /dev/null +++ b/t/processing/job.t @@ -0,0 +1,54 @@ +#!/usr/bin/env perl + +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use lib '../../lib'; +use Log::Log4perl; +use Flair::Task::Flair; +say `pwd`; +my $file = find_rel_dir('etc/testlog.conf'); +say $file; +Log::Log4perl::init($file); +use Data::Dumper::Concise; + +my $t = Test::Mojo->new('Flair'); +system("../../bin/load_core_regexes.pl"); +my $data = { + type => 'alertgroup', + id => 1234, + data => [ + { + alert_id => 44321, + columns => [ 'foo', 'bar', 'boom', 'baz' ], + data => { + foo => '["10.10.10.1", "20.20.20.2"]', # note: string not array (SCOT4) + bar => [ 'getscot.sandia.gov', 'scotdemo.com' ], # arrary (scot3) + boom => 'Text that does not mean much', + baz => '["single element text with filename bad.exe"]', + }, + } + ], +}; + + +$t->post_ok('/flair/v1/flair' => json => $data); +say Dumper($t->tx->res->json); +my $id = $t->tx->res->json->{job_id}; + +$t->app->minion->perform_jobs; + +$t->get_ok("/flair/v1/flair/$id")->status_is(200); +say Dumper($t->tx->res->json); + +done_testing(); + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} diff --git a/t/processing/processor.t b/t/processing/processor.t new file mode 100755 index 0000000..d3a0550 --- /dev/null +++ b/t/processing/processor.t @@ -0,0 +1,402 @@ +#!/usr/bin/env perl +use Mojo::Base -strict; +use strict; +use warnings; + +use Test::More; +use Test::Most; +use Data::Dumper::Concise; +use DateTime; +use Log::Log4perl; +use lib '../../lib'; +use Flair::Processor; +use Flair::Util::Log; +use File::Slurp; +use Digest::MD5 qw(md5_hex); +use Mojo::Pg; +use HTML::Element; +use HTML::TreeBuilder; + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $config = { + image_root => '/tmp/flairtest', + scot_image_root => '/cached_images', + image_store_dir => '/tmp/images', + flair_servername => 'localhost', + pguri => 'postgresql://flairtest:flair1234@localhost:5432/flairtest', + model => { + regex => { + default_list_options => { + fields => ['*'], + where => [], + order => [ '-id' ], + limit => 50, + offset => 0, + }, + default_fetch_options => { + fields => ['*'], + where => [], + order => [ '-id' ], + limit => 1, + offset => 0, + }, + }, + }, +}; + +my $pg = Mojo::Pg->new($config->{pguri}); +$pg->migrations->from_file('../../etc/flair.pg.sql')->migrate(0)->migrate; +system("../../bin/load_core_regexes.pl"); + +my $proc = Flair::Processor->new(log => $log, config => $config); +ok(defined $proc, "Processor instantiated"); + +ok ($proc->wrapq("foo") eq "\"foo\"", "wrapq works"); +ok ($proc->stringify_array(["foo", "bar", "boom"]) eq '["foo", "bar", "boom"]', "stringify_array works"); + +my @a = $proc->arrayify_string('["foo", "bar", "boom"]'); +my @e = ("foo", "bar", "boom"); +cmp_deeply(\@a, \@e, "arrayify worked"); + +# test input is not valid +my @input = ({ + type => "alertgroup", + id => 1, + data => [ "one", "two", "three" ], +}); +is($proc->validate_data(@input), undef, "Detected invalid input for Alertgroup"); + +$input[0]->{type} = "entry"; +$input[0]->{data} = [ { foo => 1 } ]; +is($proc->validate_data(@input), undef, "Detected invalid data in Entry"); + +$input[0]->{data} = [ "now is the time for all good men..."]; +ok($proc->validate_data(@input), "Good input for Entry"); + +$input[0]->{id} = "foo"; +is($proc->validate_data(@input), undef, "Detected bad id"); + + + +my @args = ( { + type => "entry", + id => 1230, + data => [ + 'this is a string to flair', + ], +}); + +my $got = $proc->validate_data(@args); +my $exp = { + type => 'entry', + data => $args[0]->{data}, + id => $args[0]->{id}, +}; +cmp_deeply($got, $exp, "Got valid data from validation"); + + +# build html tree +my $html = 'Foo
  • one
'; +is (ref($proc->build_html_tree($html)), 'HTML::Element', 'Build a tree'); + +# test already_cached +my $uri = "http://foo.com/bar.png"; +is ($proc->already_cached($uri), '', "Correctly identified not cached uri"); +$uri = '/cached_images/bar.png'; +is ($proc->already_cached($uri), 1, "Correctly identified cached uri"); + +# test build_new_uri +is ($proc->build_new_uri('/opt/images/abcdef1234.png'),'/cached_images/abcdef1234.png', 'built the new uri correctly') ; + +# test create_file +# TODO: + +# get_store_dir +my $year = DateTime->now->year; +system("rm -rf ".$config->{image_store_dir}); +ok (! -d $config->{image_store_dir}."/$year", "Directory was missing"); +is ($proc->get_store_dir(), $config->{image_store_dir} . "/$year", "Got Dir"); +ok (-d $config->{image_store_dir}."/$year", "Directory was created"); + +# create db_file_rec +my $fid = $proc->create_db_file_rec("/tmp/images/foo.jpg"); +ok ($fid == 1, "Got file_id for created file record"); + +# local uri +my $local_filename = "/opt/images/foo.jpg"; +my $local_uri = $proc->local_uri($local_filename); +my $file_id = 2; +ok ($local_uri eq "https://".$config->{flair_servername}."/images/$file_id", "Got uri"); + +# is_leaf_node +my $element = HTML::Element->new('p'); +is ($proc->is_leaf_node($element), '', "Not a leaf"); +ok ($proc->is_leaf_node("foobar"), "Is a leaf"); + +# predfiend entity +my $pdedb = {}; +is ($proc->is_predefined_entity($element, $pdedb), undef, "Not predefined flair"); +$element = HTML::Element->new('span', + class => 'entity ipaddr', + 'data-entity-type' => 'ipaddr', + 'data-entity-value' => '10.10.10.1', +); + +ok($proc->is_predefined_entity($element, $pdedb), "Predefined"); +cmp_deeply($pdedb, { 'ipaddr' => { '10.10.10.1' => 1 } }, "Edb correct"); + +$html = q{
This is some text
}; + +my $tree = $proc->build_html_tree($html); +ok(ref($tree) eq "HTML::Element", "Build a tree"); + +my ($fhtml, $ptext) = $proc->output_tree($tree); +ok($fhtml eq '
This is some text
', "Flair html ok"); +ok($ptext eq 'This is some text', "Plain text ok"); + +$html = q{foobar

foobar

}; +$tree = $proc->build_html_tree($html); +my ($f,$p) = $proc->output_tree($tree); +ok($f eq '

foobar

', "Flair html ok"); +ok($p eq 'foobar', "Plain text ok"); + +# merge edb test +my $existing = { 'ipaddr' => { '10.10.10.1' => 1 } }; +my $new = { 'domain' => { 'foo.com' => 1 } }; +$proc->merge_edb($existing, $new); +my $exp_edb = { + 'ipaddr' => { '10.10.10.1' => 1 } , + 'domain' => { 'foo.com' => 1}, +}; +cmp_deeply($existing, $exp_edb, "EDB merged 1 new item"); + +my $adone = { 'ipaddr' => { '10.10.10.1' => 1 }}; + +$proc->merge_edb($existing, $adone); +$exp_edb = { + 'ipaddr' => { '10.10.10.1' => 2 } , + 'domain' => { 'foo.com' => 1}, +}; +cmp_deeply($existing, $exp_edb, "EDB merged 1 existing item"); + +# sparkline test +my $spark = q{["##__SPARKLINE__##","0","1","2","1","0"]}; + +ok($proc->contains_sparkline($spark), "Found sparkline"); + +my $svg = $proc->process_sparkline($spark); +$exp = ''; + +is($svg, $exp, "Sparkline Array corrent"); +my $sparkstr = '[##__SPARKLINE__##, 0, 1, 2, 1, 0]'; +ok($proc->contains_sparkline($sparkstr), "Found sparkline"); +$svg = $proc->process_sparkline($sparkstr); +is($svg, $exp, "Sparkline String in element 0 corrent"); + +$sparkstr = '##__SPARKLINE__##, 0, 1, 2, 1, 0'; +ok($proc->contains_sparkline($sparkstr), "Found sparkline"); +$svg = $proc->process_sparkline($sparkstr); +is($svg, $exp, "Sparkline String corrent"); + +$sparkstr = '"##__SPARKLINE__##", "0", "1", "2", "1", "0"'; +ok($proc->contains_sparkline($sparkstr), "Found sparkline"); +$svg = $proc->process_sparkline($sparkstr); +is($svg, $exp, "Sparkline String corrent"); + +# get column type +my %coltypetest = ( + 'message_id', 'Message_ID', + 'message_id', 'Message-ID', + 'message_id', 'MESSAGE-ID', + 'uuid1', 'lbscanid', + 'uuid1', 'scanid', + 'filename', 'attachments', + 'filename', 'attachment-name', + 'sentinel', 'sentinel_incident_url', + 'normal', 'foobar', +); + +while (my ($type, $col) = each %coltypetest) { + is ($proc->get_column_type($col), $type, "$col returned correct type $type"); +} + +# genspan +is ( $proc->genspan('foo', 'bar'), 'bar', "Genspan works"); + +# sentinel test +is ( $proc->create_sentinel_flair('https://foo.com'), '
view in azure sentinel', 'flair sentinel works'); + +# prepare parser +system('../../bin/load_core_regexes.pl'); +$proc->init_parser(); + +# flair cell +my $alert = { + id => 1, + alertgroup => 10, + data => { + foo => q{["sandia.gov", "scotdemo.com"]} + } +}; + +my $cell_results = $proc->flair_cell($alert, "foo", $alert->{data}->{foo}, {}); +my $exp_flair = ['sandia.gov', 'scotdemo.com']; +my $exp_text = ["sandia.gov", "scotdemo.com"]; +is ($cell_results->{entities}->{domain}->{'sandia.gov'}, 1, "EDB 1 correct"); +is ($cell_results->{entities}->{domain}->{'scotdemo.com'}, 1, "EDB 2 correct"); +cmp_deeply ($cell_results->{flair}, $exp_flair, "Got expected flair"); +cmp_deeply ($cell_results->{text}, $exp_text, "Got expected text"); + +# flair alert +$alert = { + id => 10, + alertgroup => 100, + columns => [ + 'foo', 'sparkline', 'filename', 'sentinel', 'lbscanid', 'message-id' + ], + data => { + foo => [ '10.10.10.1', '192.168.5.1' ], + filename => [ 'foo.exe', 'bar.bat', 'boom.py' ], + sentinel => [ 'https://scotdemo.com' ], + lbscanid => [ 'abcdef12345689' ], + 'message-id'=> [ '' ], + } +}; + +$got = $proc->flair_alert($alert,{}); +my $expected = { + entities => { + domain => { + 'scotdemo.com' => 1, + }, + file => { + 'bar.bat' => 1, + 'boom.py' => 1, + 'foo.exe' => 1, + }, + ipaddr => { + '10.10.10.1' => 1, + '192.168.5.1' => 1, + }, + message_id => { + '' => 1, + }, + uuid1 => { + abcdef12345689 => 1, + }, + }, + flair_data => { + filename => [ + 'foo.exe', + 'bar.bat', + 'boom.py', + ], + foo => [ + '10.10.10.1', + '192.168.5.1', + ], + lbscanid => [ + 'abcdef12345689', + ], + "message-id" => [ + '', + ], + sentinel => [ + 'https://scotdemo.com', + ], + }, + id => 10, + text_data => { + filename => [ + "foo.exe", + "bar.bat", + "boom.py", + ], + foo => [ + "10.10.10.1", + "192.168.5.1", + ], + lbscanid => [ + "abcdef12345689", + ], + "message-id" => [ + '', + ], + sentinel => [ + "https://scotdemo.com", + ], + }, +}; + +cmp_deeply($got, $expected, "Alert row correctly flaired"); +print Dumper($got); + +my $alertgroup = { + id => 100, + data => [ + $alert, + ] +}; + +my $agexp = { + id => $alertgroup->{id}, + type => 'alertgroup', + alerts => [ + $expected, + ], + entities => $expected->{entities}, +}; + +my $gotag = $proc->process_alertgroup(undef, $alertgroup); +cmp_deeply($gotag, $agexp, "Alertgroup correctly flaired"); +# say Dumper($gotag); + +# process an alert with an invalid domain name +$log->debug("invalid domain test"); + +my $inval = { + id => 600, + columns => [ 'foo' ], + data => { + foo => q{["sandia.giv", "scotdemo.foo"]} + }, +}; +my $invag = { + id => 700, + type => 'alertgroup', + data => [ + $inval + ], +}; +my $invexp = { + alerts => [{ + entities => { + domain => { + 'scotdemo.foo' => 1, + }, + }, + id => 600, + flair_data => { + foo => [ 'sandia.giv', ,'scotdemo.foo' ], + }, + text_data => { + foo => [ 'sandia.giv', 'scotdemo.foo' ], + }, + }], + entities => { + domain => { + 'scotdemo.foo' => 1, + } + }, + id => 700, + type => 'alertgroup', +}; + +$gotag = $proc->process_alertgroup(undef, $invag); +cmp_deeply($gotag, $invexp, "Invalid domain rejected"); + +done_testing(); +exit 0; diff --git a/t/processing/simparser.t b/t/processing/simparser.t new file mode 100755 index 0000000..c5b5014 --- /dev/null +++ b/t/processing/simparser.t @@ -0,0 +1,55 @@ +#!/usr/bin/env perl +use Mojo::Base -strict; +use strict; +use warnings; + +use Test::More; +use Test::Most; +use Data::Dumper::Concise; +use DateTime; +use Log::Log4perl; +use lib '../../lib'; +use Flair::Parser; +use Flair::Util::Log; +use File::Slurp; +use Digest::MD5 qw(md5_hex); + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $config = { + image_root => '/tmp/flairtest', +}; + +my $parser = Flair::Parser->new(log => $log); + +ok(defined $parser, "Parser instantiated"); + +my $regexes = [ + { + name => 'cve detection', + description => 'Find CVE-YYYY-XXXX in text', + match => '\b (CVE-(\d{4})-(\d{4,})) \b', + regex => qr{\b (CVE-(\d{4})-(\d{4,})) \b}xims, + entity_type => 'cve', + regex_type => 'core', + re_order => 1, + multiword => 0, + }, +]; + +$parser->regexes($regexes); + +my $text = 'One Two CVE-2022-0001 Three Four'; +my $expected= 'One Two CVE-2022-0001 Three Four'; +my $edb = {}; +my @new = $parser->find_flair($text, $edb); +my $got = join('', map { (ref $_) ? $_->as_HTML('') : $_ } @new); + +is($got, $expected, "Flaired Text correct"); +cmp_deeply($edb, { entities => { cve => { "CVE-2022-0001" => 1 } } }, "Edb Correct"); + + +done_testing(); +exit 0; diff --git a/t/processing/task_flair.t b/t/processing/task_flair.t new file mode 100755 index 0000000..720c781 --- /dev/null +++ b/t/processing/task_flair.t @@ -0,0 +1,29 @@ +use Mojo::Base -strict; + +use Test::More; +use Test::Mojo; +use lib '../../lib'; +use Log::Log4perl; +use Flair::Task::Flair; +say `pwd`; +my $file = find_rel_dir('etc/testlog.conf'); +say $file; +Log::Log4perl::init($file); +use Data::Dumper::Concise; + +my $t = Test::Mojo->new('Flair'); +my $pi = $t->app->plugins; + +say Dumper $pi; + +done_testing(); + +sub find_rel_dir { + my $target = shift; + my @path = (); + + while (not -r join('/', @path, $target)) { + push @path, '..'; + } + return join('/', @path, $target); +} diff --git a/t/processor.t b/t/processor.t new file mode 100755 index 0000000..28a7800 --- /dev/null +++ b/t/processor.t @@ -0,0 +1,324 @@ +#!/opt/perl/bin/perl + +use Mojo::Base -strict; +use Test::Most; +use Data::Dumper::Concise; +use HTML::Element; + +use lib '../lib'; +use Flair::Processor; +use Flair::Util::Log; +use Flair::Util::HTML; +use Flair::Util::Sparkline; +use Flair::Db; + +system("rm /var/flair/test.db"); + +log_init('testlog.conf'); +my $log = get_logger('Flair'); +$log->info("$0 begins"); + +my $config = { + scotapi => { + user => '', + pass => '', + apikey => '', + uri_root => '', + insecure => 1, + }, + database => { + dbtype => 'sqlite', + uri => 'file:/var/flair/test.db', + model => { + regex => {}, + metrics => {}, + admins => {}, + jobs => {}, + }, + migration => '../etc/test.sqlite.sql', + }, +}; + +my $migfile = $config->{database}->{migration}; +my $db = Flair::Db->new(log => $log, config => $config->{database}); +is (ref($db), "Flair::Db", "Got DB connection") or die "unable to connect to db"; +ok ($db->dbh->migrations->from_file($migfile)->migrate(0)->migrate, + "Migrated database") or die "Unable to intialize database"; + +# leaving out scotapi for now, will test seperately + +my $proc = Flair::Processor->new(log => $log, db => $db); +is (ref($proc), "Flair::Processor", "instatiated processor") or die "Failed to instantiate Processor"; + + data_validation_tests(); + leaf_node_detection(); + predefined_entity_detection(); + detect_skippable_columns(); + stringify_array(); + arrayify_string(); + merge_edb(); + html_tree(); + column_types(); + xss_tests(); + node_is_span_detection(); + node_is_noflair_section(); + node_flair_override_section(); + sparkline_tests(); +multi_row_spark_alert(); + +sub xss_tests { + my $html = q{

<script>alert(1);</script%gt;

}; + my $tree = build_html_tree($html); + my ($fhtml, $ptext) = output_tree_html($tree); + print "HTML = $html\n"; + print "FLAIR = $fhtml\n"; + print "PLAIN = $ptext\n"; +} + +sub html_tree { + my $html = q{
This is some text
}; + my $tree = build_html_tree($html); + ok(ref($tree) eq "HTML::Element", "Build a tree"); + + my ($fhtml, $ptext) = output_tree_html($tree); + ok($fhtml eq '
This is some text
', "Flair html ok"); + ok($ptext eq 'This is some text', "Plain text ok"); + + $html = q{foobar

foobar

}; + $tree = build_html_tree($html); + my ($f,$p) = output_tree_html($tree); + ok($f eq '

foobar

', "Flair html ok"); + ok($p eq 'foobar', "Plain text ok"); +} + +sub column_types { + my %coltypetest = ( + 'message_id', 'Message_ID', + 'message_id', 'Message-ID', + 'message_id', 'MESSAGE-ID', + 'uuid1', 'lbscanid', + 'uuid1', 'scanid', + 'filename', 'attachments', + 'filename', 'attachment-name', + 'sentinel', 'sentinel_incident_url', + 'normal', 'foobar', + ); + + while (my ($type, $col) = each %coltypetest) { + is ($proc->get_column_type($col), $type, "$col returned correct type $type"); + } +} + +sub merge_edb { + my $existing = { 'ipaddr' => { '10.10.10.1' => 1 } }; + my $new = { 'domain' => { 'foo.com' => 1 } }; + $proc->merge_edb($existing, $new); + my $exp_edb = { + 'ipaddr' => { '10.10.10.1' => 1 } , + 'domain' => { 'foo.com' => 1}, + }; + cmp_deeply($existing, $exp_edb, "EDB merged 1 new item"); +} + + +sub arrayify_string { + my $s = '["one", "two", "three"]'; + my @a = $proc->arrayify_string($s); + is (scalar(@a), 3, "Got correct number of elements from arrayified string") + or die "Failed to arrayify string"; + is ($a[0], "one", "Got correct item at position 0") + or die "Incorrect item at position 0"; + is ($a[1], "two", "Got correct item at position 2") + or die "Incorrect item at position 2"; + is ($a[2], "three", "Got correct item at position 3") + or die "Incorrect item at position 3"; +} + +sub stringify_array { + my $a = [ qw(one two three) ]; + my $s = $proc->stringify_array($a); + is ($s, '["one", "two", "three"]', "Correctly Stringified Array") + or die "Failed to stringify array"; +} + +sub detect_skippable_columns { + ok($proc->is_skippable_column("columns"), "Detected skippable column: columns") + or die "Failed to detect column: columns"; + ok($proc->is_skippable_column("_raw"), "Detected skippable column: _raw") + or die "Failed to detect column: _raw"; + ok($proc->is_skippable_column("search"), "Detected skippable column: search") + or die "Failed to detect column: search"; +} + +sub predefined_entity_detection { + my $node = HTML::Element->new('span', + class => "entity ipaddr", + 'data-entity-type' => "ipaddr", + 'data-entity-value' => "10.10.10.10", + ); + my $edb = {}; + ok ($proc->is_predefined_entity($node, $edb), "Found predefined entity") + or die "Missed predefined entity"; + cmp_deeply($edb, {ipaddr => { '10.10.10.10' => 1 }}, "EDB updated correctly") + or die "Incorrect update of edb"; +} + +sub leaf_node_detection { + my $leaf_element = HTML::Element->new( + 'p', + ); + $leaf_element->push_content("fall like a leaf"); + + my @content = $leaf_element->content_list; + my $child = $content[0]; + ok($proc->is_leaf_node($child), "Found Leaf Node") or die "Failed to find leaf node"; + + my $non_leaf_element = HTML::Element->new( + 'div', + ); + my $sibling_element = HTML::Element->new( + 'div', + ); + $sibling_element->push_content("testing is fun"); + $non_leaf_element->push_content($sibling_element,$leaf_element); + @content = $non_leaf_element->content_list; + $child = $content[0]; + ok(! $proc->is_leaf_node($child), "Found non Leaf Node") or die "Found leaf, when there was none"; +} + +sub node_is_span_detection { + my $not_span_node = HTML::Element->new('p'); + $not_span_node->push_content("foobar"); + + my $span_node = HTML::Element->new('span'); + $span_node->push_content("this is a span"); + + ok(! $proc->node_is_span($not_span_node), "correctly did not find node for non-span node"); + ok($proc->node_is_span($span_node), "correctly found a span"); +} + +sub node_is_noflair_section { + my $noflair1 = HTML::Element->new('noflair'); + $noflair1->push_content('do not flair this'); + + ok($proc->is_no_flair_span($noflair1), "Correctly found "); + + my $noflair2 = HTML::Element->new('span', class => 'noflair'); + $noflair2->push_content("also do not flair"); + + print "noflair2 = ".$noflair2->dump."\n"; + + ok($proc->is_no_flair_span($noflair2), "Correctly found "); +} + +sub node_flair_override_section { + my $nonsection = HTML::Element->new('span', 'class' => 'foobar'); + $nonsection->push_content('zoooooooo'); + my $section = HTML::Element->new('span', class => "flairoverride", 'data-entity-type' => "foobar"); + $section->push_content("boombaz"); + + ok (!$proc->is_flair_override_span($nonsection), "correctly did not detect override in normal span"); + ok ($proc->is_flair_override_span($section), "Detected flair override correctly"); + + my $edb = {}; + my $tedb = { + foobar => { boombaz => 1 } + }; + my $span = $proc->extract_flair_override($section, $edb); + + ok (ref($span) eq "HTML::Element", "Got an HTML::Element"); + ok ($span->tag eq "span", "Got a Span"); + ok ($span->attr('class') eq 'entity foobar', 'Got right classes'); + ok ($span->attr('data-entity-type') eq "foobar", "Got right data-entity-type"); + ok ($span->attr('data-entity-value') eq "boombaz", "Got right entity-value"); + + cmp_deeply($edb, $tedb, "populated edb correctly"); + +} + +sub data_validation_tests { + my @valid_request_data1 = ({ + type => 'alertgroup', + id => 1234, + data => { + alerts => [ + {alert_id => 1, columns => [qw(foo bar boom)], data => { foo => 1, bar => 2, boom => 3 }}, + {alert_id => 2, columns => [qw(foo bar boom)], data => { foo => 7, bar => 8, boom => 9 }}, + ], + }, + }); + ok ($proc->validate_data(@valid_request_data1), "Valid data passed") + or die "marked valid data invalid"; + + @valid_request_data1 = ({ + type => 'entry', + id => 1234, + data => 'this is a test of the emergency broadcast alert', + }); + ok ($proc->validate_data(@valid_request_data1), "Valid data passed") + or die "marked valid data invalid"; + + my @invalid_request_data1 = ({ + type => 'foo', + id => 1234, + data => 'foobar', + }); + ok (!$proc->validate_data(@invalid_request_data1), "Detected invalid data") + or die "Passed invalid data"; + + @invalid_request_data1 = ({ + type => 'alertgroup', + id => 1234, + data => 'foobar', + }); + ok (!$proc->validate_data(@invalid_request_data1), "Detected invalid data") + or die "Passed invalid data"; + + + @invalid_request_data1 = ({ + type => 'entry', + id => 1234, + data => { foo => 'bar' }, + }); + ok (!$proc->validate_data(@invalid_request_data1), "Detected invalid data") + or die "Passed invalid data"; +} + +sub sparkline_tests { + my $data = <<'EOF'; +MULTILINE_SPARKLINE_TABLE +198.251.83.27,11,##__SPARKLINE__## 11 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0,lc_nm_udp:zeek_conn_scan:dns_answer:zeek_conn_est:lc_ca_udp:lc_ca_tcp:lc_nm_tcp +69.46.15.151,1,##__SPARKLINE__## 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0,lc_ca_udp +dns102.registrar-servers.com,365,##__SPARKLINE__## 162 3 6 5 7 6 5 3 6 7 4 2 2 4 0 1 6 6 3 1 1 2 6 3 7 8 2 0 0 2 4 5 3 3 1 7 7 5 7 8 6 4 3 6 5 5 3 4 1 6,dns_query:dns_answer:lc_nm_dns:lc_ca_dns +EOF + + ok (contains_multi_row_sparklines($data), "Found multiline spark table"); + is (contains_multi_row_sparklines('##__SPARKLINE__##'), undef, "not multi-line sparkline"); + is (contains_sparkline('##__SPARKLINE__##'), 1, "not multi-line sparkline, is singleline"); + + my $table = $proc->build_multi_sparkline_table($data); + + say $table; + +} + +sub multi_row_spark_alert { + my $alert = { row => { + "Intel_id" => "5749", + "Subject" => "LRI alert test", + "Tags" => ["test"], + "Sources" => ["mandiant"], + "LRI Hit Information" => <<"EOF" +MULTILINE_SPARKLINE_TABLE +dns101.registrar-servers.com,365,##__SPARKLINE__## 162 4 5 6 7 5 4 4 6 6 5 0 3 3 0 2 6 5 3 1 1 4 4 5 7 7 1 0 0 3 4 6 1 3 3 6 7 6 7 8 5 4 3 7 4 4 4 4 2 6,lc_nm_dns:dns_answer:lc_ca_dns:dns_query +146.70.53.153,4,##__SPARKLINE__## 3 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0,lc_nm_udp:lc_nm_tcp:zeek_conn_scan +EOF + }}; + my $fp; + my $result = $proc->flair_alert($alert, $fp); + say Dumper $result; +} + + +done_testing(); +exit 0; diff --git a/t/re1.pl b/t/re1.pl new file mode 100755 index 0000000..c816e06 --- /dev/null +++ b/t/re1.pl @@ -0,0 +1,39 @@ +#!/opt/perl/bin/perl +# use Regexp::Debugger; +use warnings; +no warnings qw(experimental::vlb); + +my $re = qr{ + ( + (?: + # one or more of these + [\=a-z0-9!\#$%&'*+/?^_`{|}~-]+ + # zero or more of these + (?:\.[\=a-z0-9!\#$%&'*+/?^_`{|}~-]+)* + ) + @ + (?: + (?!\d+\.\d+) + (?=.{4,255}) + (?: + (?:[a-zA-Z0-9-]{1,63}(?, "Foobar" +Message-ID: + +More text. + +EOF + +while ( $text =~ m/$re/g ) { + print "$1\n"; +} diff --git a/t/regex.pl b/t/regex.pl new file mode 100755 index 0000000..f0b6640 --- /dev/null +++ b/t/regex.pl @@ -0,0 +1,45 @@ +#!/opt/perl/bin/perl +# use Regexp::Debugger; +use warnings; +no warnings qw(experimental::vlb); + +my $re = qr{ + ( + (?: + # one or more of these + [\=a-z0-9!\#$%&'*+/?^_`{|}~-]+ + # zero or more of these + (?:\.[\=a-z0-9!\#$%&'*+/?^_`{|}~-]+)* + ) + @ + (?: + (?!\d+\.\d+) + (?=.{4,255}) + (?: + (?:[a-zA-Z0-9-]{1,63}(?, "Foobar" +MessageID: + +More text. + +EOF + +while ( $text =~ m/$re/g ) { + + my $pre = substr($text, 0, $-[0]); + my $match = substr($text, $-[0], $+[0] - $-[0]); + my $post = substr($text, $+[0]); + + print "Pre = $pre\n"; + print "Match = $match\n"; + print "Pos = $post\n"; +} diff --git a/t/retry.t b/t/retry.t new file mode 100755 index 0000000..7abe966 --- /dev/null +++ b/t/retry.t @@ -0,0 +1,18 @@ +#!/opt/perl/bin/perl + +use Try::Tiny::Retry ':all'; +my $i = 3; +my $foo = retry { doit($i++); } + catch { print "caught exception = $_\n"; return;} + on_retry { print "Retrying...\n"; } + delay_exp { 3, 10000} + # delay { return if $_[0] >= 3; sleep 1 }; + ; +print "foo = $foo\n"; + +sub doit { + my $i = shift; + print "Doit($i)\n"; + die "Fudge!" if ($i > 2 and $i <5); + return "wow"; +} diff --git a/t/util/all.t b/t/util/all.t new file mode 100755 index 0000000..5601dc9 --- /dev/null +++ b/t/util/all.t @@ -0,0 +1,11 @@ +#!/usr/bin/env perl + +use TAP::Harness; +my %args = ( verbosity => 1 ); +my $harness = TAP::Harness->new(\%args); +$harness->runtests( + './config.t', + './sparkline.t', + './html.t', + './timer.t', +); diff --git a/t/util/config.t b/t/util/config.t new file mode 100755 index 0000000..47940da --- /dev/null +++ b/t/util/config.t @@ -0,0 +1,63 @@ +#!/usr/bin/env perl + +use Test::More; +use Test::Deep; +use lib '../../lib'; + +use Flair::Util::Config; +use Data::Dumper::Concise; + +my $fuc = Flair::Util::Config->new(); +my $config = $fuc->get_config("flair.conf"); + +my $expected = { + default_expiration => 14400, + hypnotoad => { + clients => 1, + heartbeat_timeout => 40, + listen => [ + "http://localhost:3001?reuse=1", + ], + pidfile => "/var/run/flair.hypno.pid", + proxy => 1, + workers => 5, + }, + logconf => "log.conf", + mode => "development", + model => { + regex => { + default_fetch_options => { + fields => [ + "*", + ], + limit => 1, + offset => 0, + order => [ + "-id", + ], + where => [], + }, + default_list_options => { + fields => [ + "*", + ], + limit => 50, + offset => 0, + order => [ + "-id", + ], + where => [], + }, + }, + }, + pguri => "postgresql://flairtest:flair1234\@localhost:5432/flairtest", + secrets => [ + "f00b\@r4w1n", + "dafasdfasdf", + ], + version => "1.0", +}; + +cmp_deeply($config, $expected, "Correctly parsed config"); +done_testing(); + diff --git a/t/util/html.t b/t/util/html.t new file mode 100755 index 0000000..1268293 --- /dev/null +++ b/t/util/html.t @@ -0,0 +1,48 @@ +#!/opt/perl/bin/perl + +use Test::More; + +use lib '../../lib'; +use Flair::Util::HTML; + +my $html = << 'EOF'; + + + Foobar + + +
    +
  • Foo
  • +
  • Bar
  • +
+ + +EOF + +my $tree = build_html_tree($html); + +is(ref($tree), "HTML::Element", "Got expected object"); + +my ($div, $plain) = output_tree_html($tree); +is ($div, '
  • Foo
  • Bar
', "Tree round trip worked"); +is ($plain, 'FooBar', 'So did plain text'); + +my $span = generate_span("foobar", "boombaz"); +is($span, 'boombaz', "Generated span correctly"); + +my $sentinel = create_sentinel_flair("http://foo.com/xyz"); +is ($sentinel, 'View in Azure Sentinel', "created sentinel anchor correctly"); + + +my $xss = q{

<script>alert(1);</script>

}; +my $xtree = build_html_tree($xss); +my ($xdiv, $xplain) = output_tree_html($xtree); + +print <<"EOF"; +XSS = $xss +DIV = $xdiv +PLAIN = $xplain +EOF + +done_testing(); +exit 0; diff --git a/t/util/sparkline.t b/t/util/sparkline.t new file mode 100755 index 0000000..c957c0b --- /dev/null +++ b/t/util/sparkline.t @@ -0,0 +1,32 @@ +#!/opt/perl/bin/perl + +use Test::Most; + +use lib '../../lib'; +use Flair::Util::Sparkline; +use Data::Dumper::Concise; +use feature qw(say); + +my $data = '[ "##__SPARKLINE__##", "1", "2", "", "4", "0", "5" ]'; + +ok (contains_sparkline($data), "correctly identified a sparkline string"); +ok (!contains_sparkline('1,3,4,4'), "correctly did not identify a numeric string"); + +my @sdata = normalize_sparkline_string($data); +is (scalar(@sdata), 5, "correct number of elements"); + +my $svg = data_to_sparkline_svg($data); +is ($svg, '', "Correct SVG generated"); + +my @d = ('##__SPARKLINE__##', '5', '4', '', '3', '2','1'); +ok(contains_sparkline(\@d), "correctly identified a sparkline array"); +ok(!contains_sparkline([1,2,3]), "correctly did not identify numerica array as sparkline"); + +my @s2data = normalize_sparkline_array(\@d); +is (scalar(@s2data), 5, "correct number of elements"); +my $svg2 = data_to_sparkline_svg(\@d); +is($svg2, "", "Correct SVG"); + + +done_testing(); +exit 0; diff --git a/t/util/timer.t b/t/util/timer.t new file mode 100755 index 0000000..a47121e --- /dev/null +++ b/t/util/timer.t @@ -0,0 +1,25 @@ +#!/usr/bin/env perl + +use Test::More; +use Test::Deep; +use lib '../../lib'; + +use Flair::Util::Timer; +use Data::Dumper::Concise; +use feature qw(say); + +my $timer1 = get_timer(); +sleep 1; +my $e1 = &$timer1; + +say $e1; +ok ($e1 < 2, "Timer probably correct"); + +is ( get_english_duration(3600),"1 hour", "duration correct"); + +my $dt = DateTime->now(); +is ( get_ymdhms_time(), join(' ',$dt->ymd, $dt->hms), "get_ymdhms_timer correct"); + + +done_testing(); + diff --git a/templates/controller/admins/dt.html.ep b/templates/controller/admins/dt.html.ep new file mode 100644 index 0000000..4a577be --- /dev/null +++ b/templates/controller/admins/dt.html.ep @@ -0,0 +1,31 @@ +% layout 'default'; +% my $prefix = $ENV{'MOJO_REVERSE_PROXY'}; +

Admins

+ + + + + + + + + + + + + + +
AdminIdUpdatedUsernameWhoLastLoginLastAccessPWHash
+ + + + diff --git a/templates/controller/admins/edit.html.ep b/templates/controller/admins/edit.html.ep new file mode 100644 index 0000000..2a2bad2 --- /dev/null +++ b/templates/controller/admins/edit.html.ep @@ -0,0 +1,36 @@ +% layout 'default'; +

Admins

+ +% use Data::Dumper::Concise; +% my @cols = (qw(admin_id updated username who lastlogin lastaccess pwhash)); + + + + + + + + + + % foreach my $col (@cols) { + + + % my $val = $result->{$col}; + + % } + + +
<%== $col %>
+ + + diff --git a/templates/controller/admins/newitem.html.ep b/templates/controller/admins/newitem.html.ep new file mode 100644 index 0000000..9fab782 --- /dev/null +++ b/templates/controller/admins/newitem.html.ep @@ -0,0 +1,28 @@ +% layout 'default'; +

Admins

+ +% use Data::Dumper::Concise; +% my @cols = (qw(username who pwhash)); + + + + + + + + % foreach my $col (@cols) { + + + + % } + + +
<%== $col %>
+ + + diff --git a/templates/controller/apikeys/dt.html.ep b/templates/controller/apikeys/dt.html.ep new file mode 100644 index 0000000..743b663 --- /dev/null +++ b/templates/controller/apikeys/dt.html.ep @@ -0,0 +1,34 @@ +% layout 'default'; +% my $prefix = $ENV{'MOJO_REVERSE_PROXY'}; +

ApiKeys

+ + + + + + + + + + + + + + + + + +
ApikeyIdUpdatedUsernameApikeyLastAccessFlairJobRegex_RORegex_CRUDMetrics
+ + + + diff --git a/templates/controller/apikeys/edit.html.ep b/templates/controller/apikeys/edit.html.ep new file mode 100644 index 0000000..40bc6d5 --- /dev/null +++ b/templates/controller/apikeys/edit.html.ep @@ -0,0 +1,37 @@ +% layout 'default'; +

Apikey

+ +% use Data::Dumper::Concise; +% my @cols = (qw(apikey_id username apikey flairjob regex_ro regex_crud metrics )); +% my @boolcols = (qw(flairjob regex_ro regex_crud metrics)); + + + + + + + + + + % foreach my $col (@cols) { + + + % my $val = $result->{$col}; + + % } + + +
<%== $col %>
+ + + diff --git a/templates/controller/apikeys/newitem.html.ep b/templates/controller/apikeys/newitem.html.ep new file mode 100644 index 0000000..801420f --- /dev/null +++ b/templates/controller/apikeys/newitem.html.ep @@ -0,0 +1,37 @@ +% layout 'default'; +

Apikeys

+ +% use Data::Dumper::Concise; +% my @cols = (qw(username apikey flairjob regex_ro regex_crud metrics )); +% my @boolcols = (qw(flairjob regex_ro regex_crud metrics)); + + + + + + + % foreach my $col (@cols) { + + + % if (grep {/$col/} @boolcols) { + + % } else { + + % } + % } + + +
<%== $col %> + +
+ + + diff --git a/templates/controller/auth/login.html.ep b/templates/controller/auth/login.html.ep new file mode 100644 index 0000000..187f9ac --- /dev/null +++ b/templates/controller/auth/login.html.ep @@ -0,0 +1,28 @@ +% my $prefix = $ENV{MOJO_REVERSE_PROXY}; + + + Flair Login + + +
+

FLAIR Login

+
+ + + + + + + + + + + <%= csrf_field %> + +
+ Flair Logo +

 
+
+
+ + diff --git a/templates/controller/metrics/dt.html.ep b/templates/controller/metrics/dt.html.ep new file mode 100644 index 0000000..522be01 --- /dev/null +++ b/templates/controller/metrics/dt.html.ep @@ -0,0 +1,30 @@ +% layout 'default'; +% my $prefix = $ENV{'MOJO_REVERSE_PROXY'}; +

Metrics

+ + + + + + + + + + + + + +
MetricIdYearMonthDayHourMetricValue
+ + + + diff --git a/templates/controller/metrics/edit.html.ep b/templates/controller/metrics/edit.html.ep new file mode 100644 index 0000000..5d814cf --- /dev/null +++ b/templates/controller/metrics/edit.html.ep @@ -0,0 +1,34 @@ +% layout 'default'; +

Metrics

+ +% use Data::Dumper::Concise; +% my @cols = (qw(metric_id year month day hour metric value)); + + + + + + + + + + % foreach my $col (@cols) { + + + % my $val = $result->{$col}; + + % } + + +
<%== $col %>
+ + + diff --git a/templates/controller/metrics/newitem.html.ep b/templates/controller/metrics/newitem.html.ep new file mode 100644 index 0000000..c6067c4 --- /dev/null +++ b/templates/controller/metrics/newitem.html.ep @@ -0,0 +1,28 @@ +% layout 'default'; +

Metrics

+ +% use Data::Dumper::Concise; +% my @cols = (qw(year month day hour metric value)); + + + + + + + + % foreach my $col (@cols) { + + + + % } + + +
<%== $col %>
+ + + diff --git a/templates/controller/metrics/status.html.ep b/templates/controller/metrics/status.html.ep new file mode 100644 index 0000000..a9f4ccd --- /dev/null +++ b/templates/controller/metrics/status.html.ep @@ -0,0 +1,95 @@ +% layout 'default'; +% my @metrics = (qw(entry_processed alertgroup_processed remoteflair_processed flairjobs_requested invalid_flairjob_request entites_found completed_flairjobs elapsed_flair_time parsed_data_size images_replaced)); +% my $height = "300px"; +% my $width = "300px"; +% my $datum_limit = 10; + +

Status

+ + + + + + % for (my $i = 0; $i < 5; $i ++) { + % my $metric = $metrics[$i]; + % my $mname = $metric."_value"; + + % } + + + % for (my $i = 0; $i < 5; $i ++) { + % my $metric = $metrics[$i]; + + % } + +
<%= $metric %>
+
+ % my $chartname = $metric."_chart"; + +
+
diff --git a/templates/controller/regex/dt.html.ep b/templates/controller/regex/dt.html.ep new file mode 100644 index 0000000..0f1b55b --- /dev/null +++ b/templates/controller/regex/dt.html.ep @@ -0,0 +1,34 @@ +% layout 'default'; +% my $prefix = $ENV{'MOJO_REVERSE_PROXY'}; +

Regex

+ + + + + + + + + + + + + + + +
RegexIdNameDescriptionRegexEntityTypeRegexTypeRegexOrdermultiword
+ + diff --git a/templates/controller/regex/edit.html.ep b/templates/controller/regex/edit.html.ep new file mode 100644 index 0000000..83f9a13 --- /dev/null +++ b/templates/controller/regex/edit.html.ep @@ -0,0 +1,41 @@ +% layout 'default'; +

Regexes

+ +% use Data::Dumper::Concise; +% my @cols = (qw(regex_id name description match entity_type regex_type re_order multiword)); + + + + + + + + + + % foreach my $col (@cols) { + + + % my $val = $result->{$col}; + % if ($col eq "match") { + + % } else { + + % } + % } + + +
<%== $col %>
+ + + diff --git a/templates/controller/regex/newregex.html.ep b/templates/controller/regex/newregex.html.ep new file mode 100644 index 0000000..0319562 --- /dev/null +++ b/templates/controller/regex/newregex.html.ep @@ -0,0 +1,37 @@ +% layout 'default'; +

Regexes

+ +% use Data::Dumper::Concise; +% my @cols = (qw(name description match entity_type regex_type re_order multiword)); + + + + + + + % foreach my $col (@cols) { + + + % if ($col eq "match") { + + % } else { + + % } + % } + + +
<%== $col %>
+ + + diff --git a/templates/example/welcome.html.ep b/templates/example/welcome.html.ep new file mode 100644 index 0000000..0a67219 --- /dev/null +++ b/templates/example/welcome.html.ep @@ -0,0 +1,9 @@ +% layout 'default'; +% title 'Welcome'; +

<%= $msg %>

+

+ This page was generated from the template "templates/example/welcome.html.ep" + and the layout "templates/layouts/default.html.ep", + <%= link_to 'click here' => url_for %> to reload the page or + <%= link_to 'here' => '/index.html' %> to move forward to a static page. +

diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep new file mode 100644 index 0000000..ebaaf5a --- /dev/null +++ b/templates/layouts/default.html.ep @@ -0,0 +1,105 @@ +% my $prefix = $ENV{MOJO_REVERSE_PROXY}; + + + + FLAIR <%= title %> + + + <%= datatable_js %> + <%= datatable_css %> + + + +
+ + + + + + + + + + + +
+ <%= content %> + +