diff options
Diffstat (limited to 'drivers/net/pptp.c')
| -rw-r--r-- | drivers/net/pptp.c | 726 | 
1 files changed, 726 insertions, 0 deletions
diff --git a/drivers/net/pptp.c b/drivers/net/pptp.c new file mode 100644 index 00000000000..ccbc91326bf --- /dev/null +++ b/drivers/net/pptp.c @@ -0,0 +1,726 @@ +/* + *  Point-to-Point Tunneling Protocol for Linux + * + *	Authors: Dmitry Kozlov <xeb@mail.ru> + * + *	This program is free software; you can redistribute it and/or + *	modify it under the terms of the GNU General Public License + *	as published by the Free Software Foundation; either version + *	2 of the License, or (at your option) any later version. + * + */ + +#include <linux/string.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/netdevice.h> +#include <linux/net.h> +#include <linux/skbuff.h> +#include <linux/vmalloc.h> +#include <linux/init.h> +#include <linux/ppp_channel.h> +#include <linux/ppp_defs.h> +#include <linux/if_pppox.h> +#include <linux/if_ppp.h> +#include <linux/notifier.h> +#include <linux/file.h> +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/netfilter.h> +#include <linux/netfilter_ipv4.h> +#include <linux/version.h> +#include <linux/rcupdate.h> +#include <linux/spinlock.h> + +#include <net/sock.h> +#include <net/protocol.h> +#include <net/ip.h> +#include <net/icmp.h> +#include <net/route.h> +#include <net/gre.h> + +#include <linux/uaccess.h> + +#define PPTP_DRIVER_VERSION "0.8.5" + +#define MAX_CALLID 65535 + +static DECLARE_BITMAP(callid_bitmap, MAX_CALLID + 1); +static struct pppox_sock **callid_sock; + +static DEFINE_SPINLOCK(chan_lock); + +static struct proto pptp_sk_proto __read_mostly; +static const struct ppp_channel_ops pptp_chan_ops; +static const struct proto_ops pptp_ops; + +#define PPP_LCP_ECHOREQ 0x09 +#define PPP_LCP_ECHOREP 0x0A +#define SC_RCV_BITS	(SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP) + +#define MISSING_WINDOW 20 +#define WRAPPED(curseq, lastseq)\ +	((((curseq) & 0xffffff00) == 0) &&\ +	(((lastseq) & 0xffffff00) == 0xffffff00)) + +#define PPTP_GRE_PROTO  0x880B +#define PPTP_GRE_VER    0x1 + +#define PPTP_GRE_FLAG_C	0x80 +#define PPTP_GRE_FLAG_R	0x40 +#define PPTP_GRE_FLAG_K	0x20 +#define PPTP_GRE_FLAG_S	0x10 +#define PPTP_GRE_FLAG_A	0x80 + +#define PPTP_GRE_IS_C(f) ((f)&PPTP_GRE_FLAG_C) +#define PPTP_GRE_IS_R(f) ((f)&PPTP_GRE_FLAG_R) +#define PPTP_GRE_IS_K(f) ((f)&PPTP_GRE_FLAG_K) +#define PPTP_GRE_IS_S(f) ((f)&PPTP_GRE_FLAG_S) +#define PPTP_GRE_IS_A(f) ((f)&PPTP_GRE_FLAG_A) + +#define PPTP_HEADER_OVERHEAD (2+sizeof(struct pptp_gre_header)) +struct pptp_gre_header { +	u8  flags; +	u8  ver; +	u16 protocol; +	u16 payload_len; +	u16 call_id; +	u32 seq; +	u32 ack; +} __packed; + +static struct pppox_sock *lookup_chan(u16 call_id, __be32 s_addr) +{ +	struct pppox_sock *sock; +	struct pptp_opt *opt; + +	rcu_read_lock(); +	sock = rcu_dereference(callid_sock[call_id]); +	if (sock) { +		opt = &sock->proto.pptp; +		if (opt->dst_addr.sin_addr.s_addr != s_addr) +			sock = NULL; +		else +			sock_hold(sk_pppox(sock)); +	} +	rcu_read_unlock(); + +	return sock; +} + +static int lookup_chan_dst(u16 call_id, __be32 d_addr) +{ +	struct pppox_sock *sock; +	struct pptp_opt *opt; +	int i; + +	rcu_read_lock(); +	for (i = find_next_bit(callid_bitmap, MAX_CALLID, 1); i < MAX_CALLID; +	     i = find_next_bit(callid_bitmap, MAX_CALLID, i + 1)) { +		sock = rcu_dereference(callid_sock[i]); +		if (!sock) +			continue; +		opt = &sock->proto.pptp; +		if (opt->dst_addr.call_id == call_id && +			  opt->dst_addr.sin_addr.s_addr == d_addr) +			break; +	} +	rcu_read_unlock(); + +	return i < MAX_CALLID; +} + +static int add_chan(struct pppox_sock *sock) +{ +	static int call_id; + +	spin_lock(&chan_lock); +	if (!sock->proto.pptp.src_addr.call_id)	{ +		call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, call_id + 1); +		if (call_id == MAX_CALLID) { +			call_id = find_next_zero_bit(callid_bitmap, MAX_CALLID, 1); +			if (call_id == MAX_CALLID) +				goto out_err; +		} +		sock->proto.pptp.src_addr.call_id = call_id; +	} else if (test_bit(sock->proto.pptp.src_addr.call_id, callid_bitmap)) +		goto out_err; + +	set_bit(sock->proto.pptp.src_addr.call_id, callid_bitmap); +	rcu_assign_pointer(callid_sock[sock->proto.pptp.src_addr.call_id], sock); +	spin_unlock(&chan_lock); + +	return 0; + +out_err: +	spin_unlock(&chan_lock); +	return -1; +} + +static void del_chan(struct pppox_sock *sock) +{ +	spin_lock(&chan_lock); +	clear_bit(sock->proto.pptp.src_addr.call_id, callid_bitmap); +	rcu_assign_pointer(callid_sock[sock->proto.pptp.src_addr.call_id], NULL); +	spin_unlock(&chan_lock); +	synchronize_rcu(); +} + +static int pptp_xmit(struct ppp_channel *chan, struct sk_buff *skb) +{ +	struct sock *sk = (struct sock *) chan->private; +	struct pppox_sock *po = pppox_sk(sk); +	struct pptp_opt *opt = &po->proto.pptp; +	struct pptp_gre_header *hdr; +	unsigned int header_len = sizeof(*hdr); +	int err = 0; +	int islcp; +	int len; +	unsigned char *data; +	__u32 seq_recv; + + +	struct rtable *rt; +	struct net_device *tdev; +	struct iphdr  *iph; +	int    max_headroom; + +	if (sk_pppox(po)->sk_state & PPPOX_DEAD) +		goto tx_error; + +	{ +		struct flowi fl = { .oif = 0, +			.nl_u = { +				.ip4_u = { +					.daddr = opt->dst_addr.sin_addr.s_addr, +					.saddr = opt->src_addr.sin_addr.s_addr, +					.tos = RT_TOS(0) } }, +			.proto = IPPROTO_GRE }; +		err = ip_route_output_key(&init_net, &rt, &fl); +		if (err) +			goto tx_error; +	} +	tdev = rt->dst.dev; + +	max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(*iph) + sizeof(*hdr) + 2; + +	if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) { +		struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom); +		if (!new_skb) { +			ip_rt_put(rt); +			goto tx_error; +		} +		if (skb->sk) +			skb_set_owner_w(new_skb, skb->sk); +		kfree_skb(skb); +		skb = new_skb; +	} + +	data = skb->data; +	islcp = ((data[0] << 8) + data[1]) == PPP_LCP && 1 <= data[2] && data[2] <= 7; + +	/* compress protocol field */ +	if ((opt->ppp_flags & SC_COMP_PROT) && data[0] == 0 && !islcp) +		skb_pull(skb, 1); + +	/* Put in the address/control bytes if necessary */ +	if ((opt->ppp_flags & SC_COMP_AC) == 0 || islcp) { +		data = skb_push(skb, 2); +		data[0] = PPP_ALLSTATIONS; +		data[1] = PPP_UI; +	} + +	len = skb->len; + +	seq_recv = opt->seq_recv; + +	if (opt->ack_sent == seq_recv) +		header_len -= sizeof(hdr->ack); + +	/* Push down and install GRE header */ +	skb_push(skb, header_len); +	hdr = (struct pptp_gre_header *)(skb->data); + +	hdr->flags       = PPTP_GRE_FLAG_K; +	hdr->ver         = PPTP_GRE_VER; +	hdr->protocol    = htons(PPTP_GRE_PROTO); +	hdr->call_id     = htons(opt->dst_addr.call_id); + +	hdr->flags      |= PPTP_GRE_FLAG_S; +	hdr->seq         = htonl(++opt->seq_sent); +	if (opt->ack_sent != seq_recv)	{ +		/* send ack with this message */ +		hdr->ver |= PPTP_GRE_FLAG_A; +		hdr->ack  = htonl(seq_recv); +		opt->ack_sent = seq_recv; +	} +	hdr->payload_len = htons(len); + +	/*	Push down and install the IP header. */ + +	skb_reset_transport_header(skb); +	skb_push(skb, sizeof(*iph)); +	skb_reset_network_header(skb); +	memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); +	IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED | IPSKB_REROUTED); + +	iph =	ip_hdr(skb); +	iph->version =	4; +	iph->ihl =	sizeof(struct iphdr) >> 2; +	if (ip_dont_fragment(sk, &rt->dst)) +		iph->frag_off	=	htons(IP_DF); +	else +		iph->frag_off	=	0; +	iph->protocol = IPPROTO_GRE; +	iph->tos      = 0; +	iph->daddr    = rt->rt_dst; +	iph->saddr    = rt->rt_src; +	iph->ttl      = dst_metric(&rt->dst, RTAX_HOPLIMIT); +	iph->tot_len  = htons(skb->len); + +	skb_dst_drop(skb); +	skb_dst_set(skb, &rt->dst); + +	nf_reset(skb); + +	skb->ip_summed = CHECKSUM_NONE; +	ip_select_ident(iph, &rt->dst, NULL); +	ip_send_check(iph); + +	ip_local_out(skb); + +tx_error: +	return 1; +} + +static int pptp_rcv_core(struct sock *sk, struct sk_buff *skb) +{ +	struct pppox_sock *po = pppox_sk(sk); +	struct pptp_opt *opt = &po->proto.pptp; +	int headersize, payload_len, seq; +	__u8 *payload; +	struct pptp_gre_header *header; + +	if (!(sk->sk_state & PPPOX_CONNECTED)) { +		if (sock_queue_rcv_skb(sk, skb)) +			goto drop; +		return NET_RX_SUCCESS; +	} + +	header = (struct pptp_gre_header *)(skb->data); + +	/* test if acknowledgement present */ +	if (PPTP_GRE_IS_A(header->ver)) { +		__u32 ack = (PPTP_GRE_IS_S(header->flags)) ? +				header->ack : header->seq; /* ack in different place if S = 0 */ + +		ack = ntohl(ack); + +		if (ack > opt->ack_recv) +			opt->ack_recv = ack; +		/* also handle sequence number wrap-around  */ +		if (WRAPPED(ack, opt->ack_recv)) +			opt->ack_recv = ack; +	} + +	/* test if payload present */ +	if (!PPTP_GRE_IS_S(header->flags)) +		goto drop; + +	headersize  = sizeof(*header); +	payload_len = ntohs(header->payload_len); +	seq         = ntohl(header->seq); + +	/* no ack present? */ +	if (!PPTP_GRE_IS_A(header->ver)) +		headersize -= sizeof(header->ack); +	/* check for incomplete packet (length smaller than expected) */ +	if (skb->len - headersize < payload_len) +		goto drop; + +	payload = skb->data + headersize; +	/* check for expected sequence number */ +	if (seq < opt->seq_recv + 1 || WRAPPED(opt->seq_recv, seq)) { +		if ((payload[0] == PPP_ALLSTATIONS) && (payload[1] == PPP_UI) && +				(PPP_PROTOCOL(payload) == PPP_LCP) && +				((payload[4] == PPP_LCP_ECHOREQ) || (payload[4] == PPP_LCP_ECHOREP))) +			goto allow_packet; +	} else { +		opt->seq_recv = seq; +allow_packet: +		skb_pull(skb, headersize); + +		if (payload[0] == PPP_ALLSTATIONS && payload[1] == PPP_UI) { +			/* chop off address/control */ +			if (skb->len < 3) +				goto drop; +			skb_pull(skb, 2); +		} + +		if ((*skb->data) & 1) { +			/* protocol is compressed */ +			skb_push(skb, 1)[0] = 0; +		} + +		skb->ip_summed = CHECKSUM_NONE; +		skb_set_network_header(skb, skb->head-skb->data); +		ppp_input(&po->chan, skb); + +		return NET_RX_SUCCESS; +	} +drop: +	kfree_skb(skb); +	return NET_RX_DROP; +} + +static int pptp_rcv(struct sk_buff *skb) +{ +	struct pppox_sock *po; +	struct pptp_gre_header *header; +	struct iphdr *iph; + +	if (skb->pkt_type != PACKET_HOST) +		goto drop; + +	if (!pskb_may_pull(skb, 12)) +		goto drop; + +	iph = ip_hdr(skb); + +	header = (struct pptp_gre_header *)skb->data; + +	if (ntohs(header->protocol) != PPTP_GRE_PROTO || /* PPTP-GRE protocol for PPTP */ +		PPTP_GRE_IS_C(header->flags) ||                /* flag C should be clear */ +		PPTP_GRE_IS_R(header->flags) ||                /* flag R should be clear */ +		!PPTP_GRE_IS_K(header->flags) ||               /* flag K should be set */ +		(header->flags&0xF) != 0)                      /* routing and recursion ctrl = 0 */ +		/* if invalid, discard this packet */ +		goto drop; + +	po = lookup_chan(htons(header->call_id), iph->saddr); +	if (po) { +		skb_dst_drop(skb); +		nf_reset(skb); +		return sk_receive_skb(sk_pppox(po), skb, 0); +	} +drop: +	kfree_skb(skb); +	return NET_RX_DROP; +} + +static int pptp_bind(struct socket *sock, struct sockaddr *uservaddr, +	int sockaddr_len) +{ +	struct sock *sk = sock->sk; +	struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr; +	struct pppox_sock *po = pppox_sk(sk); +	struct pptp_opt *opt = &po->proto.pptp; +	int error = 0; + +	lock_sock(sk); + +	opt->src_addr = sp->sa_addr.pptp; +	if (add_chan(po)) { +		release_sock(sk); +		error = -EBUSY; +	} + +	release_sock(sk); +	return error; +} + +static int pptp_connect(struct socket *sock, struct sockaddr *uservaddr, +	int sockaddr_len, int flags) +{ +	struct sock *sk = sock->sk; +	struct sockaddr_pppox *sp = (struct sockaddr_pppox *) uservaddr; +	struct pppox_sock *po = pppox_sk(sk); +	struct pptp_opt *opt = &po->proto.pptp; +	struct rtable *rt; +	int error = 0; + +	if (sp->sa_protocol != PX_PROTO_PPTP) +		return -EINVAL; + +	if (lookup_chan_dst(sp->sa_addr.pptp.call_id, sp->sa_addr.pptp.sin_addr.s_addr)) +		return -EALREADY; + +	lock_sock(sk); +	/* Check for already bound sockets */ +	if (sk->sk_state & PPPOX_CONNECTED) { +		error = -EBUSY; +		goto end; +	} + +	/* Check for already disconnected sockets, on attempts to disconnect */ +	if (sk->sk_state & PPPOX_DEAD) { +		error = -EALREADY; +		goto end; +	} + +	if (!opt->src_addr.sin_addr.s_addr || !sp->sa_addr.pptp.sin_addr.s_addr) { +		error = -EINVAL; +		goto end; +	} + +	po->chan.private = sk; +	po->chan.ops = &pptp_chan_ops; + +	{ +		struct flowi fl = { +			.nl_u = { +				.ip4_u = { +					.daddr = opt->dst_addr.sin_addr.s_addr, +					.saddr = opt->src_addr.sin_addr.s_addr, +					.tos = RT_CONN_FLAGS(sk) } }, +			.proto = IPPROTO_GRE }; +		security_sk_classify_flow(sk, &fl); +		if (ip_route_output_key(&init_net, &rt, &fl)) { +			error = -EHOSTUNREACH; +			goto end; +		} +		sk_setup_caps(sk, &rt->dst); +	} +	po->chan.mtu = dst_mtu(&rt->dst); +	if (!po->chan.mtu) +		po->chan.mtu = PPP_MTU; +	ip_rt_put(rt); +	po->chan.mtu -= PPTP_HEADER_OVERHEAD; + +	po->chan.hdrlen = 2 + sizeof(struct pptp_gre_header); +	error = ppp_register_channel(&po->chan); +	if (error) { +		pr_err("PPTP: failed to register PPP channel (%d)\n", error); +		goto end; +	} + +	opt->dst_addr = sp->sa_addr.pptp; +	sk->sk_state = PPPOX_CONNECTED; + + end: +	release_sock(sk); +	return error; +} + +static int pptp_getname(struct socket *sock, struct sockaddr *uaddr, +	int *usockaddr_len, int peer) +{ +	int len = sizeof(struct sockaddr_pppox); +	struct sockaddr_pppox sp; + +	sp.sa_family	  = AF_PPPOX; +	sp.sa_protocol  = PX_PROTO_PPTP; +	sp.sa_addr.pptp = pppox_sk(sock->sk)->proto.pptp.src_addr; + +	memcpy(uaddr, &sp, len); + +	*usockaddr_len = len; + +	return 0; +} + +static int pptp_release(struct socket *sock) +{ +	struct sock *sk = sock->sk; +	struct pppox_sock *po; +	struct pptp_opt *opt; +	int error = 0; + +	if (!sk) +		return 0; + +	lock_sock(sk); + +	if (sock_flag(sk, SOCK_DEAD)) { +		release_sock(sk); +		return -EBADF; +	} + +	po = pppox_sk(sk); +	opt = &po->proto.pptp; +	del_chan(po); + +	pppox_unbind_sock(sk); +	sk->sk_state = PPPOX_DEAD; + +	sock_orphan(sk); +	sock->sk = NULL; + +	release_sock(sk); +	sock_put(sk); + +	return error; +} + +static void pptp_sock_destruct(struct sock *sk) +{ +	if (!(sk->sk_state & PPPOX_DEAD)) { +		del_chan(pppox_sk(sk)); +		pppox_unbind_sock(sk); +	} +	skb_queue_purge(&sk->sk_receive_queue); +} + +static int pptp_create(struct net *net, struct socket *sock) +{ +	int error = -ENOMEM; +	struct sock *sk; +	struct pppox_sock *po; +	struct pptp_opt *opt; + +	sk = sk_alloc(net, PF_PPPOX, GFP_KERNEL, &pptp_sk_proto); +	if (!sk) +		goto out; + +	sock_init_data(sock, sk); + +	sock->state = SS_UNCONNECTED; +	sock->ops   = &pptp_ops; + +	sk->sk_backlog_rcv = pptp_rcv_core; +	sk->sk_state       = PPPOX_NONE; +	sk->sk_type        = SOCK_STREAM; +	sk->sk_family      = PF_PPPOX; +	sk->sk_protocol    = PX_PROTO_PPTP; +	sk->sk_destruct    = pptp_sock_destruct; + +	po = pppox_sk(sk); +	opt = &po->proto.pptp; + +	opt->seq_sent = 0; opt->seq_recv = 0; +	opt->ack_recv = 0; opt->ack_sent = 0; + +	error = 0; +out: +	return error; +} + +static int pptp_ppp_ioctl(struct ppp_channel *chan, unsigned int cmd, +	unsigned long arg) +{ +	struct sock *sk = (struct sock *) chan->private; +	struct pppox_sock *po = pppox_sk(sk); +	struct pptp_opt *opt = &po->proto.pptp; +	void __user *argp = (void __user *)arg; +	int __user *p = argp; +	int err, val; + +	err = -EFAULT; +	switch (cmd) { +	case PPPIOCGFLAGS: +		val = opt->ppp_flags; +		if (put_user(val, p)) +			break; +		err = 0; +		break; +	case PPPIOCSFLAGS: +		if (get_user(val, p)) +			break; +		opt->ppp_flags = val & ~SC_RCV_BITS; +		err = 0; +		break; +	default: +		err = -ENOTTY; +	} + +	return err; +} + +static const struct ppp_channel_ops pptp_chan_ops = { +	.start_xmit = pptp_xmit, +	.ioctl      = pptp_ppp_ioctl, +}; + +static struct proto pptp_sk_proto __read_mostly = { +	.name     = "PPTP", +	.owner    = THIS_MODULE, +	.obj_size = sizeof(struct pppox_sock), +}; + +static const struct proto_ops pptp_ops = { +	.family     = AF_PPPOX, +	.owner      = THIS_MODULE, +	.release    = pptp_release, +	.bind       = pptp_bind, +	.connect    = pptp_connect, +	.socketpair = sock_no_socketpair, +	.accept     = sock_no_accept, +	.getname    = pptp_getname, +	.poll       = sock_no_poll, +	.listen     = sock_no_listen, +	.shutdown   = sock_no_shutdown, +	.setsockopt = sock_no_setsockopt, +	.getsockopt = sock_no_getsockopt, +	.sendmsg    = sock_no_sendmsg, +	.recvmsg    = sock_no_recvmsg, +	.mmap       = sock_no_mmap, +	.ioctl      = pppox_ioctl, +}; + +static const struct pppox_proto pppox_pptp_proto = { +	.create = pptp_create, +	.owner  = THIS_MODULE, +}; + +static const struct gre_protocol gre_pptp_protocol = { +	.handler = pptp_rcv, +}; + +static int __init pptp_init_module(void) +{ +	int err = 0; +	pr_info("PPTP driver version " PPTP_DRIVER_VERSION "\n"); + +	callid_sock = __vmalloc((MAX_CALLID + 1) * sizeof(void *), +		GFP_KERNEL | __GFP_ZERO, PAGE_KERNEL); +	if (!callid_sock) { +		pr_err("PPTP: cann't allocate memory\n"); +		return -ENOMEM; +	} + +	err = gre_add_protocol(&gre_pptp_protocol, GREPROTO_PPTP); +	if (err) { +		pr_err("PPTP: can't add gre protocol\n"); +		goto out_mem_free; +	} + +	err = proto_register(&pptp_sk_proto, 0); +	if (err) { +		pr_err("PPTP: can't register sk_proto\n"); +		goto out_gre_del_protocol; +	} + +	err = register_pppox_proto(PX_PROTO_PPTP, &pppox_pptp_proto); +	if (err) { +		pr_err("PPTP: can't register pppox_proto\n"); +		goto out_unregister_sk_proto; +	} + +	return 0; + +out_unregister_sk_proto: +	proto_unregister(&pptp_sk_proto); +out_gre_del_protocol: +	gre_del_protocol(&gre_pptp_protocol, GREPROTO_PPTP); +out_mem_free: +	vfree(callid_sock); + +	return err; +} + +static void __exit pptp_exit_module(void) +{ +	unregister_pppox_proto(PX_PROTO_PPTP); +	proto_unregister(&pptp_sk_proto); +	gre_del_protocol(&gre_pptp_protocol, GREPROTO_PPTP); +	vfree(callid_sock); +} + +module_init(pptp_init_module); +module_exit(pptp_exit_module); + +MODULE_DESCRIPTION("Point-to-Point Tunneling Protocol"); +MODULE_AUTHOR("D. Kozlov (xeb@mail.ru)"); +MODULE_LICENSE("GPL");  |