... | ... |
@@ -6,6 +6,132 @@ Interact with [NearlyFreeSpeech.NET][]'s [API][] from the command line. |
6 | 6 |
[NearlyFreeSpeech.NET]: https://www.nearlyfreespeech.net |
7 | 7 |
[API]: https://en.wikipedia.org/wiki/API |
8 | 8 |
|
9 |
+## Goals |
|
10 |
+ |
|
11 |
+`nfsn-utils` has three main goals: |
|
12 |
+ |
|
13 |
+1. **Be portable** (even to things like [routers][]). |
|
14 |
+ |
|
15 |
+ POSIX shell is used as glue for standard utilities. See |
|
16 |
+ [dependencies](#dependencies). |
|
17 |
+ |
|
18 |
+2. **Be modular** where it makes sense. |
|
19 |
+ |
|
20 |
+ [`nfsn-send`](#nfsn-send) is a general purpose [NearlyFreeSpeech.NET][] |
|
21 |
+ [API][] wrapper. [`nfsn-dns-update`](#nfsn-dns-update) is a general purpose |
|
22 |
+ [NearlyFreeSpeech.NET][] DNS update utility. |
|
23 |
+ |
|
24 |
+3. **Be opinionated with sane defaults** where it makes sense. |
|
25 |
+ |
|
26 |
+ The smaller utilities assume things like that you want to use [standard |
|
27 |
+ email addresses][]. |
|
28 |
+ |
|
29 |
+4. **Be easily auditable**. |
|
30 |
+ |
|
31 |
+ The scripts are well abstracted and no more than about 100 lines of code. |
|
32 |
+ |
|
33 |
+[routers]: https://openwrt.org |
|
34 |
+[standard email addresses]: https://www.ietf.org/rfc/rfc2142.txt |
|
35 |
+ |
|
36 |
+## Prerequisites |
|
37 |
+ |
|
38 |
+### API key |
|
39 |
+ |
|
40 |
+As described in the [NearlyFreeSpeech.NET][] [documentation][], one needs to |
|
41 |
+submit a [free assistance request][] to obtain an API key. |
|
42 |
+ |
|
43 |
+Place the credentials in the environment variables `NFSN_LOGIN` and |
|
44 |
+`NFSN_API_KEY` or in the file `./.nfsn-api` or `$HOME/.nfsn-api` (location |
|
45 |
+overridable by the `NFSN_CREDENTIALS_PATH` environment variable). This file |
|
46 |
+should be a JSON file consisting of an object with the keys `login` and |
|
47 |
+`api-key`. (The file format and default location is compatible with |
|
48 |
+[WebService::NFSN][] and [python-nfsn][].) |
|
49 |
+ |
|
50 |
+[documentation]: https://members.nearlyfreespeech.net/wiki/API/Introduction |
|
51 |
+[free assistance request]: https://members.nearlyfreespeech.net/support/assist?tag=apikey |
|
52 |
+[WebService::NFSN]: https://metacpan.org/pod/WebService::NFSN#INTERFACE |
|
53 |
+[python-nfsn]: https://github.com/ktdreyer/python-nfsn#authentication |
|
54 |
+ |
|
55 |
+### Dependencies |
|
56 |
+ |
|
57 |
+- Unix-like environment (in particular, `/dev/urandom`). |
|
58 |
+- [POSIX utilities][] with `date` supporting `+%s` (such as GNU `date`). |
|
59 |
+- `sha1sum` (for instance, the one in `coreutils`). |
|
60 |
+- [curl][]. |
|
61 |
+- [jq][]. |
|
62 |
+- `certbot` (only needed for `nfsn-dns-certbot*`). |
|
63 |
+ |
|
64 |
+[POSIX utilities]: http://pubs.opengroup.org/onlinepubs/9699919799/idx/utilities.html |
|
65 |
+[curl]: https://curl.haxx.se |
|
66 |
+[jq]: https://github.com/stedolan/jq |
|
67 |
+ |
|
68 |
+## Included programs |
|
69 |
+ |
|
70 |
+Dependency graph: |
|
71 |
+ |
|
72 |
+![included programs](doc/included-programs.dot.png) |
|
73 |
+ |
|
74 |
+### `nfsn-send` |
|
75 |
+ |
|
76 |
+Wraps the Requests, Responses and Authentication described in the |
|
77 |
+[NearlyFreeSpeech.NET][] [documentation][]. |
|
78 |
+ |
|
79 |
+### `nfsn-dns-update` |
|
80 |
+ |
|
81 |
+Updates several DNS records and outputs what data was actually changed. |
|
82 |
+ |
|
83 |
+### `nfsn-dns-a` |
|
84 |
+ |
|
85 |
+Updates DNS [A][] records, used to map hostnames to an IPv4 address. |
|
86 |
+ |
|
87 |
+[A]: https://en.wikipedia.org/wiki/List_of_DNS_record_types#A |
|
88 |
+ |
|
89 |
+### `nfsn-dns-spf` |
|
90 |
+ |
|
91 |
+Updates DNS [SPF][] records, used for email authorization (specifying who is |
|
92 |
+allowed to send mail from a domain). |
|
93 |
+ |
|
94 |
+[SPF]: https://en.wikipedia.org/wiki/Sender_Policy_Framework |
|
95 |
+ |
|
96 |
+### `nfsn-dns-dkim` |
|
97 |
+ |
|
98 |
+Updates DNS [DKIM][] records, used for email authentication (using digital |
|
99 |
+signatures). |
|
100 |
+ |
|
101 |
+[DKIM]: https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail |
|
102 |
+ |
|
103 |
+### `nfsn-dns-dmarc` |
|
104 |
+ |
|
105 |
+Updates DNS [DMARC][] records, extending [SPF][] and [DKIM][] by specifying |
|
106 |
+failure policy and reporting. |
|
107 |
+ |
|
108 |
+See also the [dmarc.org FAQ][]. |
|
109 |
+ |
|
110 |
+[DMARC]: https://en.wikipedia.org/wiki/DMARC |
|
111 |
+[dmarc.org FAQ]: https://dmarc.org/wiki/FAQ#Sender_Questions |
|
112 |
+ |
|
113 |
+### `nfsn-dns-certbot*` |
|
114 |
+ |
|
115 |
+`nfsn-dns-certbot` calls [certbot][] (the [Electronic Frontier Foundation][]'s |
|
116 |
+(EFF) [Let's Encrypt][] client, for getting HTTPS certificates) in [manual |
|
117 |
+mode][] to make it use `nfsn-utils` to update DNS records in order to fullfill |
|
118 |
+the [`dns-01` challenge][]. It does this by registering |
|
119 |
+`nfsn-dns-certbot-{auth,cleanup}` as [hooks][]. |
|
120 |
+ |
|
121 |
+By default, the `auth` hook sleeps for 30 seconds to let the DNS records |
|
122 |
+propagate. This can be overridden with the environment variable |
|
123 |
+`NFSN_DNS_CERTBOT_AUTH_SLEEP`. |
|
124 |
+ |
|
125 |
+Given that `nfsn-dns-certbot` has successfully run once, running `certbot |
|
126 |
+renew` will suffice to renew the certificates. |
|
127 |
+ |
|
128 |
+[certbot]: https://certbot.eff.org |
|
129 |
+[Electronic Frontier Foundation]: https://www.eff.org |
|
130 |
+[Let's Encrypt]: https://letsencrypt.org |
|
131 |
+[manual mode]: https://certbot.eff.org/docs/using.html#manual |
|
132 |
+[`dns-01` challenge]: https://tools.ietf.org/html/draft-ietf-acme-acme-03#section-7.4 |
|
133 |
+[hooks]: https://certbot.eff.org/docs/using.html#hooks |
|
134 |
+ |
|
9 | 135 |
## License |
10 | 136 |
|
11 | 137 |
Licensed under the [ISC License][] unless otherwise noted, see the |
12 | 138 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,20 @@ |
1 |
+digraph "included programs" { |
|
2 |
+ node [shape=box] |
|
3 |
+ |
|
4 |
+ { |
|
5 |
+ { "nfsn-dns-a" [URL="nfsn-dns-a"] } |
|
6 |
+ { "nfsn-dns-mx" [URL="nfsn-dns-mx"] } |
|
7 |
+ { "nfsn-dns-spf" [URL="nfsn-dns-spf"] } |
|
8 |
+ { "nfsn-dns-dkim" [URL="nfsn-dns-dkim"] } |
|
9 |
+ { "nfsn-dns-dmarc" [URL="nfsn-dns-dmarc"] } |
|
10 |
+ } |
|
11 |
+ -> { "nfsn-dns-update" [URL="nfsn-dns-update"] } |
|
12 |
+ -> { "nfsn-send" [URL="nfsn-send"] } |
|
13 |
+ |
|
14 |
+ { "nfsn-dns-certbot" [URL="nfsn-dns-certbot"] } |
|
15 |
+ -> { |
|
16 |
+ { "nfsn-dns-certbot-auth" [URL="nfsn-dns-certbot-auth"] } |
|
17 |
+ { "nfsn-dns-certbot-cleanup" [URL="nfsn-dns-certbot-cleanup"] } |
|
18 |
+ } |
|
19 |
+ -> { "nfsn-send" [URL="nfsn-send"] } |
|
20 |
+} |
2 | 23 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,21 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-dns-a DOMAIN NAME [IP]... |
|
5 |
+ |
|
6 |
+# nfsn-dns-a "example.com" "git" \ |
|
7 |
+# "$(curl -s "https://api.ipify.org")" |
|
8 |
+# nfsn-dns-a "example.com" "" \ |
|
9 |
+# "185.199.108.153" \ |
|
10 |
+# "185.199.109.153" \ |
|
11 |
+# "185.199.110.153" \ |
|
12 |
+# "185.199.111.153" |
|
13 |
+ |
|
14 |
+# Arguments. |
|
15 |
+ |
|
16 |
+domain="$1" ; shift |
|
17 |
+name="$1" ; shift |
|
18 |
+ |
|
19 |
+# Update. |
|
20 |
+ |
|
21 |
+nfsn-dns-update "$domain" "$name" "A" '' "$@" |
0 | 22 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,47 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-dns-certbot DOMAIN NAME EMAIL_NAME INSTALLER [CERTBOT_ARG]... |
|
5 |
+ |
|
6 |
+# nfsn-dns-certbot "example.com" "" "" "" |
|
7 |
+# nfsn-dns-certbot "example.com" "git" "" "apache" --quiet |
|
8 |
+ |
|
9 |
+# Arguments. |
|
10 |
+ |
|
11 |
+domain="$1" ; shift |
|
12 |
+name="$1" ; shift |
|
13 |
+email_name="${1:-"hostmaster"}" ; shift |
|
14 |
+installer="${1:-}" ; shift |
|
15 |
+ |
|
16 |
+# Certbot. |
|
17 |
+ |
|
18 |
+host="${name:+"$name."}$domain" |
|
19 |
+dir="$(cd "$(dirname "$0")" ; pwd)" |
|
20 |
+ |
|
21 |
+certbot certonly \ |
|
22 |
+ --non-interactive \ |
|
23 |
+ --email "$email_name@$host" \ |
|
24 |
+ --agree-tos \ |
|
25 |
+ --manual \ |
|
26 |
+ --manual-public-ip-logging-ok \ |
|
27 |
+ --manual-auth-hook "$dir/nfsn-dns-certbot-auth" \ |
|
28 |
+ --manual-cleanup-hook "$dir/nfsn-dns-certbot-cleanup" \ |
|
29 |
+ --preferred-challenges="dns" \ |
|
30 |
+ --domains "$host" \ |
|
31 |
+ "$@" |
|
32 |
+ |
|
33 |
+if [ -n "$installer" ] |
|
34 |
+then |
|
35 |
+ certbot install \ |
|
36 |
+ --cert-name "$host" \ |
|
37 |
+ --installer "$installer" \ |
|
38 |
+ |
|
39 |
+ certbot enhance \ |
|
40 |
+ --non-interactive \ |
|
41 |
+ --cert-name "$host" \ |
|
42 |
+ --domain "$host" \ |
|
43 |
+ --installer "$installer" \ |
|
44 |
+ --redirect \ |
|
45 |
+ --hsts \ |
|
46 |
+ --uir |
|
47 |
+fi |
0 | 48 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,20 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# This is a naive guess. A better implementation would use e.g. the Public |
|
5 |
+# Suffix List. |
|
6 |
+re='\(\(.*\)\.\)\?\([^.]\+\.[^.]\+\)' |
|
7 |
+domain="$(echo "$CERTBOT_DOMAIN" | sed -n "s/$re/\3/p")" |
|
8 |
+name="$(echo "$CERTBOT_DOMAIN" | sed -n "s/$re/\2/p")" |
|
9 |
+ |
|
10 |
+name="_acme-challenge${name:+".$name"}" |
|
11 |
+data="$CERTBOT_VALIDATION" |
|
12 |
+ |
|
13 |
+dir="$(cd "$(dirname "$0")" ; pwd)" |
|
14 |
+ |
|
15 |
+"$dir/nfsn-send" "POST" "/dns/$domain/addRR" \ |
|
16 |
+ "name" "$name" \ |
|
17 |
+ "type" "TXT" \ |
|
18 |
+ "data" "$data" |
|
19 |
+ |
|
20 |
+sleep "${NFSN_DNS_CERTBOT_AUTH_SLEEP:-"30"}" |
0 | 21 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,18 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# This is a naive guess. A better implementation would use e.g. the Public |
|
5 |
+# Suffix List. |
|
6 |
+re='\(\(.*\)\.\)\?\([^.]\+\.[^.]\+\)' |
|
7 |
+domain="$(echo "$CERTBOT_DOMAIN" | sed -n "s/$re/\3/p")" |
|
8 |
+name="$(echo "$CERTBOT_DOMAIN" | sed -n "s/$re/\2/p")" |
|
9 |
+ |
|
10 |
+name="_acme-challenge${name:+".$name"}" |
|
11 |
+data="$CERTBOT_VALIDATION" |
|
12 |
+ |
|
13 |
+dir="$(cd "$(dirname "$0")" ; pwd)" |
|
14 |
+ |
|
15 |
+"$dir/nfsn-send" "POST" "/dns/$domain/removeRR" \ |
|
16 |
+ "name" "$name" \ |
|
17 |
+ "type" "TXT" \ |
|
18 |
+ "data" "$data" |
0 | 19 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,23 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-dns-dkim DOMAIN NAME SELECTOR KEY_TYPE KEY_PUB |
|
5 |
+ |
|
6 |
+# nfsn-dns-dkim "example.com" "" "k1" "rsa" "$key_pub" |
|
7 |
+ |
|
8 |
+# Arguments. |
|
9 |
+ |
|
10 |
+domain="$1" ; shift |
|
11 |
+name="$1" ; shift |
|
12 |
+selector="$1" ; shift |
|
13 |
+key_type="$1" ; shift |
|
14 |
+key_pub="$1" ; shift |
|
15 |
+ |
|
16 |
+# Process. |
|
17 |
+ |
|
18 |
+name="$selector._domainkey${name:+".$name"}" |
|
19 |
+data="v=DKIM1; k=$key_type; p=$key_pub" |
|
20 |
+ |
|
21 |
+# Update. |
|
22 |
+ |
|
23 |
+nfsn-dns-update "$domain" "$name" "TXT" '^v=DKIM1' "$data" |
0 | 24 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,31 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-dns-dmarc DOMAIN NAME EMAIL_NAME POLICY |
|
5 |
+ |
|
6 |
+# nfsn-dns-dmarc "example.com" "" "" "none" |
|
7 |
+# nfsn-dns-dmarc "example.com" "" "" "quarantine" |
|
8 |
+# nfsn-dns-dmarc "example.com" "" "" "reject" |
|
9 |
+ |
|
10 |
+# Arguments. |
|
11 |
+ |
|
12 |
+domain="$1" ; shift |
|
13 |
+name="$1" ; shift |
|
14 |
+email_name="${1:-"postmaster"}" ; shift |
|
15 |
+policy="$1" ; shift |
|
16 |
+ |
|
17 |
+# Process. |
|
18 |
+ |
|
19 |
+name="_dmarc${name:+".$name"}" |
|
20 |
+data="$( |
|
21 |
+ printf "%s" \ |
|
22 |
+ "v=DMARC1; " \ |
|
23 |
+ "p=$policy; " \ |
|
24 |
+ "sp=$policy; " \ |
|
25 |
+ "pct=100; " \ |
|
26 |
+ "rua=mailto:$email_name@$domain" |
|
27 |
+)" |
|
28 |
+ |
|
29 |
+# Update. |
|
30 |
+ |
|
31 |
+nfsn-dns-update "$domain" "$name" "TXT" '' "$data" |
0 | 32 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,17 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-dns-mx DOMAIN NAME [MX_DATA]... |
|
5 |
+ |
|
6 |
+# nfsn-dns-mx "example.com" "" \ |
|
7 |
+# "10 mxa.mailgun.org." \ |
|
8 |
+# "10 mxb.mailgun.org." |
|
9 |
+ |
|
10 |
+# Arguments. |
|
11 |
+ |
|
12 |
+domain="$1" ; shift |
|
13 |
+name="$1" ; shift |
|
14 |
+ |
|
15 |
+# Update. |
|
16 |
+ |
|
17 |
+nfsn-dns-update "$domain" "$name" "MX" '' "$@" |
0 | 18 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,27 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-dns-spf DOMAIN NAME [INCLUDE]... |
|
5 |
+ |
|
6 |
+# nfsn-dns-spf "example.com" "mailgun.org" |
|
7 |
+ |
|
8 |
+# Arguments. |
|
9 |
+ |
|
10 |
+domain="$1" ; shift |
|
11 |
+name="$1" ; shift |
|
12 |
+ |
|
13 |
+# Process. |
|
14 |
+ |
|
15 |
+data="$( |
|
16 |
+ printf -- "v=spf1" |
|
17 |
+ for include in "$@" |
|
18 |
+ do |
|
19 |
+ printf -- " include:%s" "$include" |
|
20 |
+ done |
|
21 |
+ printf -- " -all" |
|
22 |
+ printf -- "\n" |
|
23 |
+)" |
|
24 |
+ |
|
25 |
+# Update. |
|
26 |
+ |
|
27 |
+nfsn-dns-update "$domain" "$name" "TXT" '^v=spf1' "$data" |
0 | 28 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,87 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-dns-update DOMAIN NAME TYPE FILTER_REGEX [DATA]... |
|
5 |
+ |
|
6 |
+# nfsn-dns-update "example.com" "git" "A" '' \ |
|
7 |
+# "$(curl -s "https://api.ipify.org")" |
|
8 |
+ |
|
9 |
+# Arguments. |
|
10 |
+ |
|
11 |
+domain="$1" ; shift |
|
12 |
+name="$1" ; shift |
|
13 |
+type="$1" ; shift |
|
14 |
+filter_regex="$1" ; shift |
|
15 |
+ |
|
16 |
+host="${name:+"$name."}$domain" |
|
17 |
+ |
|
18 |
+data_new_list="" |
|
19 |
+while [ "$#" -gt "0" ] |
|
20 |
+do |
|
21 |
+ data="$1" ; shift |
|
22 |
+ data_new_list="$( |
|
23 |
+ printf "%s${data_new_list:+"\n"}%s\n" \ |
|
24 |
+ "${data_new_list:-}" \ |
|
25 |
+ "$data" |
|
26 |
+ )" |
|
27 |
+done |
|
28 |
+ |
|
29 |
+# Old data. |
|
30 |
+ |
|
31 |
+data_old_response="$( |
|
32 |
+ nfsn-send "POST" "/dns/$domain/listRRs" \ |
|
33 |
+ "name" "$name" \ |
|
34 |
+ "type" "$type" |
|
35 |
+)" |
|
36 |
+data_old_list="$( |
|
37 |
+ printf "%s\n" "$data_old_response" \ |
|
38 |
+ | jq -r ' |
|
39 |
+ .[] | |
|
40 |
+ if ."aux" |
|
41 |
+ then |
|
42 |
+ "\(."aux") " |
|
43 |
+ else |
|
44 |
+ "" |
|
45 |
+ end |
|
46 |
+ + |
|
47 |
+ "\(."data"?)" |
|
48 |
+ ' \ |
|
49 |
+ | grep "$filter_regex" \ |
|
50 |
+ || true |
|
51 |
+)" |
|
52 |
+ |
|
53 |
+# Update. |
|
54 |
+ |
|
55 |
+if [ -n "$data_old_list" ] |
|
56 |
+then |
|
57 |
+ printf "%s\n" "$data_old_list" \ |
|
58 |
+ | while IFS= read -r data |
|
59 |
+ do |
|
60 |
+ if ! printf "%s\n" "$data_new_list" | grep -qFx "$data" |
|
61 |
+ then |
|
62 |
+ printf "Removing data: %s\n" "$data" |
|
63 |
+ nfsn-send "POST" "/dns/$domain/removeRR" \ |
|
64 |
+ "name" "$name" \ |
|
65 |
+ "type" "$type" \ |
|
66 |
+ "data" "$data" |
|
67 |
+ fi |
|
68 |
+ done |
|
69 |
+fi |
|
70 |
+ |
|
71 |
+if [ -n "$data_new_list" ] |
|
72 |
+then |
|
73 |
+ printf "%s\n" "$data_new_list" \ |
|
74 |
+ | while IFS= read -r data |
|
75 |
+ do |
|
76 |
+ if ! printf "%s\n" "$data_old_list" | grep -qFx "$data" |
|
77 |
+ then |
|
78 |
+ printf "Adding data: %s\n" "$data" |
|
79 |
+ nfsn-send "POST" "/dns/$domain/addRR" \ |
|
80 |
+ "name" "$name" \ |
|
81 |
+ "type" "$type" \ |
|
82 |
+ "data" "$data" |
|
83 |
+ else |
|
84 |
+ printf "Data already present: %s\n" "$data" |
|
85 |
+ fi |
|
86 |
+ done |
|
87 |
+fi |
0 | 88 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,114 @@ |
1 |
+#!/bin/sh |
|
2 |
+set -euC |
|
3 |
+ |
|
4 |
+# nfsn-send METHOD REQUEST_URI [KEY VALUE]... |
|
5 |
+ |
|
6 |
+# nfsn-send "GET" "member/$member/sites" |
|
7 |
+# nfsn-send "POST" "dns/$domain/listRRs" \ |
|
8 |
+# "name" "" \ |
|
9 |
+# "type" "MX" |
|
10 |
+ |
|
11 |
+# Arguments. |
|
12 |
+ |
|
13 |
+method="$1" ; shift |
|
14 |
+request_uri="$1" ; shift |
|
15 |
+ |
|
16 |
+body="" |
|
17 |
+while [ "$#" -gt "0" ] |
|
18 |
+do |
|
19 |
+ key="$1" ; shift |
|
20 |
+ value="$1" ; shift |
|
21 |
+ body="${body:-}${body:+"&"}$key=$(printf "%s" "$value" | jq -sRr '@uri')" |
|
22 |
+done |
|
23 |
+ |
|
24 |
+# Authentication. |
|
25 |
+ |
|
26 |
+login="" |
|
27 |
+api_key="" |
|
28 |
+for auth in "${NFSN_CREDENTIALS_PATH:-}" ".nfsn-api" "$HOME/.nfsn-api" |
|
29 |
+do |
|
30 |
+ if [ -f "$auth" ] |
|
31 |
+ then |
|
32 |
+ login="$(jq -r '."login"' "$auth")" |
|
33 |
+ api_key="$(jq -r '."api-key"' "$auth")" |
|
34 |
+ break |
|
35 |
+ fi |
|
36 |
+done |
|
37 |
+login="${NFSN_LOGIN:-"$login"}" |
|
38 |
+api_key="${NFSN_API_KEY:-"$api_key"}" |
|
39 |
+ |
|
40 |
+ |
|
41 |
+if [ -z "${login:-}" ] || [ -z "${api_key:-}" ] |
|
42 |
+then |
|
43 |
+ >&2 printf "%s: %s\n" \ |
|
44 |
+ "$0" \ |
|
45 |
+ "Could not find authentication credentials." |
|
46 |
+ exit 1 |
|
47 |
+fi |
|
48 |
+ |
|
49 |
+# URL. |
|
50 |
+ |
|
51 |
+protocol="https" |
|
52 |
+host="api.nearlyfreespeech.net" |
|
53 |
+ |
|
54 |
+# protocol="http" |
|
55 |
+# host="localhost:8080" |
|
56 |
+# nc -l 8080 & |
|
57 |
+ |
|
58 |
+url="$protocol://$host/$request_uri" |
|
59 |
+ |
|
60 |
+# Data. |
|
61 |
+ |
|
62 |
+body_hash="$( |
|
63 |
+ printf "%s" "$body" \ |
|
64 |
+ | sha1sum -b \ |
|
65 |
+ | cut -d ' ' -f 1 |
|
66 |
+)" |
|
67 |
+ |
|
68 |
+timestamp="$( |
|
69 |
+ date "+%s" |
|
70 |
+)" |
|
71 |
+salt="$( |
|
72 |
+ < "/dev/urandom" \ |
|
73 |
+ tr -dc "a-zA-Z0-9" \ |
|
74 |
+ | head -c 16 |
|
75 |
+)" |
|
76 |
+ |
|
77 |
+hash="$( |
|
78 |
+ printf "%s" \ |
|
79 |
+ "$login;$timestamp;$salt;$api_key;$request_uri;$body_hash" \ |
|
80 |
+ | sha1sum -b \ |
|
81 |
+ | cut -d ' ' -f 1 |
|
82 |
+)" |
|
83 |
+ |
|
84 |
+header="X-NFSN-Authentication: $login;$timestamp;$salt;$hash" |
|
85 |
+ |
|
86 |
+# Request. |
|
87 |
+ |
|
88 |
+response="$( |
|
89 |
+ curl \ |
|
90 |
+ --silent \ |
|
91 |
+ --request "$method" \ |
|
92 |
+ --header "$header" \ |
|
93 |
+ --data-raw "$body" \ |
|
94 |
+ "$url" |
|
95 |
+)" |
|
96 |
+ |
|
97 |
+error="$(printf "%s\n" "$response" | jq -r '."error"?')" |
|
98 |
+debug="$(printf "%s\n" "$response" | jq -r '."debug"?')" |
|
99 |
+ |
|
100 |
+if [ -n "$error" ] |
|
101 |
+then |
|
102 |
+ >&2 printf "%s" "$error" |
|
103 |
+ if [ -n "$debug" ] |
|
104 |
+ then |
|
105 |
+ >&2 printf " %s" "$debug" |
|
106 |
+ fi |
|
107 |
+ >&2 printf "\n" |
|
108 |
+ return 1 |
|
109 |
+fi |
|
110 |
+ |
|
111 |
+if [ -n "$response" ] |
|
112 |
+then |
|
113 |
+ printf "%s\n" "$response" |
|
114 |
+fi |