Fedora kernel-2.6.17-1.2142_FC4 patched with stable patch-2.6.17.4-vs2.0.2-rc26.diff
[linux-2.6.git] / net / ipv4 / ip_sockglue.c
index 47012b9..12e0bf1 100644 (file)
 #include <linux/skbuff.h>
 #include <linux/ip.h>
 #include <linux/icmp.h>
+#include <linux/inetdevice.h>
 #include <linux/netdevice.h>
 #include <net/sock.h>
 #include <net/ip.h>
 #include <net/icmp.h>
-#include <net/tcp.h>
-#include <linux/tcp.h>
+#include <net/tcp_states.h>
 #include <linux/udp.h>
 #include <linux/igmp.h>
 #include <linux/netfilter.h>
@@ -50,6 +50,7 @@
 #define IP_CMSG_TOS            4
 #define IP_CMSG_RECVOPTS       8
 #define IP_CMSG_RETOPTS                16
+#define IP_CMSG_PASSSEC                32
 
 /*
  *     SOL_IP control messages.
@@ -109,6 +110,19 @@ static void ip_cmsg_recv_retopts(struct msghdr *msg, struct sk_buff *skb)
        put_cmsg(msg, SOL_IP, IP_RETOPTS, opt->optlen, opt->__data);
 }
 
+static void ip_cmsg_recv_security(struct msghdr *msg, struct sk_buff *skb)
+{
+       char *secdata;
+       u32 seclen;
+       int err;
+
+       err = security_socket_getpeersec_dgram(skb, &secdata, &seclen);
+       if (err)
+               return;
+
+       put_cmsg(msg, SOL_IP, SCM_SECURITY, seclen, secdata);
+}
+
 
 void ip_cmsg_recv(struct msghdr *msg, struct sk_buff *skb)
 {
@@ -138,6 +152,11 @@ void ip_cmsg_recv(struct msghdr *msg, struct sk_buff *skb)
 
        if (flags & 1)
                ip_cmsg_recv_retopts(msg, skb);
+       if ((flags>>=1) == 0)
+               return;
+
+       if (flags & 1)
+               ip_cmsg_recv_security(msg, skb);
 }
 
 int ip_cmsg_send(struct msghdr *msg, struct ipcm_cookie *ipc)
@@ -153,7 +172,7 @@ int ip_cmsg_send(struct msghdr *msg, struct ipcm_cookie *ipc)
                switch (cmsg->cmsg_type) {
                case IP_RETOPTS:
                        err = cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr));
-                       err = ip_options_get(&ipc->opt, CMSG_DATA(cmsg), err < 40 ? err : 40, 0);
+                       err = ip_options_get(&ipc->opt, CMSG_DATA(cmsg), err < 40 ? err : 40);
                        if (err)
                                return err;
                        break;
@@ -202,8 +221,7 @@ int ip_ra_control(struct sock *sk, unsigned char on, void (*destructor)(struct s
                if (ra->sk == sk) {
                        if (on) {
                                write_unlock_bh(&ip_ra_lock);
-                               if (new_ra)
-                                       kfree(new_ra);
+                               kfree(new_ra);
                                return -EADDRINUSE;
                        }
                        *rap = ra->next;
@@ -360,14 +378,14 @@ int ip_recv_error(struct sock *sk, struct msghdr *msg, int len)
        err = copied;
 
        /* Reset and regenerate socket error */
-       spin_lock_irq(&sk->sk_error_queue.lock);
+       spin_lock_bh(&sk->sk_error_queue.lock);
        sk->sk_err = 0;
        if ((skb2 = skb_peek(&sk->sk_error_queue)) != NULL) {
                sk->sk_err = SKB_EXT_ERR(skb2)->ee.ee_errno;
-               spin_unlock_irq(&sk->sk_error_queue.lock);
+               spin_unlock_bh(&sk->sk_error_queue.lock);
                sk->sk_error_report(sk);
        } else
-               spin_unlock_irq(&sk->sk_error_queue.lock);
+               spin_unlock_bh(&sk->sk_error_queue.lock);
 
 out_free_skb:  
        kfree_skb(skb);
