1/***********************************************************************
2*
3* common.c
4*
5* Implementation of user-space PPPoE redirector for Linux.
6*
7* Common functions used by PPPoE client and server
8*
9* Copyright (C) 2000 by Roaring Penguin Software Inc.
10*
11* This program may be distributed according to the terms of the GNU
12* General Public License, version 2 or (at your option) any later version.
13*
14* LIC: GPL
15*
16***********************************************************************/
17
18static char const RCSID[] =
19"$Id: common.c,v 1.1.1.1 2008/10/15 03:30:51 james26_jang Exp $";
20/* For vsnprintf prototype */
21#define _ISOC99_SOURCE 1
22
23#include "pppoe.h"
24
25
26#ifdef HAVE_SYSLOG_H
27#include <syslog.h>
28#endif
29
30#include <string.h>
31#include <errno.h>
32#include <stdlib.h>
33#include <stdarg.h>
34
35#ifdef HAVE_UNISTD_H
36#include <unistd.h>
37#endif
38
39/**********************************************************************
40*%FUNCTION: parsePacket
41*%ARGUMENTS:
42* packet -- the PPPoE discovery packet to parse
43* func -- function called for each tag in the packet
44* extra -- an opaque data pointer supplied to parsing function
45*%RETURNS:
46* 0 if everything went well; -1 if there was an error
47*%DESCRIPTION:
48* Parses a PPPoE discovery packet, calling "func" for each tag in the packet.
49* "func" is passed the additional argument "extra".
50***********************************************************************/
51int
52parsePacket(PPPoEPacket *packet, ParseFunc *func, void *extra)
53{
54    UINT16_t len = ntohs(packet->length);
55    unsigned char *curTag;
56    UINT16_t tagType, tagLen;
57
58    if (packet->ver != 1) {
59	syslog(LOG_ERR, "Invalid PPPoE version (%d)", (int) packet->ver);
60	return -1;
61    }
62    if (packet->type != 1) {
63	syslog(LOG_ERR, "Invalid PPPoE type (%d)", (int) packet->type);
64	return -1;
65    }
66
67    /* Do some sanity checks on packet */
68    if (len > ETH_DATA_LEN - 6) { /* 6-byte overhead for PPPoE header */
69	syslog(LOG_ERR, "Invalid PPPoE packet length (%u)", len);
70	return -1;
71    }
72
73    /* Step through the tags */
74    curTag = packet->payload;
75    while(curTag - packet->payload < len) {
76	/* Alignment is not guaranteed, so do this by hand... */
77	tagType = (((UINT16_t) curTag[0]) << 8) +
78	    (UINT16_t) curTag[1];
79	tagLen = (((UINT16_t) curTag[2]) << 8) +
80	    (UINT16_t) curTag[3];
81	if (tagType == TAG_END_OF_LIST) {
82	    return 0;
83	}
84	if ((curTag - packet->payload) + tagLen + TAG_HDR_SIZE > len) {
85	    syslog(LOG_ERR, "Invalid PPPoE tag length (%u)", tagLen);
86	    return -1;
87	}
88	func(tagType, tagLen, curTag+TAG_HDR_SIZE, extra);
89	curTag = curTag + TAG_HDR_SIZE + tagLen;
90    }
91    return 0;
92}
93
94/**********************************************************************
95*%FUNCTION: findTag
96*%ARGUMENTS:
97* packet -- the PPPoE discovery packet to parse
98* type -- the type of the tag to look for
99* tag -- will be filled in with tag contents
100*%RETURNS:
101* A pointer to the tag if one of the specified type is found; NULL
102* otherwise.
103*%DESCRIPTION:
104* Looks for a specific tag type.
105***********************************************************************/
106unsigned char *
107findTag(PPPoEPacket *packet, UINT16_t type, PPPoETag *tag)
108{
109    UINT16_t len = ntohs(packet->length);
110    unsigned char *curTag;
111    UINT16_t tagType, tagLen;
112
113    if (packet->ver != 1) {
114	syslog(LOG_ERR, "Invalid PPPoE version (%d)", (int) packet->ver);
115	return NULL;
116    }
117    if (packet->type != 1) {
118	syslog(LOG_ERR, "Invalid PPPoE type (%d)", (int) packet->type);
119	return NULL;
120    }
121
122    /* Do some sanity checks on packet */
123    if (len > ETH_DATA_LEN - 6) { /* 6-byte overhead for PPPoE header */
124	syslog(LOG_ERR, "Invalid PPPoE packet length (%u)", len);
125	return NULL;
126    }
127
128    /* Step through the tags */
129    curTag = packet->payload;
130    while(curTag - packet->payload < len) {
131	/* Alignment is not guaranteed, so do this by hand... */
132	tagType = (((UINT16_t) curTag[0]) << 8) +
133	    (UINT16_t) curTag[1];
134	tagLen = (((UINT16_t) curTag[2]) << 8) +
135	    (UINT16_t) curTag[3];
136	if (tagType == TAG_END_OF_LIST) {
137	    return NULL;
138	}
139	if ((curTag - packet->payload) + tagLen + TAG_HDR_SIZE > len) {
140	    syslog(LOG_ERR, "Invalid PPPoE tag length (%u)", tagLen);
141	    return NULL;
142	}
143	if (tagType == type) {
144	    memcpy(tag, curTag, tagLen + TAG_HDR_SIZE);
145	    return curTag;
146	}
147	curTag = curTag + TAG_HDR_SIZE + tagLen;
148    }
149    return NULL;
150}
151
152/**********************************************************************
153*%FUNCTION: printErr
154*%ARGUMENTS:
155* str -- error message
156*%RETURNS:
157* Nothing
158*%DESCRIPTION:
159* Prints a message to stderr and syslog.
160***********************************************************************/
161void
162printErr(char const *str)
163{
164    fprintf(stderr, "pppoe: %s\n", str);
165    syslog(LOG_ERR, "%s", str);
166}
167
168
169/**********************************************************************
170*%FUNCTION: strDup
171*%ARGUMENTS:
172* str -- string to copy
173*%RETURNS:
174* A malloc'd copy of str.  Exits if malloc fails.
175***********************************************************************/
176char *
177strDup(char const *str)
178{
179    char *copy = malloc(strlen(str)+1);
180    if (!copy) {
181	rp_fatal("strdup failed");
182    }
183    strcpy(copy, str);
184    return copy;
185}
186
187/**********************************************************************
188*%FUNCTION: computeTCPChecksum
189*%ARGUMENTS:
190* ipHdr -- pointer to IP header
191* tcpHdr -- pointer to TCP header
192*%RETURNS:
193* The computed TCP checksum
194***********************************************************************/
195UINT16_t
196computeTCPChecksum(unsigned char *ipHdr, unsigned char *tcpHdr)
197{
198    UINT32_t sum = 0;
199    UINT16_t count = ipHdr[2] * 256 + ipHdr[3];
200    unsigned char *addr = tcpHdr;
201    unsigned char pseudoHeader[12];
202
203    /* Count number of bytes in TCP header and data */
204    count -= (ipHdr[0] & 0x0F) * 4;
205
206    memcpy(pseudoHeader, ipHdr+12, 8);
207    pseudoHeader[8] = 0;
208    pseudoHeader[9] = ipHdr[9];
209    pseudoHeader[10] = (count >> 8) & 0xFF;
210    pseudoHeader[11] = (count & 0xFF);
211
212    /* Checksum the pseudo-header */
213    sum += * (UINT16_t *) pseudoHeader;
214    sum += * ((UINT16_t *) (pseudoHeader+2));
215    sum += * ((UINT16_t *) (pseudoHeader+4));
216    sum += * ((UINT16_t *) (pseudoHeader+6));
217    sum += * ((UINT16_t *) (pseudoHeader+8));
218    sum += * ((UINT16_t *) (pseudoHeader+10));
219
220    /* Checksum the TCP header and data */
221    while (count > 1) {
222	sum += * (UINT16_t *) addr;
223	addr += 2;
224	count -= 2;
225    }
226    if (count > 0) {
227	sum += *addr;
228    }
229
230    while(sum >> 16) {
231	sum = (sum & 0xffff) + (sum >> 16);
232    }
233    return (UINT16_t) (~sum & 0xFFFF);
234}
235
236/**********************************************************************
237*%FUNCTION: clampMSS
238*%ARGUMENTS:
239* packet -- PPPoE session packet
240* dir -- either "incoming" or "outgoing"
241* clampMss -- clamp value
242*%RETURNS:
243* Nothing
244*%DESCRIPTION:
245* Clamps MSS option if TCP SYN flag is set.
246***********************************************************************/
247void
248clampMSS(PPPoEPacket *packet, char const *dir, int clampMss)
249{
250    unsigned char *tcpHdr;
251    unsigned char *ipHdr;
252    unsigned char *opt;
253    unsigned char *endHdr;
254    unsigned char *mssopt = NULL;
255    UINT16_t csum;
256
257    int len, minlen;
258
259    /* check PPP protocol type */
260    if (packet->payload[0] & 0x01) {
261        /* 8 bit protocol type */
262
263        /* Is it IPv4? */
264        if (packet->payload[0] != 0x21) {
265            /* Nope, ignore it */
266            return;
267        }
268
269        ipHdr = packet->payload + 1;
270	minlen = 41;
271    } else {
272        /* 16 bit protocol type */
273
274        /* Is it IPv4? */
275        if (packet->payload[0] != 0x00 ||
276            packet->payload[1] != 0x21) {
277            /* Nope, ignore it */
278            return;
279        }
280
281        ipHdr = packet->payload + 2;
282	minlen = 42;
283    }
284
285    /* Is it too short? */
286    len = (int) ntohs(packet->length);
287    if (len < minlen) {
288	/* 20 byte IP header; 20 byte TCP header; at least 1 or 2 byte PPP protocol */
289	return;
290    }
291
292    /* Verify once more that it's IPv4 */
293    if ((ipHdr[0] & 0xF0) != 0x40) {
294	return;
295    }
296
297    /* Is it a fragment that's not at the beginning of the packet? */
298    if ((ipHdr[6] & 0x1F) || ipHdr[7]) {
299	/* Yup, don't touch! */
300	return;
301    }
302    /* Is it TCP? */
303    if (ipHdr[9] != 0x06) {
304	return;
305    }
306
307    /* Get start of TCP header */
308    tcpHdr = ipHdr + (ipHdr[0] & 0x0F) * 4;
309
310    /* Is SYN set? */
311    if (!(tcpHdr[13] & 0x02)) {
312	return;
313    }
314
315    /* Compute and verify TCP checksum -- do not touch a packet with a bad
316       checksum */
317    csum = computeTCPChecksum(ipHdr, tcpHdr);
318    if (csum) {
319	syslog(LOG_ERR, "Bad TCP checksum %x", (unsigned int) csum);
320
321	/* Upper layers will drop it */
322	return;
323    }
324
325    /* Look for existing MSS option */
326    endHdr = tcpHdr + ((tcpHdr[12] & 0xF0) >> 2);
327    opt = tcpHdr + 20;
328    while (opt < endHdr) {
329	if (!*opt) break;	/* End of options */
330	switch(*opt) {
331	case 1:
332	    opt++;
333	    break;
334
335	case 2:
336	    if (opt[1] != 4) {
337		/* Something fishy about MSS option length. */
338		syslog(LOG_ERR,
339		       "Bogus length for MSS option (%u) from %u.%u.%u.%u",
340		       (unsigned int) opt[1],
341		       (unsigned int) ipHdr[12],
342		       (unsigned int) ipHdr[13],
343		       (unsigned int) ipHdr[14],
344		       (unsigned int) ipHdr[15]);
345		return;
346	    }
347	    mssopt = opt;
348	    break;
349	default:
350	    if (opt[1] < 2) {
351		/* Someone's trying to attack us? */
352		syslog(LOG_ERR,
353		       "Bogus TCP option length (%u) from %u.%u.%u.%u",
354		       (unsigned int) opt[1],
355		       (unsigned int) ipHdr[12],
356		       (unsigned int) ipHdr[13],
357		       (unsigned int) ipHdr[14],
358		       (unsigned int) ipHdr[15]);
359		return;
360	    }
361	    opt += (opt[1]);
362	    break;
363	}
364	/* Found existing MSS option? */
365	if (mssopt) break;
366    }
367
368    /* If MSS exists and it's low enough, do nothing */
369    if (mssopt) {
370	unsigned mss = mssopt[2] * 256 + mssopt[3];
371	if (mss <= clampMss) {
372	    return;
373	}
374
375	mssopt[2] = (((unsigned) clampMss) >> 8) & 0xFF;
376	mssopt[3] = ((unsigned) clampMss) & 0xFF;
377    } else {
378	/* No MSS option.  Don't add one; we'll have to use 536. */
379	return;
380    }
381
382    /* Recompute TCP checksum */
383    tcpHdr[16] = 0;
384    tcpHdr[17] = 0;
385    csum = computeTCPChecksum(ipHdr, tcpHdr);
386    (* (UINT16_t *) (tcpHdr+16)) = csum;
387}
388
389/***********************************************************************
390*%FUNCTION: sendPADT
391*%ARGUMENTS:
392* conn -- PPPoE connection
393* msg -- if non-NULL, extra error message to include in PADT packet.
394*%RETURNS:
395* Nothing
396*%DESCRIPTION:
397* Sends a PADT packet
398***********************************************************************/
399void
400sendPADT(PPPoEConnection *conn, char const *msg)
401{
402    PPPoEPacket packet;
403    unsigned char *cursor = packet.payload;
404
405    UINT16_t plen = 0;
406
407    /* Do nothing if no session established yet */
408    if (!conn->session) return;
409
410    /* Do nothing if no discovery socket */
411    if (conn->discoverySocket < 0) return;
412
413    memcpy(packet.ethHdr.h_dest, conn->peerEth, ETH_ALEN);
414    memcpy(packet.ethHdr.h_source, conn->myEth, ETH_ALEN);
415
416    packet.ethHdr.h_proto = htons(Eth_PPPOE_Discovery);
417    packet.ver = 1;
418    packet.type = 1;
419    packet.code = CODE_PADT;
420    packet.session = conn->session;
421
422    /* Reset Session to zero so there is no possibility of
423       recursive calls to this function by any signal handler */
424    conn->session = 0;
425
426    /* If we're using Host-Uniq, copy it over */
427    if (conn->useHostUniq) {
428	PPPoETag hostUniq;
429	pid_t pid = getpid();
430	hostUniq.type = htons(TAG_HOST_UNIQ);
431	hostUniq.length = htons(sizeof(pid));
432	memcpy(hostUniq.payload, &pid, sizeof(pid));
433	memcpy(cursor, &hostUniq, sizeof(pid) + TAG_HDR_SIZE);
434	cursor += sizeof(pid) + TAG_HDR_SIZE;
435	plen += sizeof(pid) + TAG_HDR_SIZE;
436    }
437
438    /* Copy error message */
439    if (msg) {
440	PPPoETag err;
441	size_t elen = strlen(msg);
442	err.type = htons(TAG_GENERIC_ERROR);
443	err.length = htons(elen);
444	strcpy(err.payload, msg);
445	memcpy(cursor, &err, elen + TAG_HDR_SIZE);
446	cursor += elen + TAG_HDR_SIZE;
447	plen += elen + TAG_HDR_SIZE;
448    }
449
450    /* Copy cookie and relay-ID if needed */
451    if (conn->cookie.type) {
452	CHECK_ROOM(cursor, packet.payload,
453		   ntohs(conn->cookie.length) + TAG_HDR_SIZE);
454	memcpy(cursor, &conn->cookie, ntohs(conn->cookie.length) + TAG_HDR_SIZE);
455	cursor += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
456	plen += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
457    }
458
459    if (conn->relayId.type) {
460	CHECK_ROOM(cursor, packet.payload,
461		   ntohs(conn->relayId.length) + TAG_HDR_SIZE);
462	memcpy(cursor, &conn->relayId, ntohs(conn->relayId.length) + TAG_HDR_SIZE);
463	cursor += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
464	plen += ntohs(conn->relayId.length) + TAG_HDR_SIZE;
465    }
466
467    packet.length = htons(plen);
468    sendPacket(conn, conn->discoverySocket, &packet, (int) (plen + HDR_SIZE));
469    if (conn->debugFile) {
470	dumpPacket(conn->debugFile, &packet, "SENT");
471	fprintf(conn->debugFile, "\n");
472	fflush(conn->debugFile);
473    }
474    syslog(LOG_INFO,"Sent PADT");
475}
476
477/***********************************************************************
478*%FUNCTION: sendPADTf
479*%ARGUMENTS:
480* conn -- PPPoE connection
481* msg -- printf-style format string
482* args -- arguments for msg
483*%RETURNS:
484* Nothing
485*%DESCRIPTION:
486* Sends a PADT packet with a formatted message
487***********************************************************************/
488void
489sendPADTf(PPPoEConnection *conn, char const *fmt, ...)
490{
491    char msg[512];
492    va_list ap;
493
494    va_start(ap, fmt);
495    vsnprintf(msg, sizeof(msg), fmt, ap);
496    va_end(ap);
497    msg[511] = 0;
498
499    sendPADT(conn, msg);
500}
501
502/**********************************************************************
503*%FUNCTION: parseLogErrs
504*%ARGUMENTS:
505* type -- tag type
506* len -- tag length
507* data -- tag data
508* extra -- extra user data
509*%RETURNS:
510* Nothing
511*%DESCRIPTION:
512* Picks error tags out of a packet and logs them.
513***********************************************************************/
514void
515parseLogErrs(UINT16_t type, UINT16_t len, unsigned char *data,
516	     void *extra)
517{
518    switch(type) {
519    case TAG_SERVICE_NAME_ERROR:
520	syslog(LOG_ERR, "PADT: Service-Name-Error: %.*s", (int) len, data);
521	fprintf(stderr, "PADT: Service-Name-Error: %.*s\n", (int) len, data);
522	break;
523    case TAG_AC_SYSTEM_ERROR:
524	syslog(LOG_ERR, "PADT: System-Error: %.*s", (int) len, data);
525	fprintf(stderr, "PADT: System-Error: %.*s\n", (int) len, data);
526	break;
527    case TAG_GENERIC_ERROR:
528	syslog(LOG_ERR, "PADT: Generic-Error: %.*s", (int) len, data);
529	fprintf(stderr, "PADT: Generic-Error: %.*s\n", (int) len, data);
530	break;
531    }
532}
533