aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/launch.json24
-rw-r--r--LICENSE202
-rw-r--r--README.md26
-rw-r--r--aws-ipblocks-csv/.gitignore1
-rw-r--r--aws-ipblocks-csv/INSTALL.md9
-rw-r--r--aws-ipblocks-csv/README.md23
-rw-r--r--aws-ipblocks-csv/index.html115
-rw-r--r--aws-ipblocks-csv/index.js101
-rw-r--r--aws-ipblocks-csv/package-lock.json21
-rw-r--r--aws-ipblocks-csv/package.json14
-rw-r--r--aws-ipblocks-csv/worker.js143
-rw-r--r--toss-aws-eip/README.md85
-rwxr-xr-xtoss-aws-eip/multitoss.sh103
-rwxr-xr-xtoss-aws-eip/toss-aws-eip.py378
-rw-r--r--writeups/headless-vnc.ko/headless-vnc.ko-kr.md88
-rw-r--r--writeups/powershell-email/README.md43
-rw-r--r--writeups/powershell-email/sendmail.ps1181
-rw-r--r--writeups/selfhosting-email/fuckyou-gmail.en.md100
18 files changed, 1657 insertions, 0 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..58ab099
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,24 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "toss-aws-eip",
+ "type": "python",
+ "request": "launch",
+ "cwd": "${workspaceFolder}/toss-aws-eip",
+ "program": "${workspaceFolder}/toss-aws-eip/toss-aws-eip.py",
+ "args": [
+ // "-r", "ap-southeast-2",
+ "-l", "tosser",
+ "-vvvv", // be very noisy
+ // "-d", // do a dryrun
+ "0.0.0.0/32" // accept no addreess
+ ],
+ "console": "integratedTerminal",
+ "justMyCode": false
+ }
+ ]
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://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..44a439f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+# David's Gist Repository
+*Stay awhile and listen!*
+
+Here you can find all the work I've publish on Github Gist. As Gist works as a
+blog service for devs and doesn't come with version control, I thought I'd put
+all the work in a git repo. Some tools I wrote for gist are actually VC-worthy
+projects that I really wouldn't bother making a git repo for each.
+
+I hope you enjoy your stay!
+
+## Index
+- aws-ipblocks-csv: download AWS Public IP address ranges in CSV format. No
+ middle man involved - everything done on your browser
+- toss-aws-eip: get an Elastic IP address until you get one in the range you
+ want
+- writeups: all the write ups written up
+ - headless-vnc.ko: setting up a virtual X11 seat to use GUI on cloud instances
+ (in Korean)
+ - powershell-email: send emails in Powershell
+ - selfhosting-email: everything I want to talk about on self-hosting your own
+ email
+
+## Copyright
+Unless classified otherwise, all the work I do I go by Apache-2.0. Mate, if I
+make something serious, I wouldn't be posting it anywhere so feel free to do
+whatever you want with whatever I post on Gist!
diff --git a/aws-ipblocks-csv/.gitignore b/aws-ipblocks-csv/.gitignore
new file mode 100644
index 0000000..b512c09
--- /dev/null
+++ b/aws-ipblocks-csv/.gitignore
@@ -0,0 +1 @@
+node_modules \ No newline at end of file
diff --git a/aws-ipblocks-csv/INSTALL.md b/aws-ipblocks-csv/INSTALL.md
new file mode 100644
index 0000000..5979a27
--- /dev/null
+++ b/aws-ipblocks-csv/INSTALL.md
@@ -0,0 +1,9 @@
+# Build Guide
+Run `npm install`. Distribute the files below.
+
+```
+index.html \
+index.js \
+worker.js \
+node_modules/csv-stringify/dist/esm/index.js
+```
diff --git a/aws-ipblocks-csv/README.md b/aws-ipblocks-csv/README.md
new file mode 100644
index 0000000..6b8cf8d
--- /dev/null
+++ b/aws-ipblocks-csv/README.md
@@ -0,0 +1,23 @@
+# AWS Public IP Address Ranges in CSV Format
+This is a neat little browser tool that downloads [the JSON
+file](https://ip-ranges.amazonaws.com/ip-ranges.json) and convert it to a CSV
+for better analysis with spreadsheet software. If you're annoyed because they
+only provide it in JSON and don't want to code to make sense of the data, you've
+come to the right place!
+
+The JSON data is probably for anyone who is affected by the Amazon's IP address
+changes, namely network admins who have to configure their firewalls for AWS
+traffic. Technically speaking, the data is not meant to be consumed by humans,
+but I personally had to consume it for [my hobby self-hosting
+project](https://gist.github.com/ashegoulding/72a8732d4a1679c343f84fc985ca8de8).
+I was particularly interested in EIP address blocks. I figured they're something
+AWS cannot easily mess with because that involves "evicting" all the EIP holders
+before releasing or repurposing the block.
+
+This tool is hosted on [my github.io
+site](https://ashegoulding.github.io/aws-ipblocks-csv). Bon appetit!
+
+## Links
+https://docs.aws.amazon.com/vpc/latest/userguide/aws-ip-ranges.html
+https://aws.amazon.com/blogs/aws/aws-ip-ranges-json/
+https://aws.amazon.com/blogs/developer/querying-the-public-ip-address-ranges-for-aws/
diff --git a/aws-ipblocks-csv/index.html b/aws-ipblocks-csv/index.html
new file mode 100644
index 0000000..ee81518
--- /dev/null
+++ b/aws-ipblocks-csv/index.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html lang="en">
+<!--
+ Copyright (c) 2022 David Timber <dxdt@dev.snart.me>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>AWS Public IP Address Ranges in CSV</title>
+
+ <script src="index.js"></script>
+
+ <style>
+ .working-animated {
+ animation-name: blink;
+ animation-duration: 0.5s;
+ animation-iteration-count: infinite;
+ animation-play-state: running;
+ animation-timing-function: steps(2, start);
+ }
+
+ .hidden {
+ visibility: hidden;
+ }
+
+ .error {
+ color: red;
+ font-weight: bold;
+ }
+
+ @keyframes blink {
+ to {
+ visibility: hidden;
+ }
+ }
+ .foot {
+ text-align: right;
+ }
+ </style>
+</head>
+<body onload="do_load()">
+ <h1>AWS Public IP Address Ranges in CSV</h1>
+ <p>
+ This tool pulls <a
+ href="https://ip-ranges.amazonaws.com/ip-ranges.json">the JSON data</a>
+ from the AWS and convert it to CSV, along with other calculated data
+ such as the size of each address block. The file can be imported to a
+ spreadsheet software of your choice to extract the desired data using
+ filters.
+ </p>
+
+ <section>
+ <h2>Tool Options</h2>
+ <form name="form" method="dialog" onsubmit="do_submit()">
+ <p>
+ <input type="checkbox" name="ipv4" checked>
+ <label for="ipv4">Pull IPv4 blocks</label>
+ </p>
+ <p>
+ <input type="checkbox" name="ipv6">
+ <label for="ipv6">Pull IPv6 blocks</label>
+ </p>
+ <p>
+ <button type="submit" name="submit">Go!</button>
+ <label for="submit">&lt;- Requires a fair bit of memory!</label>
+ </p>
+ </form>
+ <p>
+ <span id="working-indicator" class="hidden"></span>
+ <a id="save-link" target="_blank" href="" class="hidden">Save CSV file</a>
+ </p>
+ </section>
+
+ <h2>Format</h2>
+<pre>
+IPV REGION NETGRP SERVICE NET CIDR SIZE
+4 af-south-1 af-south-1 AMAZON 3.2.34.0 26 64
+4 ap-northeast-2 ap-northeast-2 AMAZON 3.5.140.0 22 1024
+4 ap-southeast-4 ap-southeast-4 AMAZON 13.34.37.64 27 32
+4 il-central-1 il-central-1 AMAZON 13.34.65.64 27 32
+4 us-east-1 us-east-1 AMAZON 13.34.66.0 27 32
+4 ca-central-1 ca-central-1 AMAZON 13.34.78.160 27 32
+4 us-west-2 us-west-2 AMAZON 13.34.103.96 27 32
+</pre>
+ <h3>Where ...</h3>
+ <ul>
+ <li><b>IPV</b> is either 4 or 6</li>
+ <li><b>SIZE</b> is the number of addresses in the block</li>
+ </ul>
+ <p>For IPv6 addresses, the CIDR length can be enormous. The tool handles
+ them using <code>BigInt</code>, but your spreadsheet software can struggle
+ to handle it. It will most likely show the numbers in scientific
+ representation.</p>
+ <p class="foot">
+ <small>by David Timber &lt;dxdt@dev.snart.me&gt; (c) 2023</small>
+ </p>
+</body>
+</html>
diff --git a/aws-ipblocks-csv/index.js b/aws-ipblocks-csv/index.js
new file mode 100644
index 0000000..48f150a
--- /dev/null
+++ b/aws-ipblocks-csv/index.js
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2019-2022 David Timber <dxdt@dev.snart.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+var worker;
+var ui = {
+ f: {}
+};
+var url;
+
+function onerror (e) {
+ console.log(e);
+
+ ui.working.innerHTML = e.toString();
+ ui.working.className = "error";
+}
+
+function onmessage (evt) {
+ let blob, url, dlname = [];
+
+ ui.f.submit.disabled = false;
+
+ if (evt.data.error) {
+ onerror(evt.data.error);
+ return;
+ }
+
+ dlname.push("aws-ip_");
+ dlname.push(evt.data.meta["createDate"]);
+ dlname.push("_");
+ dlname.push(evt.data.meta["syncToken"]);
+ dlname.push(".csv");
+
+ if (url) {
+ URL.revokeObjectURL(url);
+ }
+
+ blob = new Blob([ evt.data.payload ], { type: "text/csv" });
+ url = URL.createObjectURL(blob);
+
+ ui.working.className = "";
+ ui.working.innerHTML = "Done!";
+
+ ui.savelink.className = "";
+ ui.savelink.href = url;
+ ui.savelink.download = dlname.join("");
+}
+
+function do_load () {
+ ui.working = document.getElementById("working-indicator");
+ ui.f.ipv4 = document.form.ipv4;
+ ui.f.ipv6 = document.form.ipv6;
+ ui.f.submit = document.form.submit;
+ ui.savelink = document.getElementById("save-link");
+
+ worker = new Worker("worker.js", { type: "module" });
+ worker.onmessage = onmessage;
+}
+
+function do_submit () {
+ try {
+ if (!(ui.f.ipv4.checked || ui.f.ipv6.checked)) {
+ throw "Not pulling anything? (both v4 and v6 unchecked)";
+ }
+
+ worker.postMessage({
+ task_id: "null",
+ opt: {
+ "ipv4": ui.f.ipv4.checked,
+ "ipv6": ui.f.ipv6.checked
+ }
+ });
+
+ ui.f.submit.disabled = true;
+ ui.working.className = "working-animated";
+ ui.working.innerHTML = "Working ...";
+ }
+ catch (e) {
+ onerror(e);
+ }
+
+ return false;
+}
diff --git a/aws-ipblocks-csv/package-lock.json b/aws-ipblocks-csv/package-lock.json
new file mode 100644
index 0000000..398c0d5
--- /dev/null
+++ b/aws-ipblocks-csv/package-lock.json
@@ -0,0 +1,21 @@
+{
+ "name": "aws-ipblocks-csv",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "aws-ipblocks-csv",
+ "version": "0.0.0",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "csv-stringify": "^6.4.4"
+ }
+ },
+ "node_modules/csv-stringify": {
+ "version": "6.4.4",
+ "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.4.tgz",
+ "integrity": "sha512-NDshLupGa7gp4UG4sSNIqwYJqgSwvds0SvENntxoVoVvTzXcrHvd5gG2MWpbRpSNvk59dlmIe1IwNvSxN4IVmg=="
+ }
+ }
+}
diff --git a/aws-ipblocks-csv/package.json b/aws-ipblocks-csv/package.json
new file mode 100644
index 0000000..87e11b9
--- /dev/null
+++ b/aws-ipblocks-csv/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "aws-ipblocks-csv",
+ "version": "0.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "David Timber <david@snart.me>",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "csv-stringify": "^6.4.4"
+ }
+}
diff --git a/aws-ipblocks-csv/worker.js b/aws-ipblocks-csv/worker.js
new file mode 100644
index 0000000..9d034d3
--- /dev/null
+++ b/aws-ipblocks-csv/worker.js
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2019-2022 David Timber <dxdt@dev.snart.me>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { stringify } from "./node_modules/csv-stringify/dist/esm/index.js";
+
+function onerror (e, ctx) {
+ postMessage({
+ "task_id": ctx.task_id,
+ "error": e
+ });
+}
+
+function mkstringifier (ctx) {
+ const ret = stringify();
+ const csvdata = [];
+
+ ret.on('readable', function () {
+ let row;
+
+ while ((row = ret.read()) !== null) {
+ csvdata.push(row);
+ }
+ });
+
+ ret.on('finish', function () {
+ postMessage({
+ "task_id": ctx.task_id,
+ "meta": ctx.meta,
+ "payload": csvdata.join(""),
+ });
+ });
+
+ return ret;
+}
+
+function procMeta (ctx, data) {
+ ctx.meta = {
+ "syncToken": data["syncToken"],
+ "createDate": data["createDate"]
+ }
+}
+
+const HEADER = [
+ "IPV",
+ "REGION",
+ "NETGRP",
+ "SERVICE",
+ "NET",
+ "CIDR",
+ "SIZE"
+]
+
+function procPrefixes (ctx, data, opt, ipv, prefix_key, cidr_len_f) {
+ let i, o, p, sep, net, cidr;
+
+ for (i in data) {
+ o = data[i];
+ p = o[prefix_key];
+ sep = p.search("/");
+ net = p.substring(0, sep);
+ cidr = parseInt(p.substring(sep + 1));
+
+ ctx.csv.write([
+ ipv,
+ o["region"],
+ o["network_border_group"],
+ o["service"],
+ net,
+ cidr,
+ cidr_len_f(cidr)
+ ]);
+ }
+}
+
+function calcCidrLen (whole, cidr) {
+ return BigInt(1) << BigInt(whole - cidr);
+}
+
+
+self.onmessage = async function (evt) {
+ const ctx = {
+ task_id: evt.data.task_id
+ };
+ const opt = evt.data.opt ? evt.data.opt : {
+ "ipv4": true,
+ "ipv6": true
+ };
+
+ try {
+ ctx.csv = mkstringifier(ctx);
+
+ const r = await fetch('https://ip-ranges.amazonaws.com/ip-ranges.json');
+ const json = await r.json()
+
+ procMeta(ctx, json);
+
+ ctx.csv.write(HEADER); // emit header
+
+ if (opt["ipv4"]) {
+ procPrefixes(
+ ctx,
+ json["prefixes"],
+ opt,
+ 4,
+ "ip_prefix",
+ (cidr) => { return calcCidrLen(32, cidr) });
+ }
+
+ if (opt["ipv6"]) {
+ procPrefixes(
+ ctx,
+ json["ipv6_prefixes"],
+ opt,
+ 6,
+ "ipv6_prefix",
+ (cidr) => { return calcCidrLen(128, cidr) });
+ }
+
+ ctx.csv.end(); // The CSV string will be posted in the event handler
+ }
+ catch (e) {
+ onerror(e, ctx);
+ }
+};
diff --git a/toss-aws-eip/README.md b/toss-aws-eip/README.md
new file mode 100644
index 0000000..caa0355
--- /dev/null
+++ b/toss-aws-eip/README.md
@@ -0,0 +1,85 @@
+# Ranged AWS EIP Allocator
+When you request an EIP address, the AWS randomly allocates an EIP address from
+one of their IPv4 address pools. The list of the IPv4 pools the AWS uses for
+their service is publicly available from the following.
+
+https://ip-ranges.amazonaws.com/ip-ranges.json
+
+I also made the tool for converting the JSON data to CSV so you can use it in
+spreadsheets.
+
+https://ashegoulding.github.io/aws-ipblocks-csv
+
+This is the script you're after if you're trying to get an EIP within a specific
+range or block to get away from the lousy neighbours who constantly degrade the
+reputation of the address block or just to get a series of contiguous EIP for
+your EC2 fleet.
+
+I recommend running it on an EC2 instance rather than on the local machine to
+save the trip to the internet. The request process time from the EC2 endpoint is
+already over few hundred milliseconds so you definitely want to reduce the trip
+through the internet.
+
+Please check the pricing rules before considering using this. If they charge for
+allocation/release of EIPs, you're screwed and the script is basically useless.
+
+## This is a Bad Idea!
+The script has to be used as a last resort after you have failed to get support
+from the AWS in getting the EIP's you want. If you're a corporate user, you can
+probably get the support you need.
+
+The big issue with this approach is that there's no way of knowing how saturated
+the EIP block you're trying to get addresses from. You may use tools like nmap,
+but there's still the problem of unassociated EIP addresses.
+
+## How to
+Make sure you have done your `aws configure` and given allocate_address and
+release_address permissions to the IAM account. You may test the permissions
+using `-d` option. You'll get an error and the script will exit with code 1 if
+the account lacks the necessary permissions.
+
+Choose the block you wish to get an EIP address from. Multiple ranges can be
+specified and the script will exit if an address from any of the ranges is
+allocated.
+
+```bash
+./toss-aws-eip.py \
+ -r us-west-1 \ # not required if the default region is set in the profile
+ -l "tosser" \ # resource name for identification purposes
+ 52.94.249.80/28 \ # range
+ 52.95.255.96/28 \ # range
+ 52.94.248.128/28 # range
+```
+
+In the example, the script will allocate and release EIP addresses until one
+from any of the three blocks is acquired. The name tag on the address will be
+"tosser".
+
+You can even run the script in several processes. The process returns 0 when
+successful and it also handles `SIGINT` and `SIGTERM` gracefully without leaving
+a "residue" EIP. If you want multiple EIP's, simply count the number of
+processes that returned 0.
+
+Run with `-h` option for more.
+
+## Why?
+I was having issues with the reputations of IP addresses allocated for EC2. It
+is a known fact that many EC2 instances are hacked and used as bots for
+nefarious activities like SSH brute forcing and sending junk mails. The
+reputation is especially important for sending mails because companies take
+aggressive measures to combat junk mails.
+
+I started with an EIP without knowing this and getting my EIP already set for
+all my self-hosted services was a long and hard process. Companies like Google
+and Microsoft keep a public channel via which sysadmins can file complaints to
+get their addresses off their blacklist. But Outlook(Microsoft) has the stronger
+measure of blacklisting the entire IP address blocks attacks and junk mails
+originate from. There is no way that was legal, but I decided to get a clean EIP
+from a clean block this time instead of dealing with AWS and Microsoft Support
+because I'd never get anything good out of them.
+
+My idea is that I could be better of having an EIP from a relatively small
+block. Even if I end up getting a dirty EIP, I can go through the support
+channels again to delist the EIP and there will be less chance of the entire
+block getting blacklisted because of the small size. You can only do this in
+trial and error. This is where the script comes in.
diff --git a/toss-aws-eip/multitoss.sh b/toss-aws-eip/multitoss.sh
new file mode 100755
index 0000000..78f9f46
--- /dev/null
+++ b/toss-aws-eip/multitoss.sh
@@ -0,0 +1,103 @@
+#!/bin/bash
+declare nb_proc
+declare nb_runs=1
+declare cmdline
+declare flag_help=false
+
+## Func defs
+
+print_help () {
+ cat << EOF
+Run the command in pararrel ensuring the number of sucessful exits.
+Usage: $1 <OPTIONS> <CMDLINE>
+Options:
+ -h print this message and exit gracefully
+ -p number of processes to spawn (required)
+ -n number of successful run to count (default: 1)
+EOF
+}
+
+parse_params () {
+ local name
+ local delta
+
+ while getopts "hp:n:" name
+ do
+ case "$name" in
+ h) flag_help=true ;;
+ p) let "nb_proc=$OPTARG" ;;
+ n) let "nb_runs=$OPTARG" ;;
+ '?') exit 2;;
+ esac
+ done
+
+ let "delta = OPTIND - 1"
+ shift $delta
+
+ cmdline="$@"
+}
+
+spwan_one () {
+ $cmdline &
+}
+
+main () {
+ local procs
+ local ec
+ local good_runs=0
+ local children
+
+ # spwan initial processes
+ for (( procs = 0; procs < nb_proc; procs += 1 ))
+ do
+ spwan_one
+ done
+
+ while true
+ do
+ wait -n
+ ec=$?
+ echo $ec
+
+ if [ $ec -eq 0 ]; then
+ let "good_runs += 1"
+ if [ $good_runs -ge $nb_runs ]; then
+ break
+ else
+ spwan_one
+ fi
+ elif [ $ec -ne 3 ]; then
+ # error occurred or no more child left. do not continue
+ break
+ fi
+ done
+
+ children="$(jobs -p)"
+ [ ! -z "$children" ] && kill -TERM $children 2> /dev/null > /dev/null
+ while wait -n; do : ; done
+
+ return $ec
+}
+
+## Init script
+parse_params $@
+
+## Parametre check
+if $flag_help; then
+ print_help
+ exit 0
+fi
+if [ -z "$nb_proc" ]; then
+ cat << EOF >&2
+-p option not set. Run with -h option for help.
+EOF
+ exit 2
+fi
+if [ -z "$cmdline" ]; then
+ cat << EOF >&2
+CMDLINE not set. Run with -h option for help.
+EOF
+fi
+
+## Main start
+main
diff --git a/toss-aws-eip/toss-aws-eip.py b/toss-aws-eip/toss-aws-eip.py
new file mode 100755
index 0000000..2029ae0
--- /dev/null
+++ b/toss-aws-eip/toss-aws-eip.py
@@ -0,0 +1,378 @@
+#!/bin/env python3
+
+# Copyright (c) 2023 David Timber <dxdt@dev.snart.me>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import getopt
+import ipaddress
+import math
+import signal
+import sys
+import time
+from decimal import Decimal
+from enum import Enum
+from typing import Callable
+
+import boto3
+
+VER_STR = "0.0.0 (Nov 2023)"
+
+HELP_STR = '''Usage: {prog} [options] <SPEC> [SPEC ...]
+Repeat allocating Elastic IP address until an address in desired range is
+acquired. The multiple specs will be OR'd.
+
+SPEC: <RANGE | NET>
+ RANGE: <ADDR>-<ADDR>
+ ADDR: an IPv4 address
+ NET: <ADDR>/<CIDR>
+ CIDR: integer [0,32]
+Options:
+ -r <string> aws region name. Used if the default is not set in profile
+ -p <string> aws profile to use. Use the default profile if unspecified
+ -l <string> set the name for the allocated EIP. Defaults to an empty string
+ -x <string> the tag spec in "name=value" format
+ -n <int> limit number of attempts. Default: -1
+ -t <decimal> limit run time in seconds. Default: 'inf'
+ -d do a dry run
+ -h print this message and exit gracefully
+ -v increase verbosity
+ -q shut up
+ -V print other info and exit gracefully
+
+WARNING: check the pricing policy of your region prior to using this tool!'''
+V_STR = '''Version: {ver}
+by David Timber <dxdt@dev.snart.me> (c) 2023'''
+
+# Global classes
+
+class Verbosity (Enum):
+ '''Verbosity Level Enum'''
+ Q = ERR = 0
+ DEFAULT = WARN = 1
+ INFO = 2
+ DBG0 = 3
+ DBG1 = 4
+
+class AddrSpec:
+ '''The class works in two modes - A to B range mode and CIDR mode. `range()`
+ and `net()` factory methods can be used to instantiate a working instance.
+ The default constructor instantiates a unusable dummy instance.'''
+ def _range_in_op (self, addr: ipaddress.IPv4Address) -> bool:
+ return self.a <= addr and addr <= self.b
+
+ def _net_in_op (self, addr: ipaddress.IPv4Address) -> bool:
+ return addr in self.net
+
+ def _range_str_op (self) -> str:
+ return '''{a}-{b}'''.format(a = str(self.a), b = str(self.b))
+
+ def _net_str_op (self) -> str:
+ return str(self.net)
+
+ def range (a: ipaddress.IPv4Address, b: ipaddress.IPv4Address):
+ ret = AddrSpec()
+ ret.a = a
+ ret.b = b
+ ret.net = None
+ ret._contains_f = ret._range_in_op
+ ret._str_f = ret._range_str_op
+ return ret
+
+ def net (n: ipaddress.IPv4Network):
+ ret = AddrSpec()
+ ret.a = ret.b = None
+ ret.net = n
+ ret._contains_f = ret._net_in_op
+ ret._str_f = ret._net_str_op
+ return ret
+
+ def __init__(self):
+ self.a = None
+ self.b = None
+ self.net = None
+
+ def __contains__ (self, addr: ipaddress.IPv4Address) -> bool:
+ return self._contains_f(addr)
+
+ def __str__ (self) -> str:
+ return self._str_f()
+
+class ProgConf:
+ '''The program configuration class. The members represent the parametres
+ from the command line arguments.'''
+ def __init__(self):
+ self.range_specs = list[AddrSpec]()
+ self.profile = None
+ self.tag_spec = []
+ self.nb_runs = math.inf
+ self.runtime = math.inf
+ self.verbose = Verbosity.DEFAULT.value
+ self.help = False
+ self.dryrun = False
+ self.region = None
+ self.ver = False
+
+ def CompVerbosity (self, v: Verbosity) -> bool:
+ return self.verbose >= v.value
+
+class EIP:
+ '''Class for holding the EIP and the allocation id for a successful
+ iteration. The `str()` operator should return a string that can be more or
+ less parsed by a YAML parser.'''
+ def __init__(self, addr: ipaddress.IPv4Address, alloc_id: str):
+ self.addr = addr
+ self.alloc_id = alloc_id
+
+ def __str__(self) -> str:
+ return '''PublicIp: {addr}
+AllocationId: {alloc_id}'''.format(
+ addr = str(self.addr),
+ alloc_id = self.alloc_id)
+
+# Exceptions
+class FormatError (Exception):
+ '''Used for cmd line args parse error'''
+ ...
+
+
+def ParseAddrSpec (x: str) -> AddrSpec:
+ '''If the string contains a hyphen, try to extract the two addresses. If the
+ string contains a slash, treat the characters before it as an IP address and
+ the ones after it a CIDR length.'''
+ sep = x.find("-")
+ if sep > 0: # range
+ a = x[:sep].strip()
+ b = x[sep + 1:].strip()
+ return AddrSpec.range(ipaddress.IPv4Address(a), ipaddress.IPv4Address(b))
+
+ sep = x.find("/")
+ if sep > 0: # network
+ return AddrSpec.net(ipaddress.IPv4Network(x))
+
+ raise FormatError("Invalid address spec: " + x)
+
+def ParseParam (argv: list[str]) -> ProgConf:
+ '''The cmd line args parser'''
+ ret = ProgConf()
+ opt, args = getopt.getopt(argv, "p:l:x:n:t:hdvqr:V")
+
+ for v in args:
+ ret.range_specs.append(ParseAddrSpec(v))
+
+ for k, v in opt:
+ match k:
+ case "-r": ret.region = v
+ case "-p": ret.profile = v
+ case "-l": ret.tag_spec.append({ 'Key': 'Name', 'Value': v })
+ case "-x":
+ i = v.find("=")
+ if i < 0:
+ raise FormatError("Invalid format for option '-x': " + v)
+ tname = v[:i]
+ tvalue = v[i + 1:]
+ ret.tag_spec.append({ 'Key': tname, 'Value': tvalue })
+ case '-n':
+ ret.nb_runs = int(v)
+ if ret.nb_runs <= 0: ret.nb_runs = math.inf
+ case '-t': ret.runtime = Decimal(v) * 1000000000
+ case '-h': ret.help = True
+ case '-v': ret.verbose += 1
+ case '-q': ret.verbose = Verbosity.Q.value
+ case '-d': ret.dryrun = True
+ case '-V': ret.ver = True
+
+ return ret
+
+def GetRefClock () -> int:
+ '''A wrapper function for retrieving the monotonic clock tick value used to
+ measure the process run time.'''
+ return time.monotonic_ns()
+
+def DoPrint (s: str, v: Verbosity):
+ '''Check verbosity level and print the string to stderr.'''
+ if conf.CompVerbosity(v):
+ return sys.stderr.write(s)
+
+try:
+ conf = ParseParam(sys.argv[1:])
+except (FormatError, ipaddress.AddressValueError) as e:
+ sys.stderr.write(str(e) + "\n")
+ sys.exit(2)
+run_cnt = 0 # the number of iteration performed
+run_start = GetRefClock() # time the process started
+it_ret = None # acquired EIP information returned after a successful iteration
+flag = True # flag used to stop the main loop to serve exit signals(TERM, INT)
+
+if conf.help:
+ print(HELP_STR.format(prog = sys.argv[0]))
+ sys.exit(0)
+if conf.ver:
+ print(V_STR.format(ver = VER_STR))
+ sys.exit(0)
+
+if not conf.range_specs:
+ DoPrint("No SPEC specified. Run with '-h' option for help.\n", Verbosity.ERR)
+ sys.exit(2)
+
+# Init Boto3
+session = boto3.Session(
+ region_name = conf.region,
+ profile_name = conf.profile)
+client = session.client("ec2")
+
+def PrintedCall (func: Callable, fname: str, v: Verbosity, **kwargs):
+ DoPrint('''CALL {fname}({kwargs})\n'''.format(fname = fname, kwargs = kwargs), v)
+ return func(**kwargs)
+
+def PrintReturn (fname: str, ret, v: Verbosity):
+ return DoPrint('''RET {fname}(): {ret}'''.format(fname = fname, ret = str(ret)), v)
+
+def ShouldIterate () -> bool:
+ '''Check if the main loop should continue'''
+ global run_cnt, conf, flag
+
+ run_elapsed = GetRefClock() - run_start
+ ret = flag and run_cnt < conf.nb_runs and run_elapsed < conf.runtime
+ run_cnt += 1
+
+ return ret
+
+def OptInSignalHandler (sname: str, handler: Callable):
+ '''Install the signal handler if the signal with the name exists on the
+ platform.'''
+ if hasattr(signal, sname):
+ return signal.signal(signal.Signals(sname), handler)
+
+def HandleSignal (sn, sf):
+ '''Exit signal handler'''
+ global flag
+
+ flag = False
+ # Deregister the handler so that the subsequent signals kill the process
+ signal.signal(sn, signal.SIG_DFL)
+
+ # Signal names are not supported by all platforms
+ try:
+ signame = signal.Signals(sn).name
+ except:
+ signame = "?"
+ DoPrint('''CAUGHT {signame}({sn})\n'''.format(
+ signame = signame,
+ sn = sn), Verbosity.WARN)
+
+def ExtractBotoError (e: Exception) -> str:
+ '''Not many types of Boto3 exceptions are defined for errors. Use the "duck
+ typing technique" to extract the error code returned from the AWS
+ endpoint.'''
+ if (hasattr(e, "response") and
+ type(e.response) == dict and
+ type(e.response.get("Error")) == dict):
+ return e.response["Error"].get("Code")
+
+def IsDryRunError (e: Exception) -> bool:
+ return ExtractBotoError(e) == "DryRunOperation"
+
+def DoIteration () -> EIP | None:
+ '''Returns the EIP in the desired range if successful. None otherwise.'''
+ global conf
+ # Pre-construct the tag spec.
+ tag_spec = [ { 'ResourceType': 'elastic-ip' , 'Tags': conf.tag_spec } ] if conf.tag_spec else None
+
+ try:
+ # Send the allocation request!
+ r = PrintedCall(
+ client.allocate_address,
+ "client.allocate_address",
+ Verbosity.DBG0,
+ TagSpecifications = tag_spec,
+ DryRun = conf.dryrun)
+ PrintReturn("client.allocate_address", r, Verbosity.DBG0)
+ # The method will return the response object if successful
+ ip = ipaddress.IPv4Address(r['PublicIp'])
+ alloc_id = r['AllocationId']
+
+ DoPrint('''Got {ip}\n'''.format(ip = ip), Verbosity.INFO)
+ except Exception as e:
+ if conf.dryrun and IsDryRunError(e):
+ # This is expected for dry run. Carry on with mock data.
+ ip = None
+ alloc_id = "DryIce"
+ else:
+ # Propagate other errors
+ # Could be bad internet connection or insufficient privileges
+ raise e
+
+ if ip:
+ # Check if the address allocated is within the desired range
+ for spec in conf.range_specs:
+ DoPrint("IS {ip} in {range}?: ".format(
+ ip = ip,
+ range = spec
+ ), Verbosity.DBG1)
+ ret = ip in spec # this calls `_net_in_op()` or `_range_in_op()`
+ DoPrint("{verdict}\n".format(
+ verdict = "yes" if ret else "no"), Verbosity.DBG1)
+ if ret:
+ # Instantiate and return the result!
+ return EIP(ip, alloc_id)
+
+ # Reached because the allocated EIP is not in the desired range
+ # Release the EIP.
+ try:
+ r = PrintedCall(
+ client.release_address,
+ "client.release_address",
+ Verbosity.DBG0,
+ AllocationId = alloc_id,
+ DryRun = conf.dryrun)
+ PrintReturn("client.release_address", r, Verbosity.DBG0)
+ except Exception as e:
+ if conf.dryrun and IsDryRunError(e):
+ # This is expected for dry run. Let the function return. It is
+ # possible that the user has allocate rights but not release rights.
+ # If that's the case, this is where the user will find out.
+ pass
+ else:
+ # Propagate other errors
+ # Could be bad internet connection or insufficient privileges
+ raise e
+
+# Catch these normal case signals so that the current iteration can release the
+# EIP before coming out of the main loop.
+OptInSignalHandler("SIGINT", HandleSignal)
+OptInSignalHandler("SIGTERM", HandleSignal)
+OptInSignalHandler("SIGHUP", HandleSignal) # multitoss support
+
+while ShouldIterate():
+ DoPrint('''Iteration #{nr_run} ...\n'''.format(nr_run = run_cnt), Verbosity.INFO)
+ it_ret = DoIteration()
+ if it_ret:
+ DoPrint("Got EIP in target range!\n", Verbosity.INFO)
+ print(it_ret)
+ break
+
+run_elapsed = GetRefClock() - run_start
+DoPrint(
+ '''Run complete after {nb_runs} run(s) in {run_elapsed:.3f}\n'''.format(
+ nb_runs = run_cnt,
+ run_elapsed = run_elapsed / 1000000000.0
+ ), Verbosity.INFO)
+
+sys.exit(0 if it_ret else 3)
diff --git a/writeups/headless-vnc.ko/headless-vnc.ko-kr.md b/writeups/headless-vnc.ko/headless-vnc.ko-kr.md
new file mode 100644
index 0000000..05d2486
--- /dev/null
+++ b/writeups/headless-vnc.ko/headless-vnc.ko-kr.md
@@ -0,0 +1,88 @@
+# 클라우드 리눅스 VM에 GUI 돌리기
+
+AWS EC2나 GCP CE의 윈도 머신은 대부분 RDP로 접근하는 반면에, 리눅스 머신은 모두 CUI로만
+사용하도록 되어있음. 리눅스는 셸로 모든 작업을 할 수 있지만 유니티 에디터같은 소프트웨어는 GUI없이
+거의 사용이 불가능하다. 머신에 하드웨어를 추가할 수 없는 환경이라면 이 방법을 추천한다.
+
+## 개요
+**Xorg X11 dummy video**를 사용해서 가상 fb를 만들고, **TigerVNC X 윈도 서버 모듈**을
+사용해 원격에서 접속할 수 있도록 설정한다. 이 문서는 VM에 GDE를 설치하는 방법을 다룬다. 이 문서는
+VNC 포트를 외부에 공개하지 않고 SSH 터널링을 사용해 서버 VNC에 연결하는 방법을 설명한다.
+
+## 방법
+### SSH 접속
+원격 VNC 포트를 로컬로 터널링한다. 포트 2개를 사용하는 이유는 추후에 설명:
+```
+ssh -L15900:127.0.0.1:5900 -L15901:127.0.0.1:5901 <주소>
+```
+
+### 패키지 설치
+RPM:
+
+```xorg-x11-drv-dummy tigervnc-server-module gnome-shell gnome-terminal```
+
+DEB: (TODO)
+
+### 설정
+
+**/etc/X11/xorg.conf.d/00-dummy-vnc-video.conf** (새로 만들기):
+```
+Section "Module"
+ Load "vnc"
+EndSection
+
+Section "Device"
+ Identifier "Configured Video Device"
+ Driver "dummy"
+EndSection
+
+Section "Monitor"
+ Identifier "Configured Monitor"
+ HorizSync 31.5-48.5
+ VertRefresh 50-70
+EndSection
+
+Section "Screen"
+ Identifier "Default Screen"
+ Monitor "Configured Monitor"
+ Device "Configured Video Device"
+ DefaultDepth 24
+ SubSection "Display"
+ Depth 24
+ Modes "1280x720"
+ EndSubSection
+
+ Option "SecurityTypes" "None"
+ Option "AlwaysShared" "true"
+EndSection
+```
+
+해상도나 색상 깊이를 알맞게 변경한다.
+
+`Option "SecurityTypes" "None"` 행은 VNC 접속 시
+암호를 묻지 않도록 설정한다는 의미이다. 암호를 설정하고 싶다면 [이 문서](https://wiki.archlinux.org/index.php/TigerVNC#Expose_the_local_display_directly)
+를 참조.
+
+`Option "AlwaysShared" "true"` 행은 동시 접속을 허용한다는 의미. 원치 않으면 주석처리.
+
+VNC 포트를 외부에 노출하여 사용하는 것은 안전하지 않다. TigerVNC가 TLS를 지원하나, X11 모듈로
+사용되어 X11 설정으로 옵션을 넘겨주어야 해서 복잡한 설정이 어려울 것이다. 따라서 저자는 VNC 포트를
+개방하지 않고 SSH 터널링으로 VNC 접속하는 방법을 택하였다.
+
+### X 서버 실행
+```
+systemctl set-default graphical.target
+systemctl start graphical.target
+```
+
+Systemd를 사용하지 않는 배포판에서는 gdm을 enable, start하거나 runlevel을 5로 설정하는 등의
+작업을 하여 gdm을 실행한다.
+
+### 접속, 로그인
+**127.0.0.1:15900**에 접속하여 원하는 계정으로 로그인한다. 로그인에 성공하면 VNC로 보이는
+화면이 빈 화면으로 유지되면 정상. **127.0.0.1:15901**로 접속하면 로그인된 GUI 세션을 이용할 수
+있다.
+
+## 외부 링크
+* https://lxtreme.nl/blog/headless-x11/
+* https://wiki.archlinux.org/index.php/TigerVNC#Expose_the_local_display_directly
diff --git a/writeups/powershell-email/README.md b/writeups/powershell-email/README.md
new file mode 100644
index 0000000..4d815d4
--- /dev/null
+++ b/writeups/powershell-email/README.md
@@ -0,0 +1,43 @@
+# Sending Mail using Powershell
+Turns out, Powershell can be used to send emails through harnessing the power of
+C#. I made this script as a POC as to show how far .Net and Powershell have
+come.
+
+Should work on all platforms that support Powershell.
+
+## Usage
+Copied from the script.
+```sh
+echo 'Hi! it going? Testing my Powershell script.' | \
+ smtp_host=smtp.gmail.com \
+ smtp_username=example@gmail.com \
+ smtp_password='0123456789' \
+ mail_from=alice@gmail.com \
+ mail_to=bob@example.com \
+ mail_subject='Sent using Powershell' \
+ sendmail.ps1 \
+ doc.pdf
+```
+
+## Few Tips
+### Password
+Services like Gmail will require you to get a separate password for external
+apps. Google calls this "App password". Refer to the links below.
+
+* https://support.google.com/accounts/answer/185833
+* https://support.google.com/mail/answer/7126229
+
+Even if the normal password for the account can be used, a separate password
+should always be used for program access. Always check if your email provider
+supports this.
+
+### TLS
+Most services will refuse to serve on unsecure connections. Use `smtp_tls=O`
+only as the last resort.
+
+`smtp_tls_cert` is for TLS CN SASL authentication. if authenticating using this
+method, `smtp_username` and `smtp_password` are not required.
+
+### CC and More
+Didn't think about CC and all the advanced composition. Feel free to add more
+feature to the script that is already monstrous!
diff --git a/writeups/powershell-email/sendmail.ps1 b/writeups/powershell-email/sendmail.ps1
new file mode 100644
index 0000000..e5500ec
--- /dev/null
+++ b/writeups/powershell-email/sendmail.ps1
@@ -0,0 +1,181 @@
+#!/usr/bin/env pwsh
+
+# Send an email using Powershell.
+# Usage: sendmail.ps1 [attachment 1 [attachment 2 [... attachment N]]]
+#
+# This is a POC on how to send an email using Powershell. The script should work
+# on all platforms. The information required for the script to work is all
+# supplied via environment variables.
+#
+# Env Vars
+# smtp_host
+# smtp_port (best if you let the implementation decide)
+# smtp_tls:
+# 'F' to insist on secure connection (default)
+# 'O' for opportunistic
+# 'N' to disable (default if $smtp_host is "localhost")
+# smtp_tls_cert
+# smtp_username
+# smtp_password
+# mail_from (required)
+# mail_to (required)
+# mail_subject (required)
+#
+# Example
+#```pwsh
+# echo 'Hi! it going? Testing my Powershell script.' | \
+# smtp_host=smtp.gmail.com \
+# smtp_username=example@gmail.com \
+# smtp_password='0123456789' \
+# mail_from=alice@gmail.com \
+# mail_to=bob@example.com \
+# mail_subject='Sent using Powershell' \
+# sendmail.ps1 \
+# doc.pdf
+#```
+using namespace System
+using namespace System.Net
+using namespace System.Security.Cryptography
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = "Stop"
+$PSDefaultParameterValues['*:ErrorAction'] = 'Stop'
+
+<#
+.SYNOPSIS
+Get an environment variable and unset it
+
+.PARAMETER Name
+The name of the environment variable to read and unset
+
+.PARAMETER Required
+If set, throw FileNotFoundException if the environment variable requested is not
+set
+
+.NOTES
+The purpose of the function is to get and scrub off the password passed as an
+env var in one go. To preserve the env var, use `GetEnvironmentVariable()`
+directly.
+#>
+function FetchEnv ([string]$Name, [bool]$Required = $false) {
+ $RetVal = [Environment]::GetEnvironmentVariable($Name)
+ [Environment]::SetEnvironmentVariable($Name, '')
+ if ( $RetVal ) {
+ return $RetVal
+ }
+ else {
+ if ($Required) {
+ throw New-Object IO.FileNotFoundException ("${Name}: unset env var")
+ }
+ else {
+ return $null
+ }
+ }
+}
+
+<#
+.SYNOPSIS
+Read data from STDIN until EOF and return data as decoded string
+#>
+function ExhaustStdin () {
+ $stream = New-Object IO.StreamReader ( [Console]::OpenStandardInput() )
+ return $stream.ReadToEnd()
+}
+
+
+######################################################################
+# Execution starts here
+######################################################################
+
+# Compose a message
+$mail = New-Object Mail.MailMessage (
+ (FetchEnv "mail_from" $true),
+ (FetchEnv "mail_to" $true),
+ (FetchEnv "mail_subject" $true),
+ (ExhaustStdin)) # This is the part where the mail body is read from STDIN
+
+# Add attachments
+foreach ($file in $args) {
+ [string]$file = $file
+
+ $a = New-Object Mail.Attachment (
+ $file,
+ # Treat all attachments as binary
+ [System.Net.Mime.MediaTypeNames+Application]::Octet)
+ # Timestamp support
+ $a.ContentDisposition.CreationDate = [IO.File]::GetCreationTime($file)
+ $a.ContentDisposition.ModificationDate = [IO.File]::GetLastWriteTime($file)
+ $a.ContentDisposition.ReadDate = [IO.File]::GetLastAccessTime($file)
+
+ $mail.Attachments.Add($a)
+}
+
+# Set up credentials
+$client_cred = New-Object NetworkCredential (
+ (FetchEnv "smtp_username"),
+ (FetchEnv "smtp_password"))
+
+# Set up client TLS cert
+$tls_cert = FetchEnv("smtp_tls_cert")
+if ($null -ne $tls_cert) {
+ $cert_chain = New-Object X509Certificates.X509Certificate ( $tls_cert )
+}
+else {
+ $cert_chain = $null
+}
+
+# Read target SMTP host
+$smtp_host = FetchEnv "smtp_host"
+if (!$smtp_host) {
+ $smtp_host = "localhost"
+}
+
+# Set up SMTP client object
+$smtp = New-Object Mail.SmtpClient ($smtp_host)
+if ($cert_chain) {
+ $smtp.ClientCertificates.Add($cert_chain)
+}
+$smtp.Credentials = $client_cred
+$smtp_port = FetchEnv "smtp_port"
+if ($smtp_port) {
+ $smtp.Port = [int]$smtp_port
+}
+
+$tlsmode = FetchEnv "smtp_tls"
+if (!$tlsmode) {
+ # Determine the "tlsmode" to default to
+ if ($smtp_host -eq "localhost") {
+ # No need to waste computing power on TLS.
+ # Unless you're paranoid and don't trust the hosts file.
+ $tlsmode = "N"
+ }
+ else {
+ # Transmitting plain text data on the internet nowadays is no-brainer.
+ # Most email services will refuse anyway.
+ $tlsmode = "F"
+ }
+}
+
+# Set `$smtp.EnableSsl` based on `$tlsmode`
+if ($tlsmode -eq "F" -or $tlsmode -eq "O") {
+ $smtp.EnableSsl = $true
+}
+elseif ($tlsmode -eq "N") {
+ $smtp.EnableSsl = $false
+}
+
+try {
+ $smtp.Send($mail)
+}
+catch {
+ if ($tlsmode -eq "O") {
+ # Opportunistic tlsmode. Try again with TLS disabled.
+ # Please think twice and fix the problem before resorting to this bit.
+ $smtp.EnableSsl = $false
+ $smtp.Send($mail)
+ }
+ else {
+ # Let the script die
+ throw $_
+ }
+}
diff --git a/writeups/selfhosting-email/fuckyou-gmail.en.md b/writeups/selfhosting-email/fuckyou-gmail.en.md
new file mode 100644
index 0000000..1bdfe7f
--- /dev/null
+++ b/writeups/selfhosting-email/fuckyou-gmail.en.md
@@ -0,0 +1,100 @@
+# What to do when Gmail marks all the mails from your server as spam
+If you're self-hosting your services and having trouble getting your emails
+through Gmail and infuriated by Google's non-existent support, you're not the
+only one. I'd like to share my experiences trying to get it sorted out.
+
+* https://support.google.com/mail/thread/171517615?hl=en&msgid=172576102
+
+I'm the author of the post above. You can tell how arrogant Google employees are
+from all the previous posts he made in the past.
+
+* https://support.google.com/mail/thread/4857692/how-to-delist-my-ip-address-from-gmail-blacklist?hl=en
+* https://support.google.com/mail/thread/3745648?hl=en
+
+![Mocking Spongebob: There is nothing wrong with out servers. You're doing
+something
+wrong!](https://ashegoulding.github.io/attmnts/fuckyou-gmail.en/stoP-THAt-RiGHT-nOW.en.jpg)
+
+Seriously, fuck these guys.
+
+## The Basics
+Don't embarrase yourself by setting up your servers wrong. Make sure that emails
+have valid DKIM signatures, mail contents are good, rDNS is properly set, MTAs
+use TLS 1.3 with valid certificates, and there's no error in TXT records. You
+have to get those all "PASS" marks and the padlock icon next to the email
+address. This is the very basic. Make sure your servers are complaint before
+sending anything. And whenever you change the settings, **TEST IT** or your IP
+address can be listed because of broken configuration.
+
+Here are the tools I use to diagnose.
+
+- https://www.checktls.com/TestReceiver (most favourable for TLS diagnosis)
+- https://www.mail-tester.com/
+- https://mxtoolbox.com/deliverability
+
+## Getting a clean public IPv4 address
+If you're sure you've got everything right and all the other providers respect
+the mails from your server, it's most likely Google's internal rep list.
+
+Contrary to that guy's claim, it is evident that Google does keep an internal IP
+reputation list. If the IPv4 address you have been assigned is dirty, all the
+email from your server could be marked as spam. Forever. Doesn't matter if
+you've delisted your IP from all known blacklists. Not only Google but also all
+the other major email service providers do not account for the fact that IP
+addresses get passed around and the blacklist entries must expire. **Google DOES
+NOT CARE**. It's our job to ensure that we get clean IP addresses.
+
+There are plenty of posts on the internet on how to check if your IP address is
+dirty, but here are the ones I use.
+
+- https://mxtoolbox.com/blacklists.aspx
+- https://dnschecker.org/ip-blacklist-checker.php
+- https://whatismyipaddress.com/blacklist-check
+- https://cleantalk.org/blacklists
+
+So basically all the tools that show up on the search result.
+
+## IPv6
+It could be safer to just use an IPv6 address for sending emails as the IPv6
+address range is wide and the use of IPv6 addresses is not yet widely spread,
+hence the less chance of getting a dirty address. But there are still MTA's with
+only IPv4 addresses. But at least most of Google's servers use IPv6, so this
+could be the solution for you. See the next section if you're using AWS.
+
+However, if your cloud service provider or your ISP would not support rDNS for
+IPv6 , make sure your server does not send any emails using the IPv6 connection.
+This can be done in many ways.
+
+- Don't assign your machine an IPv6 address at all
+- Disable the setting. For example,
+ - Postfix: `smtp_address_preference` or `inet_interfaces` altogether
+
+There shouldn't be any problem receiving mails via IPv6 connections. But if
+you're paranoid, you can disable IPv6 SMTP connectivity on your daemon or
+firewall.
+
+## Dealing with grumpy AWS support rep
+**AWS will set up a rDNS record for your IPv6 address on request!** Which is
+pretty cool.
+
+However, sometimes your ticket will be assigned to a grumpy representative who
+thinks that they're doing their job right. If your ticket is responded by
+something like "do you know what you're doing, mate?", do not attempt to reason
+with the rep. Instead, toss the ticket in the bin and retry your luck in 2 weeks
+time. Hopefully, your ticket will be assigned to someone generous. It took me 3
+attempts. It's probably because they have to put up with Telstra. It could
+depend on how strict the ISPs are in the part of the world you're in.
+
+This is the link to the tickets I'm talking about:
+
+- https://support.console.aws.amazon.com/support/contacts#/rdns-limits
+
+## Gmail Specific Tests
+There's this awesome tool made by awesome people that lists all the emails
+received by their test accounts. You can use the tool before sending your emails
+to real people's Gmail accounts.
+
+- https://www.gmass.co/inbox
+
+## FUCK YOU GOOGLE
+There I said it.