@@ -381,20 +399,19 @@ out:
  *     an IP socket.
  */
 
-int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval, int optlen)
+static int do_ip_setsockopt(struct sock *sk, int level,
+               int optname, char __user *optval, int optlen)
 {
        struct inet_sock *inet = inet_sk(sk);
        int val=0,err;
 
-       if (level != SOL_IP)
-               return -ENOPROTOOPT;
-
        if (((1<<optname) & ((1<<IP_PKTINFO) | (1<<IP_RECVTTL) | 
                            (1<<IP_RECVOPTS) | (1<<IP_RECVTOS) | 
                            (1<<IP_RETOPTS) | (1<<IP_TOS) | 
                            (1<<IP_TTL) | (1<<IP_HDRINCL) | 
                            (1<<IP_MTU_DISCOVER) | (1<<IP_RECVERR) | 
-                           (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND))) || 
+                           (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND) |
+                           (1<<IP_PASSSEC))) ||
                                optname == IP_MULTICAST_TTL || 
                                optname == IP_MULTICAST_LOOP) { 
                if (optlen >= sizeof(int)) {
@@ -425,11 +442,11 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                        struct ip_options * opt = NULL;
                        if (optlen > 40 || optlen < 0)
                                goto e_inval;
-                       err = ip_options_get(&opt, optval, optlen, 1);
+                       err = ip_options_get_from_user(&opt, optval, optlen);
                        if (err)
                                break;
-                       if (sk->sk_type == SOCK_STREAM) {
-                               struct tcp_sock *tp = tcp_sk(sk);
+                       if (inet->is_icsk) {
+                               struct inet_connection_sock *icsk = inet_csk(sk);
 #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
                                if (sk->sk_family == PF_INET ||
                                    (!((1 << sk->sk_state) &
@@ -437,17 +454,16 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                                     inet->daddr != LOOPBACK4_IPV6)) {
 #endif
                                        if (inet->opt)
-                                               tp->ext_header_len -= inet->opt->optlen;
+                                               icsk->icsk_ext_hdr_len -= inet->opt->optlen;
                                        if (opt)
-                                               tp->ext_header_len += opt->optlen;
-                                       tcp_sync_mss(sk, tp->pmtu_cookie);
+                                               icsk->icsk_ext_hdr_len += opt->optlen;
+                                       icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);
 #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
                                }
 #endif
                        }
                        opt = xchg(&inet->opt, opt);
-                       if (opt)
-                               kfree(opt);
+                       kfree(opt);
                        break;
                }
                case IP_PKTINFO:
@@ -480,6 +496,12 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                        else
                                inet->cmsg_flags &= ~IP_CMSG_RETOPTS;
                        break;
+               case IP_PASSSEC:
+                       if (val)
+                               inet->cmsg_flags |= IP_CMSG_PASSSEC;
+                       else
+                               inet->cmsg_flags &= ~IP_CMSG_PASSSEC;
+                       break;
                case IP_TOS:    /* This sets both TOS and Precedence */
                        if (sk->sk_type == SOCK_STREAM) {
                                val &= ~3;
@@ -614,7 +636,6 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                }
                case IP_MSFILTER:
                {
-                       extern int sysctl_optmem_max;
                        extern int sysctl_igmp_max_msf;
                        struct ip_msfilter *msf;
 
@@ -624,7 +645,7 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                                err = -ENOBUFS;
                                break;
                        }
-                       msf = (struct ip_msfilter *)kmalloc(optlen, GFP_KERNEL);
+                       msf = kmalloc(optlen, GFP_KERNEL);
                        if (msf == 0) {
                                err = -ENOBUFS;
                                break;
@@ -677,11 +698,11 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                                mreq.imr_address.s_addr = mreqs.imr_interface;
                                mreq.imr_ifindex = 0;
                                err = ip_mc_join_group(sk, &mreq);
-                               if (err)
+                               if (err && err != -EADDRINUSE)
                                        break;
                                omode = MCAST_INCLUDE;
                                add = 1;
-                       } else /*IP_DROP_SOURCE_MEMBERSHIP */ {
+                       } else /* IP_DROP_SOURCE_MEMBERSHIP */ {
                                omode = MCAST_INCLUDE;
                                add = 0;
                        }
@@ -754,7 +775,7 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                                mreq.imr_address.s_addr = 0;
                                mreq.imr_ifindex = greqs.gsr_interface;
                                err = ip_mc_join_group(sk, &mreq);
-                               if (err)
+                               if (err && err != -EADDRINUSE)
                                        break;
                                greqs.gsr_interface = mreq.imr_ifindex;
                                omode = MCAST_INCLUDE;
@@ -769,7 +790,6 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                }
                case MCAST_MSFILTER:
                {
-                       extern int sysctl_optmem_max;
                        extern int sysctl_igmp_max_msf;
                        struct sockaddr_in *psin;
                        struct ip_msfilter *msf = NULL;
@@ -782,7 +802,7 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                                err = -ENOBUFS;
                                break;
                        }
-                       gsf = (struct group_filter *)kmalloc(optlen,GFP_KERNEL);
+                       gsf = kmalloc(optlen,GFP_KERNEL);
                        if (gsf == 0) {
                                err = -ENOBUFS;
                                break;
@@ -802,7 +822,7 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
                                goto mc_msf_out;
                        }
                        msize = IP_MSFILTER_SIZE(gsf->gf_numsrc);
-                       msf = (struct ip_msfilter *)kmalloc(msize,GFP_KERNEL);
+                       msf = kmalloc(msize,GFP_KERNEL);
                        if (msf == 0) {
                                err = -ENOBUFS;
                                goto mc_msf_out;
@@ -830,10 +850,8 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
 
                        err = ip_mc_msfilter(sk, msf, ifindex);
 mc_msf_out:
-                       if (msf)
-                               kfree(msf);
-                       if (gsf)
-                               kfree(gsf);
+                       kfree(msf);
+                       kfree(gsf);
                        break;
                }
                case IP_ROUTER_ALERT:   
@@ -848,16 +866,14 @@ mc_msf_out:
  
                case IP_IPSEC_POLICY:
                case IP_XFRM_POLICY:
+                       err = -EPERM;
+                       if (!capable(CAP_NET_ADMIN))
+                               break;
                        err = xfrm_user_policy(sk, optname, optval, optlen);
                        break;
 
                default:
-#ifdef CONFIG_NETFILTER
-                       err = nf_setsockopt(sk, PF_INET, optname, optval, 
-                                           optlen);
-#else
                        err = -ENOPROTOOPT;
-#endif
                        break;
        }
        release_sock(sk);
