1 /*  PKNF
  2     a portknocking Netfilter LKM for linux kernel >= 2.6.25
  3     Copyright (C) 2008 ithilgore - ithilgore.ryu.L@gmail.com
  4
  5     This program is free software: you can redistribute it and/or modify
  6     it under the terms of the GNU General Public License as published by
  7     the Free Software Foundation, either version 3 of the License, or
  8     (at your option) any later version.
  9
 10     This program is distributed in the hope that it will be useful,
 11     but WITHOUT ANY WARRANTY; without even the implied warranty of
 12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13     GNU General Public License for more details.
 14
 15     You should have received a copy of the GNU General Public License
 16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 17 */
 18
 19 /* usage: insmod pknf.ko
 20  * OR     insmod pknf.ko port_trigs=19,26,... max_time=2
 21  * port_trigs list has to be less or equal than MAX_PORTS
 22  * max_time is defined in seconds - see source
 23  */
 24
 25
 26 #include <linux/init.h>
 27 #include <linux/module.h>
 28 #include <linux/kernel.h>
 29 #include <linux/sched.h>
 30 #include <linux/skbuff.h>
 31 #include <linux/ip.h>
 32 #include <linux/tcp.h>
 33 #include <linux/netfilter_ipv4.h>
 34 #include <linux/netfilter.h>
 35
 36 MODULE_AUTHOR("ithilgore - ithilgore.ryu.L@gmail.com");
 37 MODULE_DESCRIPTION("port knocking netfilter hook");
 38 MODULE_LICENSE("GPL");
 39 MODULE_VERSION("0.7");
 40
 41 #define MAX_PORTS 5
 42 #define MAX_TIME 5
 43 /* 
 44  * ports: number of ports - overriden by user parameter
 45  * port_trigs: array containing port sequence
 46  * port_flags: array containing flags for so far triggered ports
 47  * timestamp: time when first knock hit successfully
 48  * max_time: maximum time (in seconds) between port knocks
 49  */
 50 static unsigned int ports = MAX_PORTS;
 51 static unsigned int port_flags[MAX_PORTS];              
 52 static unsigned short port_trigs[MAX_PORTS] = { 25, 23, 21, 2340, 9047 };
 53 static unsigned long timestamp = 0;
 54 static unsigned long max_time = MAX_TIME;      
 55
 56 /* hook register struct */
 57 static struct nf_hook_ops nfho;
 58
 59 /*** functions ***/
 60 static inline void clean_flags(void);
 61 static int test_port(unsigned int portnum, unsigned short dport);
 62 static int exec_sshd(void);
 63 static inline int time_expired(void);
 64 #ifdef DEBUG
 65 static inline void print_flags(void);   /* debugging routine */
 66 #endif
 67
 68 /*** parameters ***/
 69 module_param_array(port_trigs, ushort, &ports, 0);
 70 module_param(max_time, ulong, 0);
 71
 72
 73
 74 /* returns:
 75  * - 1: time expired - caller must reset port flags
 76  * - 0: hasn't expired yet
 77  */
 78 static inline int 
 79 time_expired(void)
 80 {
 81         unsigned long current_time = jiffies;
 82 #ifdef DEBUG
 83         printk("%ld %ld \n", current_time, timestamp + max_time);
 84 #endif
 85
 86         if (time_after(current_time, timestamp + max_time)) {
 87 #ifdef DEBUG
 88                 printk("timer expired !!!\n");
 89 #endif
 90                 return 1;
 91         }
 92         else 
 93                 return 0;
 94 }
 95
 96 /* execute sshd for userspace */
 97 static int
 98 exec_sshd(void)
 99 {
100         static char *path = "/usr/sbin/sshd";
101         static char *argv[] = { "/usr/sbin/sshd", NULL };
102         static char *envp[] = {
103         "HOME=/",
104         "PATH=/sbin/:/usr/sbin/:/bin/:/usr/bin/:/usr/local\
105         /bin/:/usr/local/sbin",
106         NULL };
107         int ret;
108
109         ret = call_usermodehelper(path, argv, envp, 1);
110 #ifdef DEBUG
111         if (ret)
112                 printk("failed to exec sshd\n");
113 #endif
114         return ret;
115 }
116
117 #ifdef DEBUG
118 /* debugging function */
119 static inline void 
120 print_flags(void)
121 {
122         int i;
123         printk("port_flags:");
124         for (i = 0; i < ports; i++) {
125                 printk(" %d", port_flags[i]);
126         }
127         printk("\n");
128         
129 }
130 #endif
131
132 /* cleanup flags after timer expires */
133 static inline void 
134 clean_flags(void)
135 {
136         memset(port_flags, 0, sizeof(port_flags));
137 }
138
139
140 /* test if particular port is the correct one
141  * in the sequence
142  * returns:
143  *      0    --- don't do anything with this packet
144  *      1    --- full port knocking sequence SUCCESSFUL
145  *     -1    --- tell caller to drop the packet -> NF_DROP
146  */
147 static int
148 test_port(unsigned int portnum, unsigned short dport)
149 {
150         unsigned int i;
151         /* test if a port in the pksequence is in the packet and if it is,
152          * check if all the previous ports have been already triggered
153          */
154         if (dport == htons(port_trigs[portnum])) {
155                 if (!port_flags[portnum]) {
156                         for (i = 0; i < portnum; i++) {
157                                 if (!port_flags[portnum - i - 1]) {
158                                         clean_flags();
159                                 #ifdef DEBUG
160                                         print_flags();
161                                 #endif
162                                         return -1;
163                                 }
164                         }
165                         port_flags[portnum] = 1;
166                         timestamp = jiffies;
167                         //printk("port %d triggered\n", portnum);
168                 }
169                 else {
170                         clean_flags();
171                 }
172                 /* if you reach here then port knocking sequence is
173                  * progressing so far */
174         #ifdef DEBUG
175                 print_flags();
176         #endif
177                 if (portnum == ports - 1) /* final success */
178                         return 1;
179
180                 return -1;
181         }
182
183         return 0;
184 }
185
186
187 /* netfilter hook function */
188 static unsigned int 
189 hook_func(
190         unsigned int hooknum,
191         struct sk_buff *skb,
192         const struct net_device *in,
193         const struct net_device *out,
194         int (*okfn)(struct sk_buff *))
195 {
196         struct iphdr *iph;
197         struct tcphdr *tcph;
198         unsigned int i;
199         int test_ret;
200
201         iph = ip_hdr(skb);              /* 2.6.25 quirks - no more
202                                            nh,h,mac unions in skbuff */
203
204         //printk("saddr %x : %x \n", iph->saddr, (iph->saddr & 0x000000ff));
205
206         if (time_expired())
207                 clean_flags();
208
209         if (iph->protocol != IPPROTO_TCP) {
210                 //printk("protocol not tcp\n");
211                 return NF_ACCEPT;
212         }
213
214         /* normally to get the tcp header we would need
215          * to use skb_header_pointer() knowing the offset of skb->data
216          * where the tcp header actually begins - however I don't know
217          * at the moment a way to get this value
218          */
219
220         tcph = (struct tcphdr *)(skb->data + (iph->ihl * 4));
221
222         if (!tcph) {
223                 //printk("tcp header zero\n");
224                 return NF_ACCEPT;
225         }
226         //printk("dport: %x \n", tcph->dest);
227
228         /* test the destination port of the packet received for a port
229          * in the port knocking sequence as defined in port_trigs[]
230          */
231         for (i = 0; i < ports; i++) {
232                 test_ret = test_port(i, tcph->dest);
233                 if (test_ret < 0) {
234                         return NF_DROP;
235                 }
236                 else if (test_ret == 1) {
237                         //printk("PORTKNOCK!!\n");
238                         exec_sshd();
239                         clean_flags();
240                 }
241         }
242
243         clean_flags();
244 #ifdef DEBUG
245         print_flags();
246 #endif
247
248         return NF_ACCEPT;
249 }
250
251
252 /* Initialization routine */
253 static int __init init(void)
254 {
255         nfho.hook     = hook_func;
256         nfho.hooknum  = NF_INET_PRE_ROUTING;
257         nfho.pf       = PF_INET;
258         nfho.priority = NF_IP_PRI_FIRST;
259
260         nf_register_hook(&nfho);
261
262         clean_flags();
263         max_time *= HZ;         /* workaround for parameter issue */
264
265         return 0;
266 }
267
268 /* Cleanup routine */
269 static void __exit cleanup(void)
270 {
271         nf_unregister_hook(&nfho);
272 }
273
274 module_init(init);
275 module_exit(cleanup);