diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..2bfa8b5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+linux_header.sh text eol=lf
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8f1859f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+target*
+*.dep
+src/main/resources/git.properties
+.flattened-*.xml
+
+.classpath
+.factorypath
+.project
+.settings/
+
+.vscode
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b531235
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8b92343
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+![Logpresso Logo](logo.png)
+
+Logpresso FirewallOps is a single binary command-line tool for iptables and firewalld policy automation. It receives blocklist from Logpresso Watch and update firewall drop rule periodically.
+
+## Requirement
+* [Logpresso Watch](https://logpresso.watch) service user
+* iptables or firewalld installation
+ * iptables with ipset
+ * firewalld
+* Connectivity with [Logpresso Watch](https://logpresso.watch)
+ * If you cannot connect to internet from server farm directly, use [Logpresso HTTP proxy](https://github.com/logpresso/http-proxy) for relay.
+
+### Usage
+```
+Logpresso Firewall Ops 0.1.0 (2022-01-31)
+Usage: logpresso-firewall-ops [start|install|uninstall]
+ start
+ install [api-key] [http-proxy ip:port]
+ uninstall
+```
+
+### Getting Started
+
+* Join Logpresso Watch and copy Blocklist API Key from Profile page.
+* Install FirewallOps as systemd service.
+ * `# ./logpresso-firewall-ops install YOUR_BLOCKLIST_API_KEY`
+ * logpresso-firewall-ops.conf file will be created in the same directory which contains logpresso-firewall-ops binary.
+ * FirewallOps uses `firewalld-cmd --state` to detect firewalld is running. If firewalld is not available, it fallback to iptables backend.
+* Review logpresso-firewall-ops.conf configuration.
+ * `[allowlist]` seciton contains default private network subnets to prevent accidental IP blocking like this:
+ ```
+ [allowlist]
+ 10.0.0.0/8
+ 172.16.0.0/12
+ 192.168.0.0/16
+ ```
+ * Add more IP addresses related to normal service operation.
+* Start systemd service.
+ * `# systemctl start logpresso-firewall-ops`
+* Check service status
+ * For systemd service - `# systemctl status logpresso-firewall-ops`
+ * For iptables - `iptables -L -n`
+ * `DROP all -- 0.0.0.0/0 0.0.0.0/0 match-set logpresso-watch src`
+ * To see ipset content - `# ipset save logpresso-watch`
+
+### Uninstall
+* Stop systemd service first.
+ * `# systemctl stop logpresso-firewall-ops`
+* Run FirewallOps with uninstall option.
+ * '# ./logpresso-firewall-ops uninstall`
+ * It will delete systemd file, config file, and reload systemd daemon.
+
+
+### Contact
+If you have any question or issue, create an issue in this repository.
+
diff --git a/logo.png b/logo.png
new file mode 100644
index 0000000..719a361
Binary files /dev/null and b/logo.png differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1a12124
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,120 @@
+
+
+ 4.0.0
+ com.logpresso
+ firewall-ops
+ 1.0.0
+ jar
+ Logpresso Firewall Ops
+
+
+
+ Apache Software License 2
+ repo
+
+
+
+
+ UTF-8
+ UTF-8
+ 1.7
+ 1.7
+
+
+
+
+
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ com.logpresso.firewallops.FirewallOpsAgent
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+
+ make-linux-launch-script
+ package
+
+ java
+
+
+ com.logpresso.firewallops.AttachScriptAndJar
+
+
+
+
+
+ attach_script_and_jar.base_dir
+ ${project.basedir}
+
+
+ attach_script_and_jar.target_dir
+ ${project.build.directory}
+
+
+ attach_script_and_jar.input_script
+ src/main/sh/linux_header.sh
+
+
+ attach_script_and_jar.input_jar
+ ${project.build.finalName}.jar
+
+
+ attach_script_and_jar.output_name
+ logpresso-firewall-ops
+
+
+
+
+
+
+
+
+
+
+
+ native
+
+
+
+ org.graalvm.buildtools
+ native-maven-plugin
+ 0.9.8
+ true
+
+
+ build-native
+
+ build
+
+ package
+
+
+
+ ${project.artifactId}
+
+
+
+
+
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+
diff --git a/src/main/java/com/logpresso/firewallops/AttachScriptAndJar.java b/src/main/java/com/logpresso/firewallops/AttachScriptAndJar.java
new file mode 100644
index 0000000..881e30f
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/AttachScriptAndJar.java
@@ -0,0 +1,63 @@
+package com.logpresso.firewallops;
+
+import java.io.*;
+
+public class AttachScriptAndJar {
+ public static void main(String[] args) {
+ File scriptFile = new File(
+ new File(System.getProperty("attach_script_and_jar.base_dir")),
+ System.getProperty("attach_script_and_jar.input_script"));
+
+ File jarFile = new File(
+ new File(System.getProperty("attach_script_and_jar.target_dir")),
+ System.getProperty("attach_script_and_jar.input_jar"));
+
+ if (!scriptFile.exists())
+ throw new IllegalArgumentException("input_script does not exists: " + scriptFile.getAbsolutePath());
+
+ if (!jarFile.exists())
+ throw new IllegalArgumentException("input_jar does not exists: " + jarFile.getAbsolutePath());
+
+ File outputFile = new File(
+ new File(System.getProperty("attach_script_and_jar.target_dir")),
+ System.getProperty("attach_script_and_jar.output_name"));
+
+ FileOutputStream fos = null;
+ FileInputStream fis1 = null, fis2 = null;
+ try {
+ System.out.println("input script: " + scriptFile.getAbsolutePath());
+ System.out.println("input JAR : " + jarFile.getAbsolutePath());
+
+ fos = new FileOutputStream(outputFile, false);
+ fis1 = new FileInputStream(scriptFile);
+ fis2 = new FileInputStream(jarFile);
+
+ byte[] buf = new byte[32768];
+ for (int read = fis1.read(buf); read != -1; read = fis1.read(buf)) {
+ fos.write(buf, 0, read);
+ }
+ for (int read = fis2.read(buf); read != -1; read = fis2.read(buf)) {
+ fos.write(buf, 0, read);
+ }
+
+ System.out.println("** Successfully merged script and jar: " + outputFile.getAbsolutePath());
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ close(fos);
+ close(fis1);
+ close(fis2);
+ }
+ }
+
+ private static void close(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (Exception ignored) {
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/logpresso/firewallops/Backend.java b/src/main/java/com/logpresso/firewallops/Backend.java
new file mode 100644
index 0000000..0ca7ae7
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/Backend.java
@@ -0,0 +1,5 @@
+package com.logpresso.firewallops;
+
+public enum Backend {
+ FIREWALLD, IPTABLES
+}
diff --git a/src/main/java/com/logpresso/firewallops/Configuration.java b/src/main/java/com/logpresso/firewallops/Configuration.java
new file mode 100644
index 0000000..9ed9095
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/Configuration.java
@@ -0,0 +1,258 @@
+package com.logpresso.firewallops;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import com.logpresso.firewallops.connector.FirewallConnector;
+import com.logpresso.firewallops.connector.FirewalldConnector;
+import com.logpresso.firewallops.connector.IptablesConnector;
+
+public class Configuration {
+ private UUID apiKey;
+ private boolean debug;
+ private Backend backend;
+ private Set allowlist = new HashSet();
+
+ public static void install(UUID apiKey, InetSocketAddress proxyAddr) {
+ Backend backend = installFirewallConnector();
+ installConfigFile(backend, apiKey, proxyAddr);
+ installSystemdFile();
+ }
+
+ public static Configuration load() throws IOException {
+ Configuration c = new Configuration();
+ Pattern regex = Pattern.compile("\\s+");
+ File f = new File(IoUtils.getJarDir(), "logpresso-firewall-ops.conf");
+ BufferedReader br = null;
+
+ boolean allowlistSection = false;
+ try {
+ br = new BufferedReader(new InputStreamReader(new FileInputStream(f), "utf-8"));
+ while (true) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+
+ line = line.trim();
+
+ if (line.isEmpty() || line.startsWith("#"))
+ continue;
+
+ String[] tokens = regex.split(line);
+ String descriptor = tokens[0];
+
+ if (descriptor.equals("backend")) {
+ String value = getValue(tokens, "Specify backend value");
+ try {
+ c.backend = Backend.valueOf(value.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid backend type - " + value);
+ }
+ } else if (descriptor.equals("api-key")) {
+ String value = getValue(tokens, "Specify api-key value");
+ try {
+ c.apiKey = UUID.fromString(value);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid api-key format - " + value);
+ }
+ } else if (descriptor.equals("http-proxy")) {
+ try {
+ String s = getValue(tokens, "Specify http-proxy value (ip:port format)");
+ int p = s.indexOf(':');
+ if (p < 0)
+ throw new IllegalStateException("Missing http-proxy port - " + s);
+
+ InetAddress host = InetAddress.getByName(s.substring(0, p));
+ int port = Integer.parseInt(s.substring(p + 1));
+ if (port < 0 || port > 65535)
+ throw new IllegalStateException("Invalid http-proxy port number range - " + tokens[1]);
+
+ System.setProperty("https.proxyHost", host.getHostAddress());
+ System.setProperty("https.proxyPort", Integer.toString(port));
+
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Invalid http-proxy port number - " + tokens[1]);
+ }
+ } else if (descriptor.equals("loglevel")) {
+ c.debug = "debug".equals(getValue(tokens, "loglevel value is missing"));
+ } else if (descriptor.equals("[allowlist]")) {
+ allowlistSection = true;
+ } else {
+ if (allowlistSection) {
+ c.allowlist.add(tokens[0]);
+ }
+ }
+ }
+
+ return c;
+ } finally {
+ if (br != null)
+ br.close();
+ }
+ }
+
+ private static String getValue(String[] tokens, String error) {
+ if (tokens.length < 2)
+ throw new IllegalStateException("port number is missing");
+
+ return tokens[1];
+ }
+
+ private static Backend installFirewallConnector() {
+ // check if firewalld is running
+ if (isFirewalldRunning()) {
+ new FirewalldConnector().install();
+ return Backend.FIREWALLD;
+ } else {
+ new IptablesConnector().install();
+ return Backend.IPTABLES;
+ }
+ }
+
+ private static boolean isFirewalldRunning() {
+ try {
+ List output = PlatformUtils.execute("firewalld-cmd", "--state");
+ return "running".equals(output.get(0));
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ private static void installConfigFile(Backend backend, UUID apiKey, InetSocketAddress proxyAddr) {
+ File dir = IoUtils.getJarDir();
+ File configFile = new File(dir, "logpresso-firewall-ops.conf");
+ configFile.getParentFile().mkdirs();
+ if (configFile.exists())
+ throw new IllegalStateException("Cannot write file to " + configFile.getAbsolutePath());
+
+ BufferedWriter bw = null;
+ try {
+ bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(configFile), "utf-8"));
+ bw.write("# Logpresso Firewall Ops config file\n");
+ bw.write("backend " + backend.name().toLowerCase() + "\n");
+ bw.write("api-key " + apiKey + "\n");
+ if (proxyAddr != null)
+ bw.write("http-proxy " + proxyAddr.getAddress().getHostAddress() + ":" + proxyAddr.getPort() + "\n");
+ else
+ bw.write("# http-proxy x.x.x.x:8443\n");
+
+ bw.write("\n");
+ bw.write("# Prevent accidental IP block\n");
+ bw.write("# Network address/CIDR or IP address\n");
+ bw.write("[allowlist]\n");
+ bw.write("10.0.0.0/8\n");
+ bw.write("172.16.0.0/12\n");
+ bw.write("192.168.0.0/16\n");
+ } catch (IOException e) {
+ throw new IllegalStateException("cannot write config file to " + configFile.getAbsolutePath(), e);
+ } finally {
+ if (bw != null) {
+ try {
+ bw.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ System.out.println("Wrote " + configFile.length() + " bytes to " + configFile.getAbsolutePath());
+ }
+
+ private static void installSystemdFile() {
+ File dir = IoUtils.getJarDir();
+ File serviceFile = new File("/lib/systemd/system/logpresso-firewall-ops.service");
+ BufferedWriter bw = null;
+ try {
+ bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(serviceFile), "utf-8"));
+ bw.write("[Unit]\n");
+ bw.write("Description=Logpresso Firewall Ops\n");
+ bw.write("After=multi-user.target network.target\n");
+ bw.write("ConditionPathExists=" + dir.getAbsolutePath() + "/logpresso-firewall-ops.conf\n\n");
+ bw.write("[Service]\n");
+ bw.write("Type=simple\n");
+ bw.write("ExecStart=" + dir.getAbsolutePath() + "/logpresso-firewall-ops start\n");
+ bw.write("Restart=on-failure\n");
+ bw.write("[Install]\n");
+ bw.write("WantedBy=multi-user.target\n");
+ } catch (IOException e) {
+ throw new IllegalStateException("cannot write systemd file to " + serviceFile.getAbsolutePath(), e);
+ } finally {
+ if (bw != null) {
+ try {
+ bw.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ System.out.println("Wrote " + serviceFile.length() + " bytes to " + serviceFile.getAbsolutePath());
+
+ try {
+ PlatformUtils.execute("systemctl", "daemon-reload");
+ } catch (IOException e) {
+ }
+ }
+
+ public static void uninstall() {
+ if (isFirewalldRunning()) {
+ new FirewalldConnector().uninstall();
+ } else {
+ new IptablesConnector().uninstall();
+ }
+
+ File serviceFile = new File("/lib/systemd/system/logpresso-firewall-ops.service");
+ if (!serviceFile.exists()) {
+ System.out.println("Error: service file not found");
+ return;
+ }
+
+ if (serviceFile.delete()) {
+ System.out.println("uninstalled systemd service");
+ } else {
+ System.out.println("Cannot delete service file " + serviceFile.getAbsolutePath());
+ }
+
+ // delete config file
+ File dir = IoUtils.getJarDir();
+ File configFile = new File(dir, "logpresso-firewall-ops.conf");
+ configFile.delete();
+
+ try {
+ PlatformUtils.execute("systemctl", "daemon-reload");
+ } catch (IOException e) {
+ }
+ }
+
+ public FirewallConnector getConnector() {
+ if (backend == Backend.FIREWALLD)
+ return new FirewalldConnector();
+ else if (backend == Backend.IPTABLES)
+ return new IptablesConnector();
+ else
+ throw new UnsupportedOperationException();
+ }
+
+ public UUID getApiKey() {
+ return apiKey;
+ }
+
+ public boolean isDebug() {
+ return debug;
+ }
+
+ public Set getAllowlist() {
+ return allowlist;
+ }
+}
diff --git a/src/main/java/com/logpresso/firewallops/FirewallOpsAgent.java b/src/main/java/com/logpresso/firewallops/FirewallOpsAgent.java
new file mode 100644
index 0000000..3ceeaa8
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/FirewallOpsAgent.java
@@ -0,0 +1,185 @@
+package com.logpresso.firewallops;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class FirewallOpsAgent {
+ private Configuration conf;
+
+ public static void main(String[] args) {
+ System.out.println("Logpresso Firewall Ops 1.0.0 (2022-02-03)");
+ java.security.Security.setProperty("networkaddress.cache.ttl", "30");
+
+ if (args.length == 0) {
+ printUsage();
+ return;
+ }
+
+ String mode = args[0];
+
+ try {
+ if ("start".equals(mode)) {
+ Configuration c = Configuration.load();
+ new FirewallOpsAgent().run(c);
+ } else if ("install".equals(mode)) {
+ if (args.length < 2) {
+ printUsage();
+ return;
+ }
+
+ UUID apiKey = UUID.fromString(args[1]);
+ InetSocketAddress proxyAddr = null;
+ if (args.length > 2) {
+ String proxy = args[2];
+ int p = proxy.indexOf(':');
+ if (p < 0) {
+ System.out.println("Error: missing proxy port");
+ return;
+ }
+
+ InetAddress host = InetAddress.getByName(proxy.substring(0, p));
+ int port = Integer.parseInt(proxy.substring(p + 1));
+ proxyAddr = new InetSocketAddress(host, port);
+ }
+
+ Configuration.install(apiKey, proxyAddr);
+ } else if ("uninstall".equals(mode)) {
+ Configuration.uninstall();
+ }
+ } catch (Throwable t) {
+ System.out.println("Error: " + t.getMessage());
+ }
+ }
+
+ private static void printUsage() {
+ System.out.println("Usage: logpresso-firewall-ops [start|install|uninstall]");
+ System.out.println(" start");
+ System.out.println(" install [api-key] [http-proxy ip:port]");
+ System.out.println(" uninstall");
+ }
+
+ public void run(Configuration conf) {
+ this.conf = conf;
+
+ int interval = 60000;
+ String lastTag = "";
+
+ int i = 0;
+ while (true) {
+ try {
+ if (i++ != 0)
+ Thread.sleep(interval);
+
+ lastTag = downloadBlocklist(interval, lastTag);
+
+ } catch (InterruptedException e) {
+ // ignore
+ } catch (Throwable t) {
+ System.out.println("Error: " + t.getMessage());
+ }
+ }
+ }
+
+ private String downloadBlocklist(int interval, String lastTag) {
+ System.out.println("Checking Logpresso Watch blocklist..");
+ HttpURLConnection conn = null;
+ try {
+ UUID hostGuid = ensureHostGuid();
+ String target = "https://watch.logpresso.com";
+ conn = (HttpURLConnection) new URL(target + "/blocklist/policy?host_guid=" + hostGuid + "&tag=" + lastTag)
+ .openConnection();
+ conn.setConnectTimeout(30000);
+ conn.setReadTimeout(30000);
+ conn.setRequestProperty("Authorization", "Bearer " + conf.getApiKey());
+
+ int status = conn.getResponseCode();
+ if (status == 200) {
+ lastTag = updateBlocklist(conn.getInputStream());
+ } else if (status == 304) {
+ System.out.println("Not modified");
+ } else if (status == 503) {
+ System.out.println("Error: service unavailable");
+ } else if (status == 401) {
+ System.out.println("Error: unauthorized api key");
+ } else if (status == 404) {
+ System.out.println("Error: resource not found");
+ } else {
+ System.out.println("Error: http error status " + status);
+ }
+
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ if (conn != null)
+ conn.disconnect();
+ }
+
+ return lastTag;
+ }
+
+ private String updateBlocklist(InputStream is) throws IOException {
+ String tag = null;
+ List blocklist = new ArrayList();
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new InputStreamReader(is, "utf-8"));
+ while (true) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+
+ line = line.trim();
+ if (line.isEmpty())
+ continue;
+
+ if (line.startsWith("# tag ")) {
+ tag = line.substring("# tag ".length()).trim();
+ } else if (line.startsWith("#")) {
+ continue;
+ } else {
+ blocklist.add(InetAddress.getByName(line));
+ }
+ }
+
+ } finally {
+ IoUtils.ensureClose(br);
+ }
+
+ System.out.println("Downloaded " + blocklist.size() + " items. new tag is " + tag);
+ conf.getConnector().deployBlocklist(blocklist);
+
+ return tag;
+
+ }
+
+ private static UUID ensureHostGuid() {
+ File dir = IoUtils.getJarDir();
+ File guidFile = new File(dir, "logpresso-firewall-ops.guid");
+ if (guidFile.exists()) {
+ try {
+ return UUID.fromString(IoUtils.readLine(guidFile));
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot read logpresso-firewall-ops.guid file", e);
+ }
+ } else {
+ try {
+ UUID newGuid = UUID.randomUUID();
+ IoUtils.writeLine(guidFile, newGuid.toString());
+ return newGuid;
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot write logpresso-firewall-ops.guid file", e);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/logpresso/firewallops/IoUtils.java b/src/main/java/com/logpresso/firewallops/IoUtils.java
new file mode 100644
index 0000000..cba6eaf
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/IoUtils.java
@@ -0,0 +1,84 @@
+package com.logpresso.firewallops;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.List;
+
+public class IoUtils {
+ public static File getJarDir() {
+ try {
+ File jarPath = new File(
+ URLDecoder.decode(IoUtils.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "utf-8"));
+ return jarPath.getParentFile();
+ } catch (UnsupportedEncodingException e) {
+ // unreachable
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static List loadLines(File f) throws IOException {
+ List lines = new ArrayList();
+ FileInputStream fis = null;
+ BufferedReader br = null;
+ try {
+ br = new BufferedReader(new InputStreamReader(new FileInputStream(f), "utf-8"));
+
+ while (true) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+
+ line = line.trim();
+
+ if (line.startsWith("#") || line.isEmpty())
+ continue;
+
+ lines.add(line);
+ }
+
+ return lines;
+ } finally {
+ IoUtils.ensureClose(fis);
+ IoUtils.ensureClose(br);
+ }
+ }
+
+ public static String readLine(File f) throws IOException {
+ BufferedReader br = null;
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(f);
+ br = new BufferedReader(new InputStreamReader(fis, "utf-8"));
+ return br.readLine();
+ } finally {
+ IoUtils.ensureClose(br);
+ }
+ }
+
+ public static void writeLine(File f, String line) throws IOException {
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream(f);
+ fos.write(line.getBytes("utf-8"));
+ } finally {
+ IoUtils.ensureClose(fos);
+ }
+ }
+
+ public static void ensureClose(Closeable c) {
+ if (c != null) {
+ try {
+ c.close();
+ } catch (Throwable t) {
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/logpresso/firewallops/NetworkAddress.java b/src/main/java/com/logpresso/firewallops/NetworkAddress.java
new file mode 100644
index 0000000..a844a8c
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/NetworkAddress.java
@@ -0,0 +1,66 @@
+package com.logpresso.firewallops;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+public class NetworkAddress {
+ private InetAddress addr;
+ private int cidr;
+ private long start;
+ private long end;
+
+ public NetworkAddress(InetAddress addr, int cidr) {
+ long mask = getMask(cidr);
+
+ this.start = toLong(addr) & mask;
+ this.end = (start | ~mask) & 0xffffffffL;
+ this.addr = toIp(start);
+ this.cidr = cidr;
+ }
+
+ public InetAddress getStartIp() {
+ return toIp(start);
+ }
+
+ public InetAddress getEndIp() {
+ return toIp(end);
+ }
+
+ private static InetAddress toIp(long ip) {
+ byte b1 = (byte) ((ip >> 24) & 0xff);
+ byte b2 = (byte) ((ip >> 16) & 0xff);
+ byte b3 = (byte) ((ip >> 8) & 0xff);
+ byte b4 = (byte) (ip & 0xff);
+
+ byte[] b = new byte[] { b1, b2, b3, b4 };
+ try {
+ return InetAddress.getByAddress(b);
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException("unreachable");
+ }
+ }
+
+ private static long getMask(int cidr) {
+ long mask = 0;
+ for (int i = 0; i < cidr; i++)
+ mask |= 1 << (31 - i);
+
+ return mask;
+ }
+
+ public boolean contains(InetAddress ip) {
+ long v = toLong(ip);
+ return start <= v && v <= end;
+ }
+
+ private long toLong(InetAddress ip) {
+ byte[] b = ip.getAddress();
+ return (((b[0] & 0xff) << 24) | ((b[1] & 0xff) << 16) | ((b[2] & 0xff) << 8) | (b[3] & 0xff)) & 0xffffffffL;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s/%d (%d~%d)", addr.getHostAddress(), cidr, start, end);
+ }
+
+}
diff --git a/src/main/java/com/logpresso/firewallops/PlatformUtils.java b/src/main/java/com/logpresso/firewallops/PlatformUtils.java
new file mode 100644
index 0000000..6f7a8ea
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/PlatformUtils.java
@@ -0,0 +1,136 @@
+package com.logpresso.firewallops;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class PlatformUtils {
+ public final static boolean isWindows;
+ public final static boolean isUnixLike;
+ public final static boolean isLinux;
+ public final static boolean isAIX;
+ public final static boolean isHPUX;
+ public final static boolean isSolaris;
+ public final static boolean isMacOS;
+ static {
+ String osname = System.getProperty("os.name").toLowerCase();
+ isWindows = osname.startsWith("win");
+ isLinux = osname.startsWith("linux");
+ isAIX = osname.contains("aix");
+ isHPUX = osname.contains("hpux") || osname.contains("hp-ux");
+ isSolaris = osname.contains("solaris") || osname.contains("sunos");
+ isMacOS = osname.contains("mac");
+ isUnixLike = isLinux || isAIX || isHPUX || isSolaris || isMacOS;
+ if (!isWindows && !isUnixLike)
+ throw new UnsupportedOperationException();
+ }
+
+ public static String getHostname(boolean debug) {
+ // Try to fetch hostname without DNS resolving for closed network
+ boolean isWindows = File.separatorChar == '\\';
+ if (isWindows) {
+ return System.getenv("COMPUTERNAME");
+ } else {
+ Process p = null;
+ try {
+ p = Runtime.getRuntime().exec("uname -n");
+ BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+ String line = br.readLine();
+ return (line == null) ? null : line.trim();
+ } catch (IOException e) {
+ if (debug)
+ e.printStackTrace();
+
+ return null;
+ } finally {
+ if (p != null)
+ p.destroy();
+ }
+ }
+ }
+
+ public static String getHomeDir() {
+ if (isUnixLike)
+ return PlatformUtils.getenv("HOME");
+ else
+ return PlatformUtils.getenv("USERPROFILE");
+ }
+
+ public static String getenv(String var) {
+ String val = System.getenv(var);
+ if (val == null || val.trim().isEmpty())
+ return null;
+ else
+ return val;
+ }
+
+ public static String resolvePath(String command) {
+ if (new File(command).isAbsolute())
+ return command;
+
+ String pathEnv = PlatformUtils.getenv("PATH");
+ if (pathEnv == null || pathEnv.trim().isEmpty())
+ pathEnv = "";
+
+ String[] paths = pathEnv.split(Pattern.quote(System.getProperty("path.separator")));
+ for (String path : paths) {
+ File candidate = new File(path, command);
+ if (candidate.exists() && candidate.canExecute())
+ return candidate.toString();
+ }
+
+ return command;
+ }
+
+ public static List execute(String... commands) throws IOException {
+ List output = new ArrayList();
+ Process p = null;
+ BufferedReader br = null;
+ try {
+ commands[0] = resolvePath(commands[0]);
+ ProcessBuilder pb = new ProcessBuilder(commands);
+ pb.redirectErrorStream(true);
+ p = pb.start();
+ br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+ while (true) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+
+ output.add(line);
+ }
+
+ return output;
+ } finally {
+ if (br != null) {
+ try {
+ br.close();
+ } catch (Throwable t) {
+ }
+ }
+
+ if (p != null) {
+ try {
+ p.waitFor();
+ } catch (Throwable t) {
+ }
+ }
+ }
+ }
+
+ public static boolean isRoot() throws IOException {
+ if (!isLinux)
+ return false;
+
+ List output = execute("/usr/bin/id", "-u");
+ if (output.isEmpty())
+ return false;
+
+ return output.get(0).equals("0");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/logpresso/firewallops/connector/FirewallConnector.java b/src/main/java/com/logpresso/firewallops/connector/FirewallConnector.java
new file mode 100644
index 0000000..b985504
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/connector/FirewallConnector.java
@@ -0,0 +1,14 @@
+package com.logpresso.firewallops.connector;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.List;
+
+public interface FirewallConnector {
+
+ void install() throws IOException;
+
+ void uninstall() throws IOException;
+
+ void deployBlocklist(List addresses) throws IOException;
+}
diff --git a/src/main/java/com/logpresso/firewallops/connector/FirewalldConnector.java b/src/main/java/com/logpresso/firewallops/connector/FirewalldConnector.java
new file mode 100644
index 0000000..481b872
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/connector/FirewalldConnector.java
@@ -0,0 +1,91 @@
+package com.logpresso.firewallops.connector;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.util.List;
+
+import com.logpresso.firewallops.IoUtils;
+import com.logpresso.firewallops.PlatformUtils;
+
+public class FirewalldConnector implements FirewallConnector {
+
+ private static final File configFile = new File("/etc/firewalld/ipsets/logpresso-watch.xml");
+ private static final File backupFile = new File("/etc/firewalld/ipsets/logpresso-watch.xml.old");
+
+ public void install() {
+ try {
+ // create ipset
+ if (!configFile.exists()) {
+ List output = PlatformUtils.execute("firewall-cmd", "--permanent", "--new-ipset=logpresso-watch",
+ "--type=hash:ip");
+
+ if (!"success".equals(output.get(output.size() - 1)))
+ throw new IllegalStateException("Cannot create ipset - " + output.get(0));
+ }
+
+ PlatformUtils.execute("firewall-cmd", "--permanent", "--zone=drop", "--add-source=ipset:logpresso-watch");
+
+ reloadFirewalld();
+
+ System.out.println("Installed logpresso-watch ipset on firewalld.");
+ } catch (IOException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ }
+
+ public void uninstall() {
+ if (!configFile.exists())
+ throw new IllegalStateException("logpresso-firewall-ops.xml not found");
+
+ try {
+ // remove drop rule
+ List output = PlatformUtils.execute("firewall-cmd", "--permanent", "--zone=drop",
+ "--remove-source=ipset:logpresso-watch");
+ if (!"success".equals(output.get(output.size() - 1)))
+ throw new IllegalStateException("Cannot delete drop rule - " + output.get(0));
+
+ // remove ipset
+ output = PlatformUtils.execute("firewall-cmd", "--permanent", "--delete-ipset=logpresso-watch");
+ if (!"success".equals(output.get(output.size() - 1)))
+ throw new IllegalStateException("Cannot delete ipset - " + output.get(0));
+
+ reloadFirewalld();
+
+ System.out.println("Uninstalled logpresso-watch ipset from firewalld.");
+ } catch (IOException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ }
+
+ private void reloadFirewalld() throws IOException {
+ List output = PlatformUtils.execute("firewall-cmd", "--reload");
+ if (!"success".equals(output.get(output.size() - 1)))
+ throw new IllegalStateException("Cannot reload firewalld - " + output.get(0));
+ }
+
+ public void deployBlocklist(List addresses) {
+ backupFile.delete();
+ configFile.renameTo(backupFile);
+
+ BufferedWriter bw = null;
+ try {
+ bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(configFile), "utf-8"));
+ bw.write("\n");
+ bw.write("");
+ for (InetAddress addr : addresses) {
+ bw.write(" " + addr.getHostAddress() + "");
+ }
+ bw.write("");
+
+ reloadFirewalld();
+ } catch (IOException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ IoUtils.ensureClose(bw);
+ }
+ }
+}
diff --git a/src/main/java/com/logpresso/firewallops/connector/IptablesConnector.java b/src/main/java/com/logpresso/firewallops/connector/IptablesConnector.java
new file mode 100644
index 0000000..5aa0cb8
--- /dev/null
+++ b/src/main/java/com/logpresso/firewallops/connector/IptablesConnector.java
@@ -0,0 +1,89 @@
+package com.logpresso.firewallops.connector;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.logpresso.firewallops.IoUtils;
+import com.logpresso.firewallops.PlatformUtils;
+
+public class IptablesConnector implements FirewallConnector {
+
+ @Override
+ public void install() {
+ try {
+ PlatformUtils.execute("ipset", "-N", "logpresso-watch", "iphash");
+ PlatformUtils.execute("iptables", "-I", "INPUT", "1", "-m", "set", "--match-set", "logpresso-watch", "src", "-j",
+ "DROP");
+ System.out.println("Installed logpresso-watch ipset and drop rule to iptables.");
+ } catch (IOException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void uninstall() {
+ try {
+ PlatformUtils.execute("iptables", "-D", "INPUT", "1", "-m", "set", "--match-set", "logpresso-watch", "src");
+ PlatformUtils.execute("ipset", "destroy", "logpresso-watch");
+
+ System.out.println("Uninstalled iptables drop rule and logpresso-watch ipset.");
+ } catch (IOException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public void deployBlocklist(List addresses) {
+
+ List output = new ArrayList();
+ Process p = null;
+ BufferedReader br = null;
+ BufferedWriter bw = null;
+ try {
+ PlatformUtils.execute("ipset", "flush", "logpresso-watch");
+
+ // write to stdin of ipset restore command
+ String[] commands = new String[] { "ipset", "restore", "-!" };
+
+ commands[0] = PlatformUtils.resolvePath(commands[0]);
+ ProcessBuilder pb = new ProcessBuilder(commands);
+ pb.redirectErrorStream(true);
+ p = pb.start();
+
+ bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
+ bw.write("create logpresso-watch hash:ip family inet hashsize 1024 maxelem 65536\n");
+ for (InetAddress addr : addresses)
+ bw.write("add logpresso-watch " + addr.getHostAddress() + "\n");
+
+ bw.close();
+
+ br = new BufferedReader(new InputStreamReader(p.getInputStream()));
+ while (true) {
+ String line = br.readLine();
+ if (line == null)
+ break;
+
+ output.add(line);
+ }
+
+ } catch (IOException e) {
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ IoUtils.ensureClose(bw);
+ IoUtils.ensureClose(br);
+
+ if (p != null) {
+ try {
+ p.waitFor();
+ } catch (Throwable t) {
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/sh/linux_header.sh b/src/main/sh/linux_header.sh
new file mode 100644
index 0000000..f46340d
--- /dev/null
+++ b/src/main/sh/linux_header.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# chkconfig: 2345 99 20
+#
+# ----------------------------------------------------------------------------
+# Copyright 2001-2006 The Apache Software Foundation.
+#
+# 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.
+# ----------------------------------------------------------------------------
+#
+# Copyright (c) 2001-2006 The Apache Software Foundation. All rights
+# reserved.
+
+# resolve links - $0 may be a softlink
+PRG="$0"
+
+while [ -h "$PRG" ]; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`/"$link"
+ fi
+done
+
+PRGDIR=`dirname "$PRG"`
+
+# If a specific java binary isn't specified search for the standard 'java' binary
+if [ -z "$JAVA" ] ; then
+ # first, try built-in JRE
+ if [ -x "$PRGDIR/jre/bin/java" ] ; then
+ JAVA="$PRGDIR/jre/bin/java"
+ elif [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVA="$JAVA_HOME/jre/sh/java"
+ else
+ JAVA="$JAVA_HOME/bin/java"
+ fi
+ else
+ # or use PATH
+ JAVA=`command -v java`
+ fi
+fi
+
+if [ ! -x "$JAVA" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." 1>&2
+ echo " We cannot execute $JAVA" 1>&2
+ exit 1
+fi
+
+exec $JAVA -jar $0 "$@"
+
+#### jar will be attached after following blank lines
+#### following blank lines are intentional.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/com/logpresso/firewallops/NetworkAddressTest.java b/src/test/java/com/logpresso/firewallops/NetworkAddressTest.java
new file mode 100644
index 0000000..acc121a
--- /dev/null
+++ b/src/test/java/com/logpresso/firewallops/NetworkAddressTest.java
@@ -0,0 +1,36 @@
+package com.logpresso.firewallops;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.junit.Test;
+
+public class NetworkAddressTest {
+ @Test
+ public void testCidr() {
+
+ NetworkAddress private8 = new NetworkAddress(ip("10.0.0.0"), 8);
+ NetworkAddress private12 = new NetworkAddress(ip("172.16.0.0"), 12);
+ NetworkAddress private16 = new NetworkAddress(ip("192.168.0.0"), 16);
+
+ assertEquals(ip("10.0.0.0"), private8.getStartIp());
+ assertEquals(ip("10.255.255.255"), private8.getEndIp());
+
+ assertEquals(ip("172.16.0.0"), private12.getStartIp());
+ assertEquals(ip("172.31.255.255"), private12.getEndIp());
+
+ assertEquals(ip("192.168.0.0"), private16.getStartIp());
+ assertEquals(ip("192.168.255.255"), private16.getEndIp());
+
+ }
+
+ private InetAddress ip(String s) {
+ try {
+ return InetAddress.getByName(s);
+ } catch (UnknownHostException e) {
+ throw new IllegalArgumentException(s);
+ }
+ }
+}