@@ -868,12 +884,68 @@ e_inval:
        return -EINVAL;
 }
 
+int ip_setsockopt(struct sock *sk, int level,
+               int optname, char __user *optval, int optlen)
+{
+       int err;
+
+       if (level != SOL_IP)
+               return -ENOPROTOOPT;
+
+       err = do_ip_setsockopt(sk, level, optname, optval, optlen);
+#ifdef CONFIG_NETFILTER
+       /* we need to exclude all possible ENOPROTOOPTs except default case */
+       if (err == -ENOPROTOOPT && optname != IP_HDRINCL &&
+               optname != IP_IPSEC_POLICY && optname != IP_XFRM_POLICY
+#ifdef CONFIG_IP_MROUTE
+               && (optname < MRT_BASE || optname > (MRT_BASE + 10))
+#endif
+          ) {
+               lock_sock(sk);
+               err = nf_setsockopt(sk, PF_INET, optname, optval, optlen);
+               release_sock(sk);
+       }
+#endif
+       return err;
+}
+
+#ifdef CONFIG_COMPAT
+int compat_ip_setsockopt(struct sock *sk, int level, int optname,
+                        char __user *optval, int optlen)
+{
+       int err;
+
+       if (level != SOL_IP)
+               return -ENOPROTOOPT;
+
+       err = do_ip_setsockopt(sk, level, optname, optval, optlen);
+#ifdef CONFIG_NETFILTER
+       /* we need to exclude all possible ENOPROTOOPTs except default case */
+       if (err == -ENOPROTOOPT && optname != IP_HDRINCL &&
+           optname != IP_IPSEC_POLICY && optname != IP_XFRM_POLICY
+#ifdef CONFIG_IP_MROUTE
+           && (optname < MRT_BASE || optname > (MRT_BASE + 10))
+#endif
+          ) {
+               lock_sock(sk);
+               err = compat_nf_setsockopt(sk, PF_INET, optname,
+                                          optval, optlen);
+               release_sock(sk);
+       }
+#endif
+       return err;
+}
+
+EXPORT_SYMBOL(compat_ip_setsockopt);
+#endif
+
 /*
  *     Get the options. Note for future reference. The GET of IP options gets the
  *     _received_ ones. The set sets the _sent_ ones.
  */
 
