text: /*
   * ip_nat_pptp.c	- Version 1.5
   *
   * NAT support for PPTP (Point to Point Tunneling Protocol).
   * PPTP is a a protocol for creating virtual private networks.
   * It is a specification defined by Microsoft and some vendors
   * working with Microsoft.  PPTP is built on top of a modified
   * version of the Internet Generic Routing Encapsulation Protocol.
   * GRE is defined in RFC 1701 and RFC 1702.  Documentation of
   * PPTP can be found in RFC 2637
   *
   * (C) 2000-2003 by Harald Welte <laforge@gnumonks.org>
   *
   * Development of this code funded by Astaro AG (http://www.astaro.com/)
   *
   * TODO: - Support for multiple calls within one session
   * 	   (needs netfilter newnat code)
   * 	 - NAT to a unique tuple, not to TCP source port
   * 	   (needs netfilter tuple reservation)
   *
   * Changes:
   *     2002-02-10 - Version 1.3
   *       - Use ip_nat_mangle_tcp_packet() because of cloned skb's
   *	   in local connections (Philip Craig <philipc@snapgear.com>)
   *       - add checks for magicCookie and pptp version
   *       - make argument list of pptp_{out,in}bound_packet() shorter
   *       - move to C99 style initializers
   *       - print version number at module loadtime
   *     2003-09-22 - Version 1.5
   *       - use SNATed tcp sourceport as callid, since we get called before
   *         TCP header is mangled (Philip Craig <philipc@snapgear.com>)
   * 
   */
  
  #include <linux/config.h>
  #include <linux/module.h>
  #include <linux/ip.h>
  #include <linux/tcp.h>
  #include <net/tcp.h>
  #include <linux/netfilter_ipv4/ip_nat.h>
  #include <linux/netfilter_ipv4/ip_nat_rule.h>
  #include <linux/netfilter_ipv4/ip_nat_helper.h>
  #include <linux/netfilter_ipv4/ip_nat_pptp.h>
  #include <linux/netfilter_ipv4/ip_conntrack_helper.h>
  #include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
  #include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
  
  #define IP_NAT_PPTP_VERSION "1.5"
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
  MODULE_DESCRIPTION("Netfilter NAT helper module for PPTP");
  
  
  #if 0
  #include "ip_conntrack_pptp_priv.h"
  #define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
  				       ": " format, ## args)
  #else
  #define DEBUGP(format, args...)
  #endif
  
  static unsigned int
  pptp_nat_expected(struct sk_buff **pskb,
  		  unsigned int hooknum,
  		  struct ip_conntrack *ct,
  		  struct ip_nat_info *info)
  {
  	struct ip_conntrack *master = master_ct(ct);
  	struct ip_nat_multi_range mr;
  	struct ip_ct_pptp_master *ct_pptp_info;
  	struct ip_nat_pptp *nat_pptp_info;
  	u_int32_t newip, newcid;
  	int ret;
  
  	IP_NF_ASSERT(info);
  	IP_NF_ASSERT(master);
  	IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
  
  	DEBUGP("we have a connection!\n");
  
  	LOCK_BH(&ip_pptp_lock);
  	ct_pptp_info = &master->help.ct_pptp_info;
  	nat_pptp_info = &master->nat.help.nat_pptp_info;
  
  	/* need to alter GRE tuple because conntrack expectfn() used 'wrong'
  	 * (unmanipulated) values */
  	if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
  		DEBUGP("completing tuples with NAT info \n");
  		/* we can do this, since we're unconfirmed */
  		if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
  			htonl(ct_pptp_info->pac_call_id)) {	
  			/* assume PNS->PAC */
  			ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
  				htonl(nat_pptp_info->pns_call_id);
  			ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
  				htonl(nat_pptp_info->pns_call_id);
  			newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
  			newcid = htonl(nat_pptp_info->pac_call_id);
  		} else {
  			/* assume PAC->PNS */
  			ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
  				htonl(nat_pptp_info->pac_call_id);
  			ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
  				htonl(nat_pptp_info->pac_call_id);
  			newip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
  			newcid = htonl(nat_pptp_info->pns_call_id);
  		}
  	} else {
  		if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
  			htonl(ct_pptp_info->pac_call_id)) {	
  			/* assume PNS->PAC */
  			newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
  			newcid = htonl(ct_pptp_info->pns_call_id);
  		}
  		else {
  			/* assume PAC->PNS */
  			newip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
  			newcid = htonl(ct_pptp_info->pac_call_id);
  		}
  	}
  
  	mr.rangesize = 1;
  	mr.range[0].flags = IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED;
  	mr.range[0].min_ip = mr.range[0].max_ip = newip;
  	mr.range[0].min = mr.range[0].max = 
  		((union ip_conntrack_manip_proto ) { newcid }); 
  	DEBUGP("change ip to %u.%u.%u.%u\n", 
  		NIPQUAD(newip));
  	DEBUGP("change key to 0x%x\n", ntohl(newcid));
  	ret = ip_nat_setup_info(ct, &mr, hooknum);
  
  	UNLOCK_BH(&ip_pptp_lock);
  
  	return ret;
  
  }
  
  /* outbound packets == from PNS to PAC */
  static inline unsigned int
  pptp_outbound_pkt(struct sk_buff **pskb,
  		  struct ip_conntrack *ct,
  		  enum ip_conntrack_info ctinfo,
  		  struct ip_conntrack_expect *exp)
  
  {
  	struct iphdr *iph = (*pskb)->nh.iph;
  	struct tcphdr *tcph = (void *) iph + iph->ihl*4;
  	struct pptp_pkt_hdr *pptph = (struct pptp_pkt_hdr *) 
  					((void *)tcph + tcph->doff*4);
  
  	struct PptpControlHeader *ctlh;
  	union pptp_ctrl_union pptpReq;
  	struct ip_ct_pptp_master *ct_pptp_info = &ct->help.ct_pptp_info;
  	struct ip_nat_pptp *nat_pptp_info = &ct->nat.help.nat_pptp_info;
  
  	u_int16_t msg, *cid = NULL, new_callid;
  
  	/* FIXME: size checks !!! */
  	ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
  	pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh));
  
  	new_callid = htons(ct_pptp_info->pns_call_id);
  	
  	switch (msg = ntohs(ctlh->messageType)) {
  		case PPTP_OUT_CALL_REQUEST:
  			cid = &pptpReq.ocreq->callID;
  			/* FIXME: ideally we would want to reserve a call ID
  			 * here.  current netfilter NAT core is not able to do
  			 * this :( For now we use TCP source port. This breaks
  			 * multiple calls within one control session */
  
  			/* save original call ID in nat_info */
  			nat_pptp_info->pns_call_id = ct_pptp_info->pns_call_id;
  
  			/* don't use tcph->source since we are at a DSTmanip
  			 * hook (e.g. PREROUTING) and pkt is not mangled yet */
  			new_callid = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.tcp.port;
  
  			/* save new call ID in ct info */
  			ct_pptp_info->pns_call_id = ntohs(new_callid);
  			break;
  		case PPTP_IN_CALL_REPLY:
  			cid = &pptpReq.icreq->callID;
  			break;
  		case PPTP_CALL_CLEAR_REQUEST:
  			cid = &pptpReq.clrreq->callID;
  			break;
  		default:
  			DEBUGP("unknown outbound packet 0x%04x:%s\n", msg,
  			      (msg <= PPTP_MSG_MAX)? strMName[msg]:strMName[0]);
  			/* fall through */
  
  		case PPTP_SET_LINK_INFO:
  			/* only need to NAT in case PAC is behind NAT box */
  		case PPTP_START_SESSION_REQUEST:
  		case PPTP_START_SESSION_REPLY:
  		case PPTP_STOP_SESSION_REQUEST:
  		case PPTP_STOP_SESSION_REPLY:
  		case PPTP_ECHO_REQUEST:
  		case PPTP_ECHO_REPLY:
  			/* no need to alter packet */
  			return NF_ACCEPT;
  	}
  
  	IP_NF_ASSERT(cid);
  
  	DEBUGP("altering call id from 0x%04x to 0x%04x\n",
  		ntohs(*cid), ntohs(new_callid));
  
  	/* mangle packet */
  	ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, (void *)cid - (void *)pptph,
  				 sizeof(new_callid), (char *)&new_callid,
  				 sizeof(new_callid));
  
  	return NF_ACCEPT;
  }
  
  /* inbound packets == from PAC to PNS */
  static inline unsigned int
  pptp_inbound_pkt(struct sk_buff **pskb,
  		 struct ip_conntrack *ct,
  		 enum ip_conntrack_info ctinfo,
  		 struct ip_conntrack_expect *oldexp)
  {
  	struct iphdr *iph = (*pskb)->nh.iph;
  	struct tcphdr *tcph = (void *) iph + iph->ihl*4;
  	struct pptp_pkt_hdr *pptph = (struct pptp_pkt_hdr *) 
  					((void *)tcph + tcph->doff*4);
  
  	struct PptpControlHeader *ctlh;
  	union pptp_ctrl_union pptpReq;
  	struct ip_ct_pptp_master *ct_pptp_info = &ct->help.ct_pptp_info;
  	struct ip_nat_pptp *nat_pptp_info = &ct->nat.help.nat_pptp_info;
  
  	u_int16_t msg, new_cid = 0, new_pcid, *pcid = NULL, *cid = NULL;
  	u_int32_t old_dst_ip;
  
  	struct ip_conntrack_tuple t, inv_t;
  	struct ip_conntrack_tuple *orig_t, *reply_t;
  
  	/* FIXME: size checks !!! */
  	ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
  	pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh));
  
  	new_pcid = htons(nat_pptp_info->pns_call_id);
  
  	switch (msg = ntohs(ctlh->messageType)) {
  	case PPTP_OUT_CALL_REPLY:
  		pcid = &pptpReq.ocack->peersCallID;	
  		cid = &pptpReq.ocack->callID;
  		if (!oldexp) {
  			DEBUGP("outcall but no expectation\n");
  			break;
  		}
  		old_dst_ip = oldexp->tuple.dst.ip;
  		t = oldexp->tuple;
  		invert_tuplepr(&inv_t, &t);
  
  		/* save original PAC call ID in nat_info */
  		nat_pptp_info->pac_call_id = ct_pptp_info->pac_call_id;
  
  		/* alter expectation */
  		orig_t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
  		reply_t = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
  		if (t.src.ip == orig_t->src.ip && t.dst.ip == orig_t->dst.ip) {
  			/* expectation for PNS->PAC direction */
  			t.src.u.gre.key = htonl(nat_pptp_info->pns_call_id);
  			t.dst.u.gre.key = htonl(ct_pptp_info->pac_call_id);
  			inv_t.src.ip = reply_t->src.ip;
  			inv_t.dst.ip = reply_t->dst.ip;
  			inv_t.src.u.gre.key = htonl(nat_pptp_info->pac_call_id);
  			inv_t.dst.u.gre.key = htonl(ct_pptp_info->pns_call_id);
  		} else {
  			/* expectation for PAC->PNS direction */
  			t.src.u.gre.key = htonl(nat_pptp_info->pac_call_id);
  			t.dst.u.gre.key = htonl(ct_pptp_info->pns_call_id);
  			inv_t.src.ip = orig_t->src.ip;
  			inv_t.dst.ip = orig_t->dst.ip;
  			inv_t.src.u.gre.key = htonl(nat_pptp_info->pns_call_id);
  			inv_t.dst.u.gre.key = htonl(ct_pptp_info->pac_call_id);
  		}
  
  		if (!ip_conntrack_change_expect(oldexp, &t)) {
  			DEBUGP("successfully changed expect\n");
  		} else {
  			DEBUGP("can't change expect\n");
  		}
  		ip_ct_gre_keymap_change(oldexp->proto.gre.keymap_orig, &t);
  		ip_ct_gre_keymap_change(oldexp->proto.gre.keymap_reply, &inv_t);
  		break;
  	case PPTP_IN_CALL_CONNECT:
  		pcid = &pptpReq.iccon->peersCallID;
  		if (!oldexp)
  			break;
  		old_dst_ip = oldexp->tuple.dst.ip;
  		t = oldexp->tuple;
  
  		/* alter expectation, no need for callID */
  		if (t.dst.ip == ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip) {
  			/* expectation for PNS->PAC direction */
  			t.src.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
  		} else {
  			/* expectation for PAC->PNS direction */
  			t.dst.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
  		}
  
  		if (!ip_conntrack_change_expect(oldexp, &t)) {
  			DEBUGP("successfully changed expect\n");
  		} else {
  			DEBUGP("can't change expect\n");
  		}
  		break;
  	case PPTP_IN_CALL_REQUEST:
  		/* only need to nat in case PAC is behind NAT box */
  		break;
  	case PPTP_WAN_ERROR_NOTIFY:
  		pcid = &pptpReq.wanerr->peersCallID;
  		break;
  	case PPTP_CALL_DISCONNECT_NOTIFY:
  		pcid = &pptpReq.disc->callID;
  		break;
  
  	default:
  		DEBUGP("unknown inbound packet %s\n",
  			(msg <= PPTP_MSG_MAX)? strMName[msg]:strMName[0]);
  		/* fall through */
  
  	case PPTP_START_SESSION_REQUEST:
  	case PPTP_START_SESSION_REPLY:
  	case PPTP_STOP_SESSION_REQUEST:
  	case PPTP_STOP_SESSION_REPLY:
  	case PPTP_ECHO_REQUEST:
  	case PPTP_ECHO_REPLY:
  		/* no need to alter packet */
  		return NF_ACCEPT;
  	}
  
  	/* mangle packet */
  	IP_NF_ASSERT(pcid);
  	DEBUGP("altering peer call id from 0x%04x to 0x%04x\n",
  		ntohs(*pcid), ntohs(new_pcid));
  	ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, (void *)pcid - (void *)pptph,
  				 sizeof(new_pcid), (char *)&new_pcid, 
  				 sizeof(new_pcid));
  
  	if (new_cid) {
  		IP_NF_ASSERT(cid);
  		DEBUGP("altering call id from 0x%04x to 0x%04x\n",
  			ntohs(*cid), ntohs(new_cid));
  		ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, 
  					 (void *)cid - (void *)pptph, 
  					 sizeof(new_cid), (char *)&new_cid, 
  					 sizeof(new_cid));
  	}
  
  	/* great, at least we don't need to resize packets */
  	return NF_ACCEPT;
  }
  
  
  static unsigned int tcp_help(struct ip_conntrack *ct,
  			     struct ip_conntrack_expect *exp,
  			     struct ip_nat_info *info,
  			     enum ip_conntrack_info ctinfo,
  			     unsigned int hooknum, struct sk_buff **pskb)
  {
  	struct iphdr *iph = (*pskb)->nh.iph;
  	struct tcphdr *tcph = (void *) iph + iph->ihl*4;
  	unsigned int datalen = (*pskb)->len - iph->ihl*4 - tcph->doff*4;
  	struct pptp_pkt_hdr *pptph;
  
  	int dir;
  
  	DEBUGP("entering\n");
  
  	/* Only mangle things once: DST for original direction
  	   and SRC for reply direction. */
  	dir = CTINFO2DIR(ctinfo);
  	if (!((HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
  	     && dir == IP_CT_DIR_ORIGINAL)
  	      || (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST
  		  && dir == IP_CT_DIR_REPLY))) {
  		DEBUGP("Not touching dir %s at hook %s\n",
  		       dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
  		       hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
  		       : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
  		       : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT"
  		       : hooknum == NF_IP_LOCAL_IN ? "INPUT" : "???");
  		return NF_ACCEPT;
  	}
  
  	/* if packet is too small, just skip it */
  	if (datalen < sizeof(struct pptp_pkt_hdr)+
  		      sizeof(struct PptpControlHeader)) {
  		DEBUGP("pptp packet too short\n");
  		return NF_ACCEPT;	
  	}
  
  	pptph = (struct pptp_pkt_hdr *) ((void *)tcph + tcph->doff*4);
  
  	/* if it's not a control message, we can't handle it */
  	if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL ||
  	    ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) {
  		DEBUGP("not a pptp control packet\n");
  		return NF_ACCEPT;
  	}
  
  	LOCK_BH(&ip_pptp_lock);
  
  	if (dir == IP_CT_DIR_ORIGINAL) {
  		/* reuqests sent by client to server (PNS->PAC) */
  		pptp_outbound_pkt(pskb, ct, ctinfo, exp);
  	} else {
  		/* response from the server to the client (PAC->PNS) */
  		pptp_inbound_pkt(pskb, ct, ctinfo, exp);
  	}
  
  	UNLOCK_BH(&ip_pptp_lock);
  
  	return NF_ACCEPT;
  }
  
  /* nat helper struct for control connection */
  static struct ip_nat_helper pptp_tcp_helper = { 
  	.list = { NULL, NULL },
  	.name = "pptp", 
  	.flags = IP_NAT_HELPER_F_ALWAYS, 
  	.me = THIS_MODULE,
  	.tuple = { .src = { .ip = 0, 
  			    .u = { .tcp = { .port = 
  				    	__constant_htons(PPTP_CONTROL_PORT) } 
  				 } 
  			  },
  	  	   .dst = { .ip = 0, 
  			    .u = { .all = 0 }, 
  			    .protonum = IPPROTO_TCP 
  		   	  } 
  		 },
  
  	.mask = { .src = { .ip = 0, 
  			   .u = { .tcp = { .port = 0xFFFF } } 
  			 },
  		  .dst = { .ip = 0, 
  			   .u = { .all = 0 }, 
  			   .protonum = 0xFFFF 
  		  	 } 
  		},
  	.help = tcp_help, 
  	.expect = pptp_nat_expected 
  };
  
  			  
  static int __init init(void)
  {
  	DEBUGP("%s: registering NAT helper\n", __FILE__);
  	if (ip_nat_helper_register(&pptp_tcp_helper)) {
  		printk(KERN_ERR "Unable to register NAT application helper "
  				"for pptp\n");
  		return -EIO;
  	}
  
  	printk("ip_nat_pptp version %s loaded\n", IP_NAT_PPTP_VERSION);
  	return 0;
  }
  
  static void __exit fini(void)
  {
  	DEBUGP("cleanup_module\n" );
  	ip_nat_helper_unregister(&pptp_tcp_helper);
  	printk("ip_nat_pptp version %s unloaded\n", IP_NAT_PPTP_VERSION);
  }
  
  module_init(init);
  module_exit(fini);
  