Merge branch 'for-linus' of git://git.o-hand.com/linux-rpurdie-leds
[sfrench/cifs-2.6.git] / net / netfilter / nf_sockopt.c
1 #include <linux/kernel.h>
2 #include <linux/init.h>
3 #include <linux/module.h>
4 #include <linux/skbuff.h>
5 #include <linux/netfilter.h>
6 #include <linux/mutex.h>
7 #include <net/sock.h>
8
9 #include "nf_internals.h"
10
11 /* Sockopts only registered and called from user context, so
12    net locking would be overkill.  Also, [gs]etsockopt calls may
13    sleep. */
14 static DEFINE_MUTEX(nf_sockopt_mutex);
15 static LIST_HEAD(nf_sockopts);
16
17 /* Do exclusive ranges overlap? */
18 static inline int overlap(int min1, int max1, int min2, int max2)
19 {
20         return max1 > min2 && min1 < max2;
21 }
22
23 /* Functions to register sockopt ranges (exclusive). */
24 int nf_register_sockopt(struct nf_sockopt_ops *reg)
25 {
26         struct list_head *i;
27         int ret = 0;
28
29         if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
30                 return -EINTR;
31
32         list_for_each(i, &nf_sockopts) {
33                 struct nf_sockopt_ops *ops = (struct nf_sockopt_ops *)i;
34                 if (ops->pf == reg->pf
35                     && (overlap(ops->set_optmin, ops->set_optmax,
36                                 reg->set_optmin, reg->set_optmax)
37                         || overlap(ops->get_optmin, ops->get_optmax,
38                                    reg->get_optmin, reg->get_optmax))) {
39                         NFDEBUG("nf_sock overlap: %u-%u/%u-%u v %u-%u/%u-%u\n",
40                                 ops->set_optmin, ops->set_optmax,
41                                 ops->get_optmin, ops->get_optmax,
42                                 reg->set_optmin, reg->set_optmax,
43                                 reg->get_optmin, reg->get_optmax);
44                         ret = -EBUSY;
45                         goto out;
46                 }
47         }
48
49         list_add(&reg->list, &nf_sockopts);
50 out:
51         mutex_unlock(&nf_sockopt_mutex);
52         return ret;
53 }
54 EXPORT_SYMBOL(nf_register_sockopt);
55
56 void nf_unregister_sockopt(struct nf_sockopt_ops *reg)
57 {
58         mutex_lock(&nf_sockopt_mutex);
59         list_del(&reg->list);
60         mutex_unlock(&nf_sockopt_mutex);
61 }
62 EXPORT_SYMBOL(nf_unregister_sockopt);
63
64 /* Call get/setsockopt() */
65 static int nf_sockopt(struct sock *sk, int pf, int val,
66                       char __user *opt, int *len, int get)
67 {
68         struct list_head *i;
69         struct nf_sockopt_ops *ops;
70         int ret;
71
72         if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
73                 return -EINTR;
74
75         list_for_each(i, &nf_sockopts) {
76                 ops = (struct nf_sockopt_ops *)i;
77                 if (ops->pf == pf) {
78                         if (!try_module_get(ops->owner))
79                                 goto out_nosup;
80                         if (get) {
81                                 if (val >= ops->get_optmin
82                                     && val < ops->get_optmax) {
83                                         mutex_unlock(&nf_sockopt_mutex);
84                                         ret = ops->get(sk, val, opt, len);
85                                         goto out;
86                                 }
87                         } else {
88                                 if (val >= ops->set_optmin
89                                     && val < ops->set_optmax) {
90                                         mutex_unlock(&nf_sockopt_mutex);
91                                         ret = ops->set(sk, val, opt, *len);
92                                         goto out;
93                                 }
94                         }
95                         module_put(ops->owner);
96                 }
97         }
98  out_nosup:
99         mutex_unlock(&nf_sockopt_mutex);
100         return -ENOPROTOOPT;
101
102  out:
103         module_put(ops->owner);
104         return ret;
105 }
106
107 int nf_setsockopt(struct sock *sk, int pf, int val, char __user *opt,
108                   int len)
109 {
110         return nf_sockopt(sk, pf, val, opt, &len, 0);
111 }
112 EXPORT_SYMBOL(nf_setsockopt);
113
114 int nf_getsockopt(struct sock *sk, int pf, int val, char __user *opt, int *len)
115 {
116         return nf_sockopt(sk, pf, val, opt, len, 1);
117 }
118 EXPORT_SYMBOL(nf_getsockopt);
119
120 #ifdef CONFIG_COMPAT
121 static int compat_nf_sockopt(struct sock *sk, int pf, int val,
122                              char __user *opt, int *len, int get)
123 {
124         struct list_head *i;
125         struct nf_sockopt_ops *ops;
126         int ret;
127
128         if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
129                 return -EINTR;
130
131         list_for_each(i, &nf_sockopts) {
132                 ops = (struct nf_sockopt_ops *)i;
133                 if (ops->pf == pf) {
134                         if (!try_module_get(ops->owner))
135                                 goto out_nosup;
136
137                         if (get) {
138                                 if (val >= ops->get_optmin
139                                     && val < ops->get_optmax) {
140                                         mutex_unlock(&nf_sockopt_mutex);
141                                         if (ops->compat_get)
142                                                 ret = ops->compat_get(sk,
143                                                         val, opt, len);
144                                         else
145                                                 ret = ops->get(sk,
146                                                         val, opt, len);
147                                         goto out;
148                                 }
149                         } else {
150                                 if (val >= ops->set_optmin
151                                     && val < ops->set_optmax) {
152                                         mutex_unlock(&nf_sockopt_mutex);
153                                         if (ops->compat_set)
154                                                 ret = ops->compat_set(sk,
155                                                         val, opt, *len);
156                                         else
157                                                 ret = ops->set(sk,
158                                                         val, opt, *len);
159                                         goto out;
160                                 }
161                         }
162                         module_put(ops->owner);
163                 }
164         }
165  out_nosup:
166         mutex_unlock(&nf_sockopt_mutex);
167         return -ENOPROTOOPT;
168
169  out:
170         module_put(ops->owner);
171         return ret;
172 }
173
174 int compat_nf_setsockopt(struct sock *sk, int pf,
175                 int val, char __user *opt, int len)
176 {
177         return compat_nf_sockopt(sk, pf, val, opt, &len, 0);
178 }
179 EXPORT_SYMBOL(compat_nf_setsockopt);
180
181 int compat_nf_getsockopt(struct sock *sk, int pf,
182                 int val, char __user *opt, int *len)
183 {
184         return compat_nf_sockopt(sk, pf, val, opt, len, 1);
185 }
186 EXPORT_SYMBOL(compat_nf_getsockopt);
187 #endif