-int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval, int __user *optlen)
+static int do_ip_getsockopt(struct sock *sk, int level, int optname,
+               char __user *optval, int __user *optlen)
 {
        struct inet_sock *inet = inet_sk(sk);
        int val;
@@ -935,6 +1007,9 @@ int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
                case IP_RETOPTS:
                        val = (inet->cmsg_flags & IP_CMSG_RETOPTS) != 0;
                        break;
+               case IP_PASSSEC:
+                       val = (inet->cmsg_flags & IP_CMSG_PASSSEC) != 0;
+                       break;
                case IP_TOS:
                        val = inet->tos;
                        break;
@@ -1054,17 +1129,8 @@ int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
                        val = inet->freebind; 
                        break; 
                default:
-#ifdef CONFIG_NETFILTER
-                       val = nf_getsockopt(sk, PF_INET, optname, optval, 
-                                           &len);
-                       release_sock(sk);
-                       if (val >= 0)
-                               val = put_user(len, optlen);
-                       return val;
-#else
                        release_sock(sk);
                        return -ENOPROTOOPT;
-#endif
        }
        release_sock(sk);
        
@@ -1085,9 +1151,68 @@ int ip_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
        return 0;
 }
 
+int ip_getsockopt(struct sock *sk, int level,
+               int optname, char __user *optval, int __user *optlen)
+{
+       int err;
+
+       err = do_ip_getsockopt(sk, level, optname, optval, optlen);
+#ifdef CONFIG_NETFILTER
+       /* we need to exclude all possible ENOPROTOOPTs except default case */
+       if (err == -ENOPROTOOPT && optname != IP_PKTOPTIONS
+#ifdef CONFIG_IP_MROUTE
+               && (optname < MRT_BASE || optname > MRT_BASE+10)
+#endif
+          ) {
+               int len;
+
+               if(get_user(len,optlen))
+                       return -EFAULT;
+
+               lock_sock(sk);
+               err = nf_getsockopt(sk, PF_INET, optname, optval,
+                               &len);
+               release_sock(sk);
+               if (err >= 0)
+                       err = put_user(len, optlen);
+               return err;
+       }
+#endif
+       return err;
+}
+
+#ifdef CONFIG_COMPAT
+int compat_ip_getsockopt(struct sock *sk, int level, int optname,
+                        char __user *optval, int __user *optlen)
+{
+       int err = do_ip_getsockopt(sk, level, optname, optval, optlen);
+#ifdef CONFIG_NETFILTER
+       /* we need to exclude all possible ENOPROTOOPTs except default case */
+       if (err == -ENOPROTOOPT && optname != IP_PKTOPTIONS
+#ifdef CONFIG_IP_MROUTE
+           && (optname < MRT_BASE || optname > MRT_BASE+10)
+#endif
+          ) {
+               int len;
+
+               if (get_user(len, optlen))
+                       return -EFAULT;
+
+               lock_sock(sk);
+               err = compat_nf_getsockopt(sk, PF_INET, optname, optval, &len);
+               release_sock(sk);
+               if (err >= 0)
+                       err = put_user(len, optlen);
+               return err;
+       }
+#endif
+       return err;
+}
+
+EXPORT_SYMBOL(compat_ip_getsockopt);
+#endif
+
 EXPORT_SYMBOL(ip_cmsg_recv);
 
-#ifdef CONFIG_IP_SCTP_MODULE
 EXPORT_SYMBOL(ip_getsockopt);
 EXPORT_SYMBOL(ip_setsockopt);
-#endif