List of commits:
Subject Hash Author Date (UTC)
More tweakings all around 0c5961860deadb8bcb1dfd1be429b2966f03312a Catalin(ux) M. BOIE 2018-03-11 21:10:32
Added password support df6d270a3e243084069a31fe980d76c97d89a861 Catalin(ux) M. BOIE 2018-02-13 22:46:53
Checkpoint 049e12584744b8a51bfc5867fd0e7b2db0592deb Catalin(ux) M. BOIE 2018-02-11 22:25:13
Fixed a bug in totp, added keys in memory abec61861e2f37398026dbe7342d7751390e95d8 Catalin(ux) M. BOIE 2018-02-04 18:36:12
Initial version c641fafbd46342cd24fde45129cc3637b7ca65bc Catalin(ux) M. BOIE 2018-02-03 23:42:32
Commit 0c5961860deadb8bcb1dfd1be429b2966f03312a - More tweakings all around
Author: Catalin(ux) M. BOIE
Author date (UTC): 2018-03-11 21:10
Committer name: Catalin(ux) M. BOIE
Committer date (UTC): 2018-03-11 21:10
Parent(s): df6d270a3e243084069a31fe980d76c97d89a861
Signing key:
Tree: 9e7f261705b40ffe30a4581b98051b7c46eedf56
File Lines added Lines deleted
.gitignore 2 0
Makefile.in 3 0
README 151 5
TODO 10 7
conf.c 12 5
conf.h 6 5
duilder 1 1
key.c 1 1
nf2fa.conf.sample 12 1
nf2fa.service 0 1
nf2fa.spec.in 14 4
nf2fac.c 11 6
nf2fad.c 139 33
totp.c 14 8
File .gitignore changed (mode: 100644) (index f6b11f3..7fd8a9d)
... ... nf2fad
4 4 nf2fa_common.h nf2fa_common.h
5 5 Makefile Makefile
6 6 nf2fa.spec nf2fa.spec
7 *.out
8 Changelog
File Makefile.in changed (mode: 100644) (index cabb751..fd1e2fa)
... ... install: all
49 49 @mkdir -p $(I_ETC) @mkdir -p $(I_ETC)
50 50 @cp -vd --no-clobber nf2fa.conf.sample $(I_ETC)/nf2fa.conf @cp -vd --no-clobber nf2fa.conf.sample $(I_ETC)/nf2fa.conf
51 51 @cp -vd nf2fa.conf.sample $(I_ETC)/ @cp -vd nf2fa.conf.sample $(I_ETC)/
52 @mkdir -p /usr/lib/systemd/system
53 @cp -vd $(PRJ).service /usr/lib/systemd/system
54
File README changed (mode: 100644) (index 3aad56c..ef115c0)
... ... Name: nf2fa
2 2 Description: Port knocking meets two-factor authentication (2fa) Description: Port knocking meets two-factor authentication (2fa)
3 3 Start date: 1st Feb 2018 Start date: 1st Feb 2018
4 4 Author: Catalin(ux) M. BOIE Author: Catalin(ux) M. BOIE
5 Code: https://rocketgit.com/user/catalinux/nf2fa
5 6
6 Why we need this?
7 - Because classic port knocking can be replicated from anywhere, if the attacker
8 can "see" the packets.
9 - With a digital signature program, you need some code on the client side.
10 -
7
8 . How it works?
9 By default your firewall will drop all packets. With a special packet, which
10 will contain a code generated by your mobile device, you will be able to open
11 the firewall only from your connecting IP. Also you will be able to close
12 the firewall as soon as you do not need it open anymore.
13
14
15 . Installation & configuration
16 After installation, edit the configuration file (/etc/nf2fa.conf) and set
17 the desired parameters. Then, start the daemon.
18 You will run 'nf2fa enroll' command to enroll a new device.
19 You will need to type a password and then confirm it.
20 Install FreeIPA or Google Authenticator on your mobile device and scan the QR
21 code shown on the screen. Now your user is enrolled.
22 This procedure will set the same key on both server and mobile device.
23 Now, your 2fa application will be able to generate some time based
24 tokens (6 digit codes).
25 Check below how to setup your firewall. The rules will force control packets
26 to hit nf2fa daemon and will trigger the validation.
27 On another machine, you will need to run the special ping/nc command to open
28 the firewall. For this, you will need the password choosed at enroll time and
29 the 6 digits token. See below the examples.
30 Please note that the time must be in sync on both server and mobile device
31 because the tokens are time dependent.
32
33
34 . Why you need this program?
35 - Because classic port knocking can be replicated from anywhere, if the
36 attacker can "watch" the packets.
37 - With a digital signature program, you need some code on the client side,
38 which may not be available for all platforms.
39 - This program allows hosts to be completely silent, to not answer any
40 request from outside: no code exposed to attacks and no log pollution.
41
42
43 . Firewall preparation
44 For iptables:
45 # Connections already marked by nf2fad are accepted
46 -A INPUT -i ethX -m connmark --mark 0x77777777 -j ACCEPT
47 # Packets marked by nf2fad but with no connection tracking marking,
48 # will trigger the saving into connection tracking table
49 -A INPUT -i ethX -m mark --mark 0x77777777 -j CONNMARK --save-mark
50 # We need this rule again, to not hit again nf2fad.
51 -A INPUT -i ethX -m connmark --mark 0x77777777 -j ACCEPT
52 # These are the control channels:
53 -A INPUT -i ethX -p icmp --icmp-type echo-request -j NFQUEUE --queue-num 4444
54 -A INPUT -i ethX -p udp --dport 1 -j NFQUEUE --queue-num 4444
55 # Now, what ports to allow.
56 # Next rule will pass port 22 (for example) to nf2fa and the connection will
57 # be marked if allowed. You may add here other ports as well.
58 -A INPUT -i ethX -p tcp --dport 22 -j NFQUEUE --queue-num 4444
59
60 For ip6tables:
61 <insert here the IPv4 rules till the control channels>
62 -A INPUT -i ethX -p ipv6-icmp --icmpv6-type echo-request -j NFQUEUE --queue-num 4444
63 -A INPUT -i ethX -p udp --dport 1 -j NFQUEUE --queue-num 4444
64 -A INPUT -i ethX -p tcp --dport 22 -j NFQUEUE --queue-num 4444
65
66 Notes:
67 - Above, only SSH port was activated, but you may add any other protocol or
68 ports.
69 - These should be the first rules. If you add rules allowing ESTABLISHED conns
70 before these, you will not be able to close the firewall or renew the timer.
71 - Of course, you must have final DROP rule which drops anything.
72
73
74 . Example enrollment:
75 $ nf2fac enroll MySpecialPhone1
76 Password (only 0-9 and a-f): <type a password>
77 Password (confirmation): <type the confirmation>
78 Key is QDCAXNGWJWCIOSI5 (id 001). Scan the QR code below or type the key.
79 █████████████████████████████████████████
80 ██ ▄▄▄▄▄ █▄ ███▄█▄█▄█▀▄ ██ ▀█▀▄█ ▄▄▄▄▄ ██
81 ██ █ █ █▄ █▄ ▀▄ █▄▀ ▄█▀ ▀▄██ █ █ ██
82 ██ █▄▄▄█ █▀ ▄▄▄▄▀ ▄█▀█▄ ██ █▄█▀█ █▄▄▄█ ██
83 ██▄▄▄▄▄▄▄█▄▀ ▀▄█▄█ █▄█▄▀▄█ ▀ ▀ █▄▄▄▄▄▄▄██
84 ██▄▀▄█▀▀▄ ▀ █▀▀▄█▄ ██ ▄██ ▀█▀█ █▀ ▀ ██
85 ███▀█▀ ▀▄█ ▀ ▄█ ▄▀ █▀▀ ▄▄█▄▄▀ █▀▀ ▄▀██▄██
86 ██▄ ▀▀ ▄▄▄▀▀▄▀█▄▄▀██▀ ███▀▄▀▀ ▄▄ ▄█▄▄▀▄██
87 ██▀▄▄ ▄▄ ▀▄▄▄█▀▄ █▀▄▄▀█▄▀ ▄▄▀█▄▀ ▄▄███
88 ██▀▄█ ▄ ▄ █ ▄██ █▀ ██▀ ▄█ ▄▀█ █▀ ██
89 ██▀▄▀▄█ ▄█ ▀ ▀ ▄██ ▀▀▄ ▄█▄ ▄ ▀▀▀█ ▀ ██
90 ██▄█▀▄▄▀▄ ▄ ▀ ▀ ▀▀█ ███▀▄▀ ▀▀▀ ███▄████
91 ██▄ ▀█ ▄ ▀▄█▀ █▄▄▀▄ ▄▄▄ ▄▄▀▄▄ ▀▄▄ ▀▄▄███
92 ██▄█ ▄▄▄▄ ██▀ ▄ █ ██ ██ ▄▄ ▄██▀█▄ ██
93 ██▄▄▄ ▄▀█ ▄▄▀ ▄▀ ▄▀▄█▄█▀ █▄ ▄█▄▄ █▀█ ██
94 ██▄▄▄▄██▄█ ▀ ▀ ▄█▀ ██▀██▄▀██▄▀ ▄▄▄ ▀▄▄▀██
95 ██ ▄▄▄▄▄ █▄▀▄▄▀▄▄ ▄▀▀ ▄▀▄ ▄▀▄▀ █▄█ ▀▄ ▄██
96 ██ █ █ ██ ▄█▀▀ ▄▄▄▄ ██ ▄██ ▄▄ ███▀██
97 ██ █▄▄▄█ █▄▀█▀▀▄▀▄▄█ ▄▄▀▄█▄▀▀▀▄▀█▄▀▄▄▄██
98 ██▄▄▄▄▄▄▄█▄▄▄██▄█▄███▄██▄███▄█▄█▄█▄███▄██
99 █████████████████████████████████████████
100
101
102 . List enrollments:
103 $ nf2fac list
104
105 Sample output:
106 No Enroll time Name
107 000 2018-02-13 22:46 key1
108 Last access from IP 192.168.79.155 on 2018-02-13 23:36
109 001 2018-02-13 23:44 key2
110 Last access from IP 192.168.79.155 on 2018-02-14 00:44
111 002 2018-03-04 09:13 aaa
112 003 2018-03-04 09:15 aaa
113
114
115 . Un-enrolling
116 $ nf2fac unenroll 003
117
118 Sample outputs:
119 Successfully un-enrolled.
120 or:
121 Error: key id not found!
122
123
124 . How to send commands from the client:
125 $ ping -c1 destination -p aaPP...PPIIIIIICC
126 Where:
127 aa is the start of the command (just type two 'a' letters)
128 PP...PP is the password choosed at enrollment phase
129 IIIIII is the 6 digit pin generated by the 2fa application
130 CC is the command
131 11 - open firewall
132 cc - close firewall
133
134 Sample:
135
136 # Unlocking my IP
137 PASSWORD="12fa45"
138 PIN="082653"
139 $ ping -c1 172.30.43.4 -p aa${PASSWORD}${PIN}11
140
141 # Locking my IP
142 PASSWORD="12fa45"
143 PIN="539252"
144 $ ping -c1 172.30.43.4 -p aa${PASSWORD}${PIN}cc
145
146 If an answer is received, the command executed with success.
147 On timeout, an error occured.
148
149 Note that you can send any type of packet, not only ICMP.
150 Here, we will use UDP:
151 # On the server (we use here port 1):
152 # -A INPUT -i ethX -p udp --dport 1 -j NFQUEUE --queue-num 4444
153 PASSWORD="12fa45"
154 PIN="539252"
155 $ echo "aa${PASSWORD}$PIN}11" | nc --udp -4 172.30.43.4 1
156 # No answer will be received
File TODO changed (mode: 100644) (index c83b584..c06bbd6)
1 1 [ ] Warn user that nf2fa is not for allowing non authenticated connections. [ ] Warn user that nf2fa is not for allowing non authenticated connections.
2 [ ] Document commands.
3 [ ] Document what rules have to be added in firewall (also for shorewall).
4 2 [ ] Test non-root user enroll. [ ] Test non-root user enroll.
5 [ ] Securely compare strings
6 3 [ ] [ ]
7 4
8 5 == Future == == Future ==
6 [ ] Securely compare strings. Where?!
7 [ ] Bash auto completion
9 8 [ ] In conf file, limit which users are allowed to do the enroll. [ ] In conf file, limit which users are allowed to do the enroll.
10 9 [ ] Dynamic allocate my_ips, size in conf. [ ] Dynamic allocate my_ips, size in conf.
11 10 [ ] move load_keys to keys.c? [ ] move load_keys to keys.c?
12 [ ] Clean keys from memory! No possible, but use an XOR?
13 [ ] Should we also generate scratch codes?
11 [ ] Clean keys from memory! Not possible, but use an XOR?
12 [ ] We should also generate scratch codes.
14 13 [ ] Save the bad_entries/my_ips tables on disk? [ ] Save the bad_entries/my_ips tables on disk?
15 14 [ ] conf: log file = syslog => syslog [ ] conf: log file = syslog => syslog
16 15 [ ] Command to open the firewall till close command received? [ ] Command to open the firewall till close command received?
17 16 [ ] When we give 'close' command, remove all IPs associated with that uid? [ ] When we give 'close' command, remove all IPs associated with that uid?
18 17 Separate command! Separate command!
19 18 [ ] Open a port with a minimal web server to be able to unlock the firewall [ ] Open a port with a minimal web server to be able to unlock the firewall
20 from a web browser (think Windows (missing pattern in icmp) and phones).
21 [ ]
19 from a web browser (think Windows (missing pattern in ping) and phones).
20 [ ] Add a client which is able to run from cron and keep sending 2fa codes
21 to keep the "channel" open. Think about a vpn which should be open.
22 Name of the command? Integrate this functionality in nf2fac?
23 Where to store the key? In the same config file?
24 [ ]
File conf.c changed (mode: 100644) (index f4001b0..020e2ce)
11 11 #include "util.h" #include "util.h"
12 12 #include "conf.h" #include "conf.h"
13 13
14 unsigned short queue = 4444;
15 unsigned int bad_entries = 4096;
16 char *sock_path = "/etc/nf2fa.sock";
17 unsigned int grant_time = 3600;
18 unsigned char min_pass_len = 0;
14 unsigned short queue = 4444;
15 unsigned int bad_entries = 4096;
16 char *sock_path = "/etc/nf2fa.sock";
17 unsigned int grant_time = 3600;
18 unsigned char min_pass_len = 0;
19 unsigned int nfmark = 0x77777777;
20
19 21
20 22 /* /*
21 23 * Log configuration options * Log configuration options
 
... ... int conf_load(const char *file)
119 121 continue; continue;
120 122 } }
121 123
124 if (strcmp(buf, "nfmark") == 0) {
125 nfmark = strtoul(val, NULL, 0);
126 continue;
127 }
128
122 129 xerr("Invalid conf option: %s\n", buf); xerr("Invalid conf option: %s\n", buf);
123 130 } }
124 131 fclose(f); fclose(f);
File conf.h changed (mode: 100644) (index 899ae03..82915ee)
1 1 #define CONF_FILE "/etc/nf2fa.conf" #define CONF_FILE "/etc/nf2fa.conf"
2 2
3 extern unsigned short queue;
4 extern unsigned int bad_entries;
5 extern char *sock_path;
6 extern unsigned int grant_time;
7 extern unsigned char min_pass_len;
3 extern unsigned short queue;
4 extern unsigned int bad_entries;
5 extern char *sock_path;
6 extern unsigned int grant_time;
7 extern unsigned char min_pass_len;
8 extern unsigned int nfmark;
8 9
9 10 int conf_load(const char *file); int conf_load(const char *file);
10 11
File duilder changed (mode: 100755) (index 274273c..bcef40a)
... ... function duilder_tar()
274 274
275 275 echo "[*] Generating tarball [${P}.tar.gz]..." echo "[*] Generating tarball [${P}.tar.gz]..."
276 276 ADD_EXCLUDE="" ADD_EXCLUDE=""
277 if [ ! -z "${EXCLUDE}" ]; then
277 if [ -r "${EXCLUDE}" ]; then
278 278 ADD_EXCLUDE="--exclude-from ${P}/${EXCLUDE}" ADD_EXCLUDE="--exclude-from ${P}/${EXCLUDE}"
279 279 echo "[*] ADD_EXCLUDE=${ADD_EXCLUDE}" echo "[*] ADD_EXCLUDE=${ADD_EXCLUDE}"
280 280 fi fi
File key.c changed (mode: 100644) (index c48fe04..f0560e4)
... ... int key_remove(const unsigned int uid, const unsigned char id)
245 245 r = stat(path, &s); r = stat(path, &s);
246 246 if (r == -1) { if (r == -1) {
247 247 snprintf(error, sizeof(error), snprintf(error, sizeof(error),
248 "key not found");
248 "key id not found");
249 249 return -1; return -1;
250 250 } }
251 251
File nf2fa.conf.sample changed (mode: 100644) (index 698b145..8a169dd)
6 6 # Queue number (base 10 or base 16; 16 bits) # Queue number (base 10 or base 16; 16 bits)
7 7 # It must match your iptables rules. # It must match your iptables rules.
8 8 # Default: 4444 # Default: 4444
9 #queue = 0x4444
9 #queue = 0x5678
10 10 #queue = 65000 #queue = 65000
11 11
12 12 # Number of bad entries slots. # Number of bad entries slots.
 
27 27 # It must be an even number # It must be an even number
28 28 # Default: 0 # Default: 0
29 29 #min pass len = 10 #min pass len = 10
30
31 # Packet mark - it will be set on all connections sent to nf2fad by iptables.
32 # The purpose it to send only the SYN packets to the nf2fa daemon and the rest
33 # will not be sent, for performance reasons.
34 # Default: 0x77777777
35 #nfmark = 0x77777777
36 #nfmark = 128000
37
38 # This is for 'nf2fac send' command
39 # key = A9DY72B5GDE4OGGI
40
File nf2fa.service changed (mode: 100644) (index 18063e8..5d0ed39)
1 1 [Unit] [Unit]
2 2 Description=nf2fa daemon Description=nf2fa daemon
3 After=network.target
4 3
5 4 [Service] [Service]
6 5 ExecStart=/usr/sbin/nf2fad ExecStart=/usr/sbin/nf2fad
File nf2fa.spec.in changed (mode: 100644) (index b238bd8..b1efbab)
... ... Source: http://kernel.embedromix.ro/us/nf2fa/%{name}-%{version}.tar.gz
8 8 URL: http://kernel.embedromix.ro/us/ URL: http://kernel.embedromix.ro/us/
9 9 BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot
10 10 BuildRequires: make, gcc, openssl-devel, libnetfilter_queue-devel BuildRequires: make, gcc, openssl-devel, libnetfilter_queue-devel
11 BuildRequires: libmnl-devel, qrencode-devel
12 Requires: openssl-libs, libnetfilter_queue, libmnl, qrencode-libs
11 BuildRequires: libmnl-devel, qrencode-devel, libcap-devel
12 Requires: openssl-libs, libnetfilter_queue, libmnl, qrencode-libs, libcap
13 13
14 14 %global _hardened_build 1 %global _hardened_build 1
15 15
 
... ... Open firewall from an IP using two-factor authentication (2fa).
21 21
22 22 %build %build
23 23 %configure %configure
24 make
24 make %{?_smp_mflags}
25 25
26 26 %install %install
27 27 rm -rf ${RPM_BUILD_ROOT} rm -rf ${RPM_BUILD_ROOT}
 
... ... make install DESTDIR=${RPM_BUILD_ROOT}
31 31 %clean %clean
32 32 rm -rf ${RPM_BUILD_ROOT} rm -rf ${RPM_BUILD_ROOT}
33 33
34 %post
35 %systemd_post @PRJ@.service
36
37 %preun
38 %systemd_preun @PRJ@.service
39
40 %postun
41 %systemd_postun_with_restart @PRJ@.service
42
34 43 %files %files
35 44 %attr (-,root,root) %attr (-,root,root)
36 45 %{_sbindir}/nf2fad %{_sbindir}/nf2fad
37 46 %{_bindir}/nf2fac %{_bindir}/nf2fac
38 %{_sysconfdir}/nf2fac.conf.sample
47 %{_sysconfdir}/@PRJ@.conf.sample
39 48 %config(noreplace) @ETC@/nf2fa.conf %config(noreplace) @ETC@/nf2fa.conf
49 %{_unitdir}/@PRJ@.service
40 50 %doc README Changelog %doc README Changelog
41 51
42 52 %changelog %changelog
File nf2fac.c changed (mode: 100644) (index e6d69e1..68ca4a1)
... ... int main(int argc, char *argv[])
127 127
128 128 n = connect(sock, (struct sockaddr *) &sa_un, slen); n = connect(sock, (struct sockaddr *) &sa_un, slen);
129 129 if (n == -1) { if (n == -1) {
130 fprintf(stderr, "Error: cannot connect: %s!\n", strerror(errno));
130 fprintf(stderr, "Error: cannot connect to nf2fa daemon: %s!\n",
131 strerror(errno));
131 132 return 1; return 1;
132 133 } }
133 134
134 135 n = send(sock, buf, i, 0); n = send(sock, buf, i, 0);
135 136 if (n == -1) { if (n == -1) {
136 fprintf(stderr, "Error: cannot send: %s!\n", strerror(errno));
137 fprintf(stderr, "Error: cannot send data to nf2fa daemon: %s!\n",
138 strerror(errno));
137 139 return 1; return 1;
138 140 } }
139 141
140 142 n = recv(sock, buf, sizeof(buf), 0); n = recv(sock, buf, sizeof(buf), 0);
141 143 if (n == -1) { if (n == -1) {
142 fprintf(stderr, "Error: cannot receive: %s!\n", strerror(errno));
144 fprintf(stderr, "Error: cannot receive data from nf2fa daemon: %s!\n",
145 strerror(errno));
143 146 return 1; return 1;
144 147 } }
145 148 if (n == 0) { if (n == 0) {
146 fprintf(stderr, "Error: server unexpectedly closed the connection.\n");
149 fprintf(stderr,
150 "Error: nf2fa daemon unexpectedly closed the connection.\n");
147 151 return 1; return 1;
148 152 } }
149 153 if (n < 2) { if (n < 2) {
150 fprintf(stderr, "Error: server sent partial data.\n");
154 fprintf(stderr, "Error: nf2fa daemon sent partial data.\n");
151 155 return 1; return 1;
152 156 } }
153 157
 
... ... int main(int argc, char *argv[])
199 203 k.key[len2] = '\0'; k.key[len2] = '\0';
200 204 i += len; n -= len; i += len; n -= len;
201 205
202 printf("Key is %s (id %03hhu). Scan the QR code or type the key.\n",
206 printf("Key is %s (id %03hhu)."
207 " Scan the QR code below or type the key.\n",
203 208 k.key, id); k.key, id);
204 209 snprintf(info, sizeof(info), "%s - id %03hhu", k.name, id); snprintf(info, sizeof(info), "%s - id %03hhu", k.name, id);
205 210 r = totp_text(k.key, info); r = totp_text(k.key, info);
File nf2fad.c changed (mode: 100644) (index cf7745b..4244d60)
31 31 #include <sys/capability.h> #include <sys/capability.h>
32 32 #include <grp.h> #include <grp.h>
33 33 #include <pwd.h> #include <pwd.h>
34 #include <ctype.h>
34 35 #include <arpa/inet.h> #include <arpa/inet.h>
35 36 #include <netinet/ip.h> #include <netinet/ip.h>
36 37 #include <netinet/ip6.h> #include <netinet/ip6.h>
 
45 46
46 47 #include <libnetfilter_queue/libnetfilter_queue.h> #include <libnetfilter_queue/libnetfilter_queue.h>
47 48
49 #include <linux/netfilter/nfnetlink_conntrack.h>
50
51
48 52 #include "nf2fa_common.h" #include "nf2fa_common.h"
49 53 #include "util.h" #include "util.h"
50 54 #include "conf.h" #include "conf.h"
 
... ... static struct nlmsghdr *nfq_hdr_put(unsigned char *buf, const int type,
92 96 } }
93 97
94 98 static int send_verdict(const int queue_num, const uint32_t id, static int send_verdict(const int queue_num, const uint32_t id,
95 const int verdict)
99 const int verdict, const unsigned int mark)
96 100 { {
97 101 unsigned char buf[MNL_SOCKET_BUFFER_SIZE]; unsigned char buf[MNL_SOCKET_BUFFER_SIZE];
98 102 struct nlmsghdr *nlh; struct nlmsghdr *nlh;
103 struct nlattr *nest;
99 104
100 105 nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num); nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num);
101 106 nfq_nlmsg_verdict_put(nlh, id, verdict); nfq_nlmsg_verdict_put(nlh, id, verdict);
107
108 nfq_nlmsg_verdict_put_mark(nlh, htonl(mark));
109
110 /* Set the connmark. First, start NFQA_CT section: */
111 nest = mnl_attr_nest_start(nlh, NFQA_CT);
112 if (!nest) {
113 xlog("%s: mnl_attr_nest_start: %s\n", __func__, strerror(errno));
114 return -1;
115 }
116
117 /* then, add the connmark attribute: */
118 mnl_attr_put_u32(nlh, CTA_MARK, htonl(mark));
119 /* TODO: it is not working because Fedora kernel, at least
120 * does not enable needed config option *_GLUE_CT */
121
122 /* more conntrack attributes, e.g. CTA_LABEL/ZONE, could be set here */
123
124 /* end conntrack section */
125 mnl_attr_nest_end(nlh, nest);
126
102 127 if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
103 xlog("mnl_socket_send send_verdict: %s\n", strerror(errno));
128 xlog("%s: mnl_socket_send: %s\n", __func__, strerror(errno));
104 129 return -1; return -1;
105 130 } }
106 131
 
