1/*
2 * ip_conntrack_pptp.c  - Version 1.9
3 *
4 * Connection tracking support for PPTP (Point to Point Tunneling Protocol).
5 * PPTP is a a protocol for creating virtual private networks.
6 * It is a specification defined by Microsoft and some vendors
7 * working with Microsoft.  PPTP is built on top of a modified
8 * version of the Internet Generic Routing Encapsulation Protocol.
9 * GRE is defined in RFC 1701 and RFC 1702.  Documentation of
10 * PPTP can be found in RFC 2637
11 *
12 * (C) 2000-2003 by Harald Welte <laforge@gnumonks.org>
13 *
14 * Development of this code funded by Astaro AG (http://www.astaro.com/)
15 *
16 * Limitations:
17 *       - We blindly assume that control connections are always
18 *         established in PNS->PAC direction.  This is a violation
19 *         of RFFC2673
20 *
21 * TODO: - finish support for multiple calls within one session
22 *         (needs expect reservations in newnat)
23 *       - testing of incoming PPTP calls
24 *
25 * Changes:
26 *      2002-02-05 - Version 1.3
27 *        - Call ip_conntrack_unexpect_related() from
28 *          pptp_timeout_related() to destroy expectations in case
29 *          CALL_DISCONNECT_NOTIFY or tcp fin packet was seen
30 *          (Philip Craig <philipc@snapgear.com>)
31 *        - Add Version information at module loadtime
32 *      2002-02-10 - Version 1.6
33 *        - move to C99 style initializers
34 *        - remove second expectation if first arrives
35 *
36 */
37
38#include <linux/config.h>
39#include <linux/module.h>
40#include <linux/netfilter.h>
41#include <linux/ip.h>
42#include <net/checksum.h>
43#include <net/tcp.h>
44
45#include <linux/netfilter_ipv4/lockhelp.h>
46#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
47#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
48#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
49
50#define IP_CT_PPTP_VERSION "1.9"
51
52MODULE_LICENSE("GPL");
53MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
54MODULE_DESCRIPTION("Netfilter connection tracking helper module for PPTP");
55
56DECLARE_LOCK(ip_pptp_lock);
57
58#if 0
59#include "ip_conntrack_pptp_priv.h"
60#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
61                                        ": " format, ## args)
62#else
63#define DEBUGP(format, args...)
64#endif
65
66#define SECS *HZ
67#define MINS * 60 SECS
68#define HOURS * 60 MINS
69#define DAYS * 24 HOURS
70
71#define PPTP_GRE_TIMEOUT                (10 MINS)
72#define PPTP_GRE_STREAM_TIMEOUT         (5 DAYS)
73
74static int pptp_expectfn(struct ip_conntrack *ct)
75{
76        struct ip_conntrack *master;
77        struct ip_conntrack_expect *exp;
78
79        DEBUGP("increasing timeouts\n");
80        /* increase timeout of GRE data channel conntrack entry */
81        ct->proto.gre.timeout = PPTP_GRE_TIMEOUT;
82        ct->proto.gre.stream_timeout = PPTP_GRE_STREAM_TIMEOUT;
83
84        master = master_ct(ct);
85        if (!master) {
86                DEBUGP(" no master!!!\n");
87                return 0;
88        }
89
90        exp = ct->master;
91        if (!exp) {
92                DEBUGP("no expectation!!\n");
93                return 0;
94        }
95
96        DEBUGP("completing tuples with ct info\n");
97        /* we can do this, since we're unconfirmed */
98        if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
99                htonl(master->help.ct_pptp_info.pac_call_id)) {
100                /* assume PNS->PAC */
101                ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
102                        htonl(master->help.ct_pptp_info.pns_call_id);
103                ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
104                        htonl(master->help.ct_pptp_info.pns_call_id);
105        } else {
106                /* assume PAC->PNS */
107                ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
108                        htonl(master->help.ct_pptp_info.pac_call_id);
109                ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
110                        htonl(master->help.ct_pptp_info.pac_call_id);
111        }
112
113        /* delete other expectation */
114        if (exp->expected_list.next != &exp->expected_list) {
115                struct ip_conntrack_expect *other_exp;
116                struct list_head *cur_item, *next;
117
118                for (cur_item = master->sibling_list.next;
119                     cur_item != &master->sibling_list; cur_item = next) {
120                        next = cur_item->next;
121                        other_exp = list_entry(cur_item,
122                                               struct ip_conntrack_expect,
123                                               expected_list);
124                        /* remove only if occurred at same sequence number */
125                        if (other_exp != exp && other_exp->seq == exp->seq) {
126                                DEBUGP("unexpecting other direction\n");
127                                ip_ct_gre_keymap_destroy(other_exp);
128                                ip_conntrack_unexpect_related(other_exp);
129                        }
130                }
131        }
132
133        return 0;
134}
135
136/* timeout GRE data connections */
137static int pptp_timeout_related(struct ip_conntrack *ct)
138{
139        struct list_head *cur_item, *next;
140        struct ip_conntrack_expect *exp;
141
142        /* FIXME: do we have to lock something ? */
143        for (cur_item = ct->sibling_list.next;
144            cur_item != &ct->sibling_list; cur_item = next) {
145                next = cur_item->next;
146                exp = list_entry(cur_item, struct ip_conntrack_expect,
147                                 expected_list);
148
149                ip_ct_gre_keymap_destroy(exp);
150                if (!exp->sibling) {
151                        ip_conntrack_unexpect_related(exp);
152                        continue;
153                }
154
155                DEBUGP("setting timeout of conntrack %p to 0\n",
156                        exp->sibling);
157                exp->sibling->proto.gre.timeout = 0;
158                exp->sibling->proto.gre.stream_timeout = 0;
159                ip_ct_refresh(exp->sibling, 0);
160        }
161
162        return 0;
163}
164
165/* expect GRE connections (PNS->PAC and PAC->PNS direction) */
166static inline int
167exp_gre(struct ip_conntrack *master,
168        u_int32_t seq,
169        u_int16_t callid,
170        u_int16_t peer_callid)
171{
172        struct ip_conntrack_expect exp;
173        struct ip_conntrack_tuple inv_tuple;
174
175        memset(&exp, 0, sizeof(exp));
176        /* tuple in original direction, PNS->PAC */
177        exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
178        exp.tuple.src.u.gre.key = htonl(ntohs(peer_callid));
179        exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
180        exp.tuple.dst.u.gre.key = htonl(ntohs(callid));
181        exp.tuple.dst.u.gre.protocol = __constant_htons(GRE_PROTOCOL_PPTP);
182        exp.tuple.dst.u.gre.version = GRE_VERSION_PPTP;
183        exp.tuple.dst.protonum = IPPROTO_GRE;
184
185        exp.mask.src.ip = 0xffffffff;
186        exp.mask.src.u.all = 0;
187        exp.mask.dst.u.all = 0;
188        exp.mask.dst.u.gre.key = 0xffffffff;
189        exp.mask.dst.u.gre.version = 0xff;
190        exp.mask.dst.u.gre.protocol = 0xffff;
191        exp.mask.dst.ip = 0xffffffff;
192        exp.mask.dst.protonum = 0xffff;
193
194        exp.seq = seq;
195        exp.expectfn = pptp_expectfn;
196
197        exp.help.exp_pptp_info.pac_call_id = ntohs(callid);
198        exp.help.exp_pptp_info.pns_call_id = ntohs(peer_callid);
199
200        DEBUGP("calling expect_related ");
201        DUMP_TUPLE_RAW(&exp.tuple);
202
203        /* Add GRE keymap entries */
204        if (ip_ct_gre_keymap_add(&exp, &exp.tuple, 0) != 0)
205                return 1;
206
207        invert_tuplepr(&inv_tuple, &exp.tuple);
208        if (ip_ct_gre_keymap_add(&exp, &inv_tuple, 1) != 0) {
209                ip_ct_gre_keymap_destroy(&exp);
210                return 1;
211        }
212
213        if (ip_conntrack_expect_related(master, &exp) != 0) {
214                ip_ct_gre_keymap_destroy(&exp);
215                DEBUGP("cannot expect_related()\n");
216                return 1;
217        }
218
219        /* tuple in reply direction, PAC->PNS */
220        exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
221        exp.tuple.src.u.gre.key = htonl(ntohs(callid));
222        exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
223        exp.tuple.dst.u.gre.key = htonl(ntohs(peer_callid));
224
225        DEBUGP("calling expect_related ");
226        DUMP_TUPLE_RAW(&exp.tuple);
227
228        /* Add GRE keymap entries */
229        ip_ct_gre_keymap_add(&exp, &exp.tuple, 0);
230        invert_tuplepr(&inv_tuple, &exp.tuple);
231        ip_ct_gre_keymap_add(&exp, &inv_tuple, 1);
232        /* FIXME: cannot handle error correctly, since we need to free
233         * the above keymap :( */
234
235        if (ip_conntrack_expect_related(master, &exp) != 0) {
236                /* free the second pair of keypmaps */
237                ip_ct_gre_keymap_destroy(&exp);
238                DEBUGP("cannot expect_related():\n");
239                return 1;
240        }
241
242        return 0;
243}
244
245static inline int
246pptp_inbound_pkt(struct tcphdr *tcph,
247                 struct pptp_pkt_hdr *pptph,
248                 size_t datalen,
249                 struct ip_conntrack *ct,
250                 enum ip_conntrack_info ctinfo)
251{
252        struct PptpControlHeader *ctlh;
253        union pptp_ctrl_union pptpReq;
254
255        struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
256        u_int16_t msg, *cid, *pcid;
257        u_int32_t seq;
258
259        ctlh = (struct PptpControlHeader *)
260                ((char *) pptph + sizeof(struct pptp_pkt_hdr));
261        pptpReq.rawreq = (void *)
262                ((char *) ctlh + sizeof(struct PptpControlHeader));
263
264        msg = ntohs(ctlh->messageType);
265        DEBUGP("inbound control message %s\n", strMName[msg]);
266
267        switch (msg) {
268        case PPTP_START_SESSION_REPLY:
269                /* server confirms new control session */
270                if (info->sstate < PPTP_SESSION_REQUESTED) {
271                        DEBUGP("%s without START_SESS_REQUEST\n",
272                                strMName[msg]);
273                        break;
274                }
275                if (pptpReq.srep->resultCode == PPTP_START_OK)
276                        info->sstate = PPTP_SESSION_CONFIRMED;
277                else
278                        info->sstate = PPTP_SESSION_ERROR;
279                break;
280
281        case PPTP_STOP_SESSION_REPLY:
282                /* server confirms end of control session */
283                if (info->sstate > PPTP_SESSION_STOPREQ) {
284                        DEBUGP("%s without STOP_SESS_REQUEST\n",
285                                strMName[msg]);
286                        break;
287                }
288                if (pptpReq.strep->resultCode == PPTP_STOP_OK)
289                        info->sstate = PPTP_SESSION_NONE;
290                else
291                        info->sstate = PPTP_SESSION_ERROR;
292                break;
293
294        case PPTP_OUT_CALL_REPLY:
295                /* server accepted call, we now expect GRE frames */
296                if (info->sstate != PPTP_SESSION_CONFIRMED) {
297                        DEBUGP("%s but no session\n", strMName[msg]);
298                        break;
299                }
300                if (info->cstate != PPTP_CALL_OUT_REQ &&
301                    info->cstate != PPTP_CALL_OUT_CONF) {
302                        DEBUGP("%s without OUTCALL_REQ\n", strMName[msg]);
303                        break;
304                }
305                if (pptpReq.ocack->resultCode != PPTP_OUTCALL_CONNECT) {
306                        info->cstate = PPTP_CALL_NONE;
307                        break;
308                }
309
310                cid = &pptpReq.ocack->callID;
311                pcid = &pptpReq.ocack->peersCallID;
312
313                info->pac_call_id = ntohs(*cid);
314
315                if (htons(info->pns_call_id) != *pcid) {
316                        DEBUGP("%s for unknown callid %u\n",
317                                strMName[msg], ntohs(*pcid));
318                        break;
319                }
320
321                DEBUGP("%s, CID=%X, PCID=%X\n", strMName[msg],
322                        ntohs(*cid), ntohs(*pcid));
323
324                info->cstate = PPTP_CALL_OUT_CONF;
325
326                seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph);
327                if (exp_gre(ct, seq, *cid, *pcid) != 0)
328                        printk("ip_conntrack_pptp: error during exp_gre\n");
329                break;
330
331        case PPTP_IN_CALL_REQUEST:
332                /* server tells us about incoming call request */
333                if (info->sstate != PPTP_SESSION_CONFIRMED) {
334                        DEBUGP("%s but no session\n", strMName[msg]);
335                        break;
336                }
337                pcid = &pptpReq.icack->peersCallID;
338                DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid));
339                info->cstate = PPTP_CALL_IN_REQ;
340                info->pac_call_id= ntohs(*pcid);
341                break;
342
343        case PPTP_IN_CALL_CONNECT:
344                /* server tells us about incoming call established */
345                if (info->sstate != PPTP_SESSION_CONFIRMED) {
346                        DEBUGP("%s but no session\n", strMName[msg]);
347                        break;
348                }
349                if (info->sstate != PPTP_CALL_IN_REP
350                    && info->sstate != PPTP_CALL_IN_CONF) {
351                        DEBUGP("%s but never sent IN_CALL_REPLY\n",
352                                strMName[msg]);
353                        break;
354                }
355
356                pcid = &pptpReq.iccon->peersCallID;
357                cid = &info->pac_call_id;
358
359                if (info->pns_call_id != ntohs(*pcid)) {
360                        DEBUGP("%s for unknown CallID %u\n",
361                                strMName[msg], ntohs(*cid));
362                        break;
363                }
364
365                DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid));
366                info->cstate = PPTP_CALL_IN_CONF;
367
368                /* we expect a GRE connection from PAC to PNS */
369                seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph);
370                if (exp_gre(ct, seq, *cid, *pcid) != 0)
371                        printk("ip_conntrack_pptp: error during exp_gre\n");
372
373                break;
374
375        case PPTP_CALL_DISCONNECT_NOTIFY:
376                /* server confirms disconnect */
377                cid = &pptpReq.disc->callID;
378                DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid));
379                info->cstate = PPTP_CALL_NONE;
380
381                /* untrack this call id, unexpect GRE packets */
382                pptp_timeout_related(ct);
383                break;
384
385        case PPTP_WAN_ERROR_NOTIFY:
386                break;
387
388        case PPTP_ECHO_REQUEST:
389        case PPTP_ECHO_REPLY:
390                /* I don't have to explain these ;) */
391                break;
392        default:
393                DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)
394                        ? strMName[msg]:strMName[0], msg);
395                break;
396        }
397
398        return NF_ACCEPT;
399
400}
401
402static inline int
403pptp_outbound_pkt(struct tcphdr *tcph,
404                  struct pptp_pkt_hdr *pptph,
405                  size_t datalen,
406                  struct ip_conntrack *ct,
407                  enum ip_conntrack_info ctinfo)
408{
409        struct PptpControlHeader *ctlh;
410        union pptp_ctrl_union pptpReq;
411        struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
412        u_int16_t msg, *cid, *pcid;
413
414        ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
415        pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh));
416
417        msg = ntohs(ctlh->messageType);
418        DEBUGP("outbound control message %s\n", strMName[msg]);
419
420        switch (msg) {
421        case PPTP_START_SESSION_REQUEST:
422                /* client requests for new control session */
423                if (info->sstate != PPTP_SESSION_NONE) {
424                        DEBUGP("%s but we already have one",
425                                strMName[msg]);
426                }
427                info->sstate = PPTP_SESSION_REQUESTED;
428                break;
429        case PPTP_STOP_SESSION_REQUEST:
430                /* client requests end of control session */
431                info->sstate = PPTP_SESSION_STOPREQ;
432                break;
433
434        case PPTP_OUT_CALL_REQUEST:
435                /* client initiating connection to server */
436                if (info->sstate != PPTP_SESSION_CONFIRMED) {
437                        DEBUGP("%s but no session\n",
438                                strMName[msg]);
439                        break;
440                }
441                info->cstate = PPTP_CALL_OUT_REQ;
442                /* track PNS call id */
443                cid = &pptpReq.ocreq->callID;
444                DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid));
445                info->pns_call_id = ntohs(*cid);
446                break;
447        case PPTP_IN_CALL_REPLY:
448                /* client answers incoming call */
449                if (info->cstate != PPTP_CALL_IN_REQ
450                    && info->cstate != PPTP_CALL_IN_REP) {
451                        DEBUGP("%s without incall_req\n",
452                                strMName[msg]);
453                        break;
454                }
455                if (pptpReq.icack->resultCode != PPTP_INCALL_ACCEPT) {
456                        info->cstate = PPTP_CALL_NONE;
457                        break;
458                }
459                pcid = &pptpReq.icack->peersCallID;
460                if (info->pac_call_id != ntohs(*pcid)) {
461                        DEBUGP("%s for unknown call %u\n",
462                                strMName[msg], ntohs(*pcid));
463                        break;
464                }
465                DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*pcid));
466                /* part two of the three-way handshake */
467                info->cstate = PPTP_CALL_IN_REP;
468                info->pns_call_id = ntohs(pptpReq.icack->callID);
469                break;
470
471        case PPTP_CALL_CLEAR_REQUEST:
472                /* client requests hangup of call */
473                if (info->sstate != PPTP_SESSION_CONFIRMED) {
474                        DEBUGP("CLEAR_CALL but no session\n");
475                        break;
476                }
477                /* FUTURE: iterate over all calls and check if
478                 * call ID is valid.  We don't do this without newnat,
479                 * because we only know about last call */
480                info->cstate = PPTP_CALL_CLEAR_REQ;
481                break;
482        case PPTP_SET_LINK_INFO:
483                break;
484        case PPTP_ECHO_REQUEST:
485        case PPTP_ECHO_REPLY:
486                /* I don't have to explain these ;) */
487                break;
488        default:
489                DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)?
490                        strMName[msg]:strMName[0], msg);
491                /* unknown: no need to create GRE masq table entry */
492                break;
493        }
494
495        return NF_ACCEPT;
496}
497
498
499/* track caller id inside control connection, call expect_related */
500static int
501conntrack_pptp_help(const struct iphdr *iph, size_t len,
502                    struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
503
504{
505        struct pptp_pkt_hdr *pptph;
506
507        struct tcphdr *tcph = (void *) iph + iph->ihl * 4;
508        u_int32_t tcplen = len - iph->ihl * 4;
509        u_int32_t datalen = tcplen - tcph->doff * 4;
510        void *datalimit;
511        int dir = CTINFO2DIR(ctinfo);
512        struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
513
514        int oldsstate, oldcstate;
515        int ret;
516
517        /* don't do any tracking before tcp handshake complete */
518        if (ctinfo != IP_CT_ESTABLISHED
519            && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
520                DEBUGP("ctinfo = %u, skipping\n", ctinfo);
521                return NF_ACCEPT;
522        }
523
524        /* not a complete TCP header? */
525        if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff * 4) {
526                DEBUGP("tcplen = %u\n", tcplen);
527                return NF_ACCEPT;
528        }
529
530        /* checksum invalid? */
531        if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
532                        csum_partial((char *) tcph, tcplen, 0))) {
533                printk(KERN_NOTICE __FILE__ ": bad csum\n");
534                /* W2K PPTP server sends TCP packets with wrong checksum :(( */
535                //return NF_ACCEPT;
536        }
537
538        if (tcph->fin || tcph->rst) {
539                DEBUGP("RST/FIN received, timeouting GRE\n");
540                /* can't do this after real newnat */
541                info->cstate = PPTP_CALL_NONE;
542
543                /* untrack this call id, unexpect GRE packets */
544                pptp_timeout_related(ct);
545        }
546
547
548        pptph = (struct pptp_pkt_hdr *) ((void *) tcph + tcph->doff * 4);
549        datalimit = (void *) pptph + datalen;
550
551        /* not a full pptp packet header? */
552        if ((void *) pptph+sizeof(*pptph) >= datalimit) {
553                DEBUGP("no full PPTP header, can't track\n");
554                return NF_ACCEPT;
555        }
556
557        /* if it's not a control message we can't do anything with it */
558        if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL ||
559            ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) {
560                DEBUGP("not a control packet\n");
561                return NF_ACCEPT;
562        }
563
564        oldsstate = info->sstate;
565        oldcstate = info->cstate;
566
567        LOCK_BH(&ip_pptp_lock);
568
569        /* FIXME: We just blindly assume that the control connection is always
570         * established from PNS->PAC.  However, RFC makes no guarantee */
571        if (dir == IP_CT_DIR_ORIGINAL)
572                /* client -> server (PNS -> PAC) */
573                ret = pptp_outbound_pkt(tcph, pptph, datalen, ct, ctinfo);
574        else
575                /* server -> client (PAC -> PNS) */
576                ret = pptp_inbound_pkt(tcph, pptph, datalen, ct, ctinfo);
577        DEBUGP("sstate: %d->%d, cstate: %d->%d\n",
578                oldsstate, info->sstate, oldcstate, info->cstate);
579        UNLOCK_BH(&ip_pptp_lock);
580
581        return ret;
582}
583
584/* control protocol helper */
585static struct ip_conntrack_helper pptp = {
586        .list = { NULL, NULL },
587        .name = "pptp",
588        .flags = IP_CT_HELPER_F_REUSE_EXPECT,
589        .me = THIS_MODULE,
590        .max_expected = 2,
591        .timeout = 0,
592        .tuple = { .src = { .ip = 0,
593                             .u = { .tcp = { .port =
594                                    __constant_htons(PPTP_CONTROL_PORT) } }
595                          },
596                   .dst = { .ip = 0,
597                            .u = { .all = 0 },
598                            .protonum = IPPROTO_TCP
599                          }
600                 },
601        .mask = { .src = { .ip = 0,
602                           .u = { .tcp = { .port = 0xffff } }
603                         },
604                  .dst = { .ip = 0,
605                           .u = { .all = 0 },
606                           .protonum = 0xffff
607                          }
608                },
609        .help = conntrack_pptp_help
610};
611
612/* ip_conntrack_pptp initialization */
613static int __init init(void)
614{
615        int retcode;
616
617        DEBUGP(__FILE__ ": registering helper\n");
618        if ((retcode = ip_conntrack_helper_register(&pptp))) {
619                printk(KERN_ERR "Unable to register conntrack application "
620                                "helper for pptp: %d\n", retcode);
621                return -EIO;
622        }
623
624        printk("ip_conntrack_pptp version %s loaded\n", IP_CT_PPTP_VERSION);
625        return 0;
626}
627
628static void __exit fini(void)
629{
630        ip_conntrack_helper_unregister(&pptp);
631        printk("ip_conntrack_pptp version %s unloaded\n", IP_CT_PPTP_VERSION);
632}
633
634module_init(init);
635module_exit(fini);
636
637EXPORT_SYMBOL(ip_pptp_lock);
638