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