... ... static int send_verdict(const int queue_num, const uint32_t id,
111 136 * Try to find the code inside the packet * Try to find the code inside the packet
112 137 * Returns 0 if not ok, else, the command * Returns 0 if not ok, else, the command
113 138 */ */
114 static int test_2fa(const unsigned char *data, const unsigned int data_len,
139 static int test_2fa_bin(const unsigned char *data, const unsigned int data_len,
115 140 const unsigned int now, const char *ip) const unsigned int now, const char *ip)
116 141 { {
117 142 unsigned short i, j, start, last_check = 0xFFFF; unsigned short i, j, start, last_check = 0xFFFF;
 
... ... static int test_2fa(const unsigned char *data, const unsigned int data_len,
122 147 char pin[7]; char pin[7];
123 148 int r; int r;
124 149
125 xlog("DEBUG: %s: %s\n", __func__, ip);
150 if (data_len == 0)
151 return 0;
152
153 xlog("DEBUG: %s: %s data_len=%u\n", __func__, ip, data_len);
126 154 dump(data, data_len); dump(data, data_len);
127 155
128 156 // Find first 0xAA // Find first 0xAA
 
... ... static int test_2fa(const unsigned char *data, const unsigned int data_len,
138 166 while (k) { while (k) {
139 167 unsigned char pass_len, needed; unsigned char pass_len, needed;
140 168
141 xlog("DEBUG: uid %u key %03hhu pass=%s\n",
142 q->uid, k->id, k->pass);
169 xlog("DEBUG: uid %u key %03hhu\n", q->uid, k->id);
143 170
144 171 pass_len = strlen(k->pass) / 2; pass_len = strlen(k->pass) / 2;
145 172 needed = 1 + pass_len + 3 + 1; // 0xaa + pass + pin + cmd needed = 1 + pass_len + 3 + 1; // 0xaa + pass + pin + cmd
 
... ... static int test_2fa(const unsigned char *data, const unsigned int data_len,
150 177 unsigned short off; unsigned short off;
151 178
152 179 if (i + needed > data_len) { if (i + needed > data_len) {
153 xlog("\tDEBUG: too little data: i(%u) + needed(%hhu) > data_len(%hu)\n",
154 i, needed, data_len);
180 //xlog("\tDEBUG: too little data: i(%u) + needed(%hhu) > data_len(%hu)\n",
181 // i, needed, data_len);
155 182 break; break;
156 183 } }
157 184
 
... ... static int test_2fa(const unsigned char *data, const unsigned int data_len,
160 187 continue; continue;
161 188 } }
162 189
163 xlog("\tDEBUG: i=%hu 0x%02hhx\n", i, data[i]);
164
165 // Optimization
190 // Optimization - skip checking same string
166 191 if ((last_check != 0xFFFF) if ((last_check != 0xFFFF)
167 192 && (memcmp(data + last_check, data + i, needed) == 0)) { && (memcmp(data + last_check, data + i, needed) == 0)) {
168 //xlog("\tDEBUG: same string\n");
169 193 i += needed; i += needed;
170 194 continue; continue;
171 195 } }
 
... ... static int test_2fa(const unsigned char *data, const unsigned int data_len,
207 231 switch (cmd) { switch (cmd) {
208 232 case 0x11: // open case 0x11: // open
209 233 snprintf(k->ip, sizeof(k->ip), "%s", ip); snprintf(k->ip, sizeof(k->ip), "%s", ip);
210 // TODO: maybe we shoul not allow access if we cannot save key? But it is saved in memory!
234 // TODO: maybe we should not allow access if we cannot save key? But it is saved in memory!
211 235 xlog("%s: uid %u: key %03hhu: authorized\n", xlog("%s: uid %u: key %03hhu: authorized\n",
212 236 ip, q->uid, k->id); ip, q->uid, k->id);
213 237 break; break;
 
... ... static int test_2fa(const unsigned char *data, const unsigned int data_len,
232 256 return 0; return 0;
233 257 } }
234 258
259 /*
260 * Binary version
261 */
262 static int test_2fa(const unsigned char *data, const unsigned int data_len,
263 const unsigned int now, const char *ip)
264 {
265 int r;
266 unsigned char buf[4096];
267 unsigned short i, j, max;
268
269 // This test is for ICMP packets
270 r = test_2fa_bin(data, data_len, now, ip);
271 if (r > 0)
272 return r;
273
274 // In UDP packets, we have 'aa' as 0x61 0x61.
275 // We try to find this string and test from there.
276 // We will not try packets odd aligned.
277
278 max = sizeof(buf);
279 if (data_len / 2 < max)
280 max = data_len / 2;
281
282 // Find first marker
283 for (i = 0; i < max; i += 2) {
284 if ((data[i] != 0x61) || (data[i + 1] != 0x61))
285 continue;
286
287 xlog("\tDEBUG: Found 0x61 0x61 marker at offset %hu\n", i);
288
289 // we found a start marker - let's see how much we can use
290 j = 0;
291 while (1) {
292 unsigned char c;
293
294 c = data[i];
295 //xlog("\tDEBUG: i=%hu j=%hu c=0x%02hhx\n", i, j, c);
296 if ((c >= '0') && (c <= '9')) {
297 if (i % 2 == 0) {
298 buf[j] = (c - '0') << 4;
299 } else {
300 buf[j] |= (c - '0');
301 j++;
302 }
303 i++;
304 continue;
305 }
306
307 c = toupper(c);
308 if ((c >= 'A') && (c <= 'F')) {
309 if (i % 2 == 0) {
310 buf[j] = (10 + (c - 'A')) << 4;
311 } else {
312 buf[j] |= 10 + (c - 'A');
313 j++;
314 }
315 i++;
316 continue;
317 }
318
319 break;
320 }
321
322 r = test_2fa_bin(buf, j, now, ip);
323 if (r > 0)
324 return r;
325
326 if (i % 2 != 0)
327 i++;
328 }
329
330 return 0;
331 }
332
235 333 /* /*
236 334 * Check if IP is in the list (1 if found) * Check if IP is in the list (1 if found)
237 335 * Returns 1 if ok * Returns 1 if ok
 
... ... static void add_ip(const unsigned int pos, const unsigned char *src,
303 401 /* /*
304 402 * Process packet * Process packet
305 403 */ */
306 static unsigned int process(const unsigned short eth,
307 const unsigned char *p, const unsigned int len)
404 static int process(const unsigned short eth,
405 const unsigned char *p, const unsigned int len, unsigned int *mark)
308 406 { {
309 407 struct iphdr *iph; struct iphdr *iph;
310 408 const unsigned char *data; const unsigned char *data;
 
... ... static unsigned int process(const unsigned short eth,
315 413 int r, af; int r, af;
316 414 char ip[40]; char ip[40];
317 415
416 *mark = 0;
417
318 418 switch (eth) { switch (eth) {
319 419 case ETH_P_IP: case ETH_P_IP:
320 420 iph = (struct iphdr *) p; iph = (struct iphdr *) p;
321 data = p + iph->ihl * 4 + 8;
421 data = p + iph->ihl * 4;
322 422 data_len = len - (data - p); data_len = len - (data - p);
323 423 memcpy(src, p + 12, 4); src_len = 4; memcpy(src, p + 12, 4); src_len = 4;
324 424 af = AF_INET; af = AF_INET;
325 425 break; break;
326 426
327 427 case ETH_P_IPV6: case ETH_P_IPV6:
328 data = p + 40 + 4;
428 data = p + 40;
329 429 data_len = len - (data - p); data_len = len - (data - p);
330 430 memcpy(src, p + 8, 16); src_len = 16; memcpy(src, p + 8, 16); src_len = 16;
331 431 af = AF_INET6; af = AF_INET6;
 
... ... static unsigned int process(const unsigned short eth,
334 434 default: return NF_DROP; default: return NF_DROP;
335 435 } }
336 436
337 //xlog("DEBUG: src: "); dump(src, src_len);
338 //xlog("DEBUG: packet: "); dump(data, data_len);
437 //xlog("\tDEBUG: src: "); dump(src, src_len);
438 //xlog("\tDEBUG: packet: "); dump(data, data_len);
339 439 inet_ntop(af, src, ip, sizeof(ip)); inet_ntop(af, src, ip, sizeof(ip));
340 //xlog("DEBUG: ip=%s\n", ip);
440 //xlog("\tDEBUG: ip=%s\n", ip);
341 441
342 442 gettimeofday(&now, NULL); gettimeofday(&now, NULL);
343 443
344 444 // Checking if the IP is already in the whitelist // Checking if the IP is already in the whitelist
345 445 r = check_ip(src, src_len, &pos, &now); r = check_ip(src, src_len, &pos, &now);
346 446 if (r == 1) { if (r == 1) {
347 //xlog("\t%s: ip is in the white list.\n", ip);
447 // IP is in the whilelist, but we cannot just accept it
448 // Because we may have a "close firewall" command.
348 449
349 450 r = test_2fa(data, data_len, now.tv_sec, ip); r = test_2fa(data, data_len, now.tv_sec, ip);
350 451 if (r == 0xcc) { if (r == 0xcc) {
 
... ... static unsigned int process(const unsigned short eth,
359 460 return NF_ACCEPT; return NF_ACCEPT;
360 461 } }
361 462
362 xlog("\t%s: invalid command (%d)\n", ip, r);
363 return NF_DROP;
463 xlog("\t%s: in the white list, allow it [mark 0x%08x]\n",
464 ip, nfmark);
465 *mark = nfmark;
466 return NF_REPEAT;
364 467 } }
365 468
366 469 r = brute_check(src, src_len, now.tv_sec); r = brute_check(src, src_len, now.tv_sec);
 
... ... static unsigned int process(const unsigned short eth,
369 472 return NF_DROP; return NF_DROP;
370 473 } }
371 474
372 //xlog("\t%s: ip is NOT in the white list.\n", ip);
475 xlog("\t%s: ip is NOT in the white list.\n", ip);
373 476 r = test_2fa(data, data_len, now.tv_sec, ip); r = test_2fa(data, data_len, now.tv_sec, ip);
374 477 if (r == 0x11) { if (r == 0x11) {
375 478 add_ip(pos, src, src_len, now.tv_sec); add_ip(pos, src, src_len, now.tv_sec);
 
... ... static int queue_cb(const struct nlmsghdr *nlh, void *data)
387 490 { {
388 491 struct nfqnl_msg_packet_hdr *ph = NULL; struct nfqnl_msg_packet_hdr *ph = NULL;
389 492 struct nlattr *attr[NFQA_MAX + 1] = {}; struct nlattr *attr[NFQA_MAX + 1] = {};
390 unsigned int id = 0, verdict;
493 unsigned int id = 0, mark;
391 494 struct nfgenmsg *nfg; struct nfgenmsg *nfg;
392 495 uint16_t plen; uint16_t plen;
393 496 unsigned char *payload; unsigned char *payload;
394 int ret;
497 int ret, verdict;
395 498
396 499 if (nfq_nlmsg_parse(nlh, attr) < 0) { if (nfq_nlmsg_parse(nlh, attr) < 0) {
397 500 xlog("Problems parsing netlink message!\n"); xlog("Problems parsing netlink message!\n");
 
... ... static int queue_cb(const struct nlmsghdr *nlh, void *data)
401 504 nfg = mnl_nlmsg_get_payload(nlh); nfg = mnl_nlmsg_get_payload(nlh);
402 505
403 506 if (attr[NFQA_PACKET_HDR] == NULL) { if (attr[NFQA_PACKET_HDR] == NULL) {
404 xlog("Packet metaheader not set!\n");
507 xlog("Packet header not set!\n");
405 508 return MNL_CB_ERROR; return MNL_CB_ERROR;
406 509 } }
407 510
 
... ... static int queue_cb(const struct nlmsghdr *nlh, void *data)
410 513
411 514 ph = mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); ph = mnl_attr_get_payload(attr[NFQA_PACKET_HDR]);
412 515 id = ntohl(ph->packet_id); id = ntohl(ph->packet_id);
413 xlog("packet received (id=%u hw=0x%04hx hook=%hhu, payload len %u"
414 " data=%p\n",
415 id, ntohs(ph->hw_protocol), ph->hook, plen, data);
516 xlog("packet received (id=%u hw=0x%04hx hook=%hhu, payload len %u\n",
517 id, ntohs(ph->hw_protocol), ph->hook, plen);
416 518
417 verdict = process(ntohs(ph->hw_protocol), payload, plen);
418 ret = send_verdict(ntohs(nfg->res_id), id, verdict);
519 verdict = process(ntohs(ph->hw_protocol), payload, plen, &mark);
520 ret = send_verdict(ntohs(nfg->res_id), id, verdict, mark);
419 521 if (ret == -1) if (ret == -1)
420 522 return MNL_CB_ERROR; return MNL_CB_ERROR;
421 523
 
... ... int main(int argc, char *argv[])
930 1032
931 1033 nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, queue); nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, queue);
932 1034 nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff); nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);
1035 /* TODO
1036 mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_CONNTRACK));
1037 mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_CONNTRACK));
1038 */
933 1039 if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
934 1040 perror("mnl_socket_send copy_packet"); perror("mnl_socket_send copy_packet");
935 1041 return 1; return 1;
 
... ... int main(int argc, char *argv[])
1015 1121 strerror(errno)); strerror(errno));
1016 1122 goto clean; goto clean;
1017 1123 } }
1018 xlog("Connection from "
1124 xlog("Connection from"
1019 1125 " pid=%ld, euid=%ld, egid=%ld\n", " pid=%ld, euid=%ld, egid=%ld\n",
1020 1126 (long) ucred.pid, (long) ucred.uid, (long) ucred.pid, (long) ucred.uid,
1021 1127 (long) ucred.gid); (long) ucred.gid);
File totp.c changed (mode: 100644) (index a3f5324..bd7c056)
... ... int totp_text(const char *secret, const char *name)
261 261 unsigned char i, j, v1, v2; unsigned char i, j, v1, v2;
262 262 unsigned short x, y, limit; unsigned short x, y, limit;
263 263 const char *sym[4] = { "█", "▄", "▀", " " }; const char *sym[4] = { "█", "▄", "▀", " " };
264 const unsigned char sym_len[4] = { 3, 3, 3, 1 };
264 265 char ename[128], ehost[400]; char ename[128], ehost[400];
265 266
266 267 gethostname(host, sizeof(host) - 1); gethostname(host, sizeof(host) - 1);
 
... ... int totp_text(const char *secret, const char *name)
271 272 snprintf(extra, sizeof(extra), "otpauth://totp/%s?secret=%s&issuer=%s", snprintf(extra, sizeof(extra), "otpauth://totp/%s?secret=%s&issuer=%s",
272 273 ename, secret, ehost); ename, secret, ehost);
273 274
274 qr = QRcode_encodeString(extra, 0, QR_ECLEVEL_M, QR_MODE_8, 1);
275 qr = QRcode_encodeString(extra, 0, QR_ECLEVEL_L, QR_MODE_8, 0 /*case*/);
275 276 if (!qr) { if (!qr) {
276 277 snprintf(error, sizeof(error), snprintf(error, sizeof(error),
277 278 "cannot generate qr code: %s", strerror(errno)); "cannot generate qr code: %s", strerror(errno));
 
... ... int totp_text(const char *secret, const char *name)
280 281
281 282 // top margin // top margin
282 283 for (j = 0; j < qr->width + 4; j++) for (j = 0; j < qr->width + 4; j++)
283 printf(sym[0]);
284 printf("\n");
284 write(STDOUT_FILENO, sym[0], sym_len[0]);
285 write(STDOUT_FILENO, "\n", 1);
285 286
286 287 limit = qr->width * qr->width; limit = qr->width * qr->width;
287 288 for (i = 0; i < qr->width; i += 2) { for (i = 0; i < qr->width; i += 2) {
288 printf("%s%s", sym[0], sym[0]);
289 write(STDOUT_FILENO, sym[0], sym_len[0]);
290 write(STDOUT_FILENO, sym[0], sym_len[0]);
291
289 292 for (j = 0; j < qr->width; j++) { for (j = 0; j < qr->width; j++) {
290 293 x = i * qr->width + j; x = i * qr->width + j;
291 294 y = (i + 1) * qr->width + j; y = (i + 1) * qr->width + j;
 
... ... int totp_text(const char *secret, const char *name)
294 297 v2 = qr->data[y] & 1; v2 = qr->data[y] & 1;
295 298 else else
296 299 v2 = 0; v2 = 0;
297 printf(sym[(v2 << 1) | v1]);
300 write(STDOUT_FILENO, sym[(v2 << 1) | v1], sym_len[(v2 << 1) | v1]);
298 301 } }
299 printf("%s%s\n", sym[0], sym[0]);
302
303 write(STDOUT_FILENO, sym[0], sym_len[0]);
304 write(STDOUT_FILENO, sym[0], sym_len[0]);
305 write(STDOUT_FILENO, "\n", 1);
300 306 } }
301 307
302 308 // bottom margin // bottom margin
303 309 for (j = 0; j < qr->width + 4; j++) for (j = 0; j < qr->width + 4; j++)
304 printf(sym[0]);
305 printf("\n");
310 write(STDOUT_FILENO, sym[0], sym_len[0]);
311 write(STDOUT_FILENO, "\n", 1);
306 312
307 313 QRcode_free(qr); QRcode_free(qr);
308 314
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/catalinux/nf2fa

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/catalinux/nf2fa

Clone this repository using git:
git clone git://git.rocketgit.com/user/catalinux/nf2fa

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main