1/*
2 *  OpenVPN -- An application to securely tunnel IP networks
3 *             over a single TCP/UDP port, with support for SSL/TLS-based
4 *             session authentication and key exchange,
5 *             packet encryption, packet authentication, and
6 *             packet compression.
7 *
8 *  Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
9 *
10 *  This program is free software; you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License version 2
12 *  as published by the Free Software Foundation.
13 *
14 *  This program is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 *  GNU General Public License for more details.
18 *
19 *  You should have received a copy of the GNU General Public License
20 *  along with this program (see the file COPYING included with this
21 *  distribution); if not, write to the Free Software Foundation, Inc.,
22 *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23 */
24
25#ifdef HAVE_CONFIG_H
26#include "config.h"
27#elif defined(_MSC_VER)
28#include "config-msvc.h"
29#endif
30
31#include "syshead.h"
32
33#include "dhcp.h"
34#include "socket.h"
35#include "error.h"
36
37#include "memdbg.h"
38
39static int
40get_dhcp_message_type (const struct dhcp *dhcp, const int optlen)
41{
42  const uint8_t *p = (uint8_t *) (dhcp + 1);
43  int i;
44
45  for (i = 0; i < optlen; ++i)
46    {
47      const uint8_t type = p[i];
48      const int room = optlen - i;
49      if (type == DHCP_END)           /* didn't find what we were looking for */
50	return -1;
51      else if (type == DHCP_PAD)      /* no-operation */
52	;
53      else if (type == DHCP_MSG_TYPE) /* what we are looking for */
54	{
55	  if (room >= 3)
56	    {
57	      if (p[i+1] == 1)        /* option length should be 1 */
58		return p[i+2];        /* return message type */
59	    }
60	  return -1;
61	}
62      else                            /* some other option */
63	{
64	  if (room >= 2)
65	    {
66	      const int len = p[i+1]; /* get option length */
67	      i += (len + 1);         /* advance to next option */
68	    }
69	}
70    }
71  return -1;
72}
73
74static in_addr_t
75do_extract (struct dhcp *dhcp, int optlen)
76{
77  uint8_t *p = (uint8_t *) (dhcp + 1);
78  int i;
79  in_addr_t ret = 0;
80
81  for (i = 0; i < optlen; )
82    {
83      const uint8_t type = p[i];
84      const int room = optlen - i;
85      if (type == DHCP_END)
86	break;
87      else if (type == DHCP_PAD)
88	++i;
89      else if (type == DHCP_ROUTER)
90	{
91	  if (room >= 2)
92	    {
93	      const int len = p[i+1]; /* get option length */
94	      if (len <= (room-2))
95		{
96		  /* get router IP address */
97		  if (!ret && len >= 4 && (len & 3) == 0)
98		    {
99		      memcpy (&ret, p+i+2, 4);
100		      ret = ntohl (ret);
101		    }
102		  {
103		    /* delete the router option */
104		    uint8_t *dest = p + i;
105		    const int owlen = len + 2;            /* len of data to overwrite */
106		    uint8_t *src = dest + owlen;
107		    uint8_t *end = p + optlen;
108		    const int movlen = end - src;
109		    if (movlen > 0)
110		      memmove(dest, src, movlen);         /* overwrite router option */
111		    memset(end - owlen, DHCP_PAD, owlen); /* pad tail */
112		  }
113		}
114	      else
115		break;
116	    }
117	  else
118	    break;
119	}
120      else                              /* some other option */
121	{
122	  if (room >= 2)
123	    {
124	      const int len = p[i+1];   /* get option length */
125	      i += (len + 2);           /* advance to next option */
126	    }
127	  else
128	    break;
129	}
130    }
131  return ret;
132}
133
134static uint16_t
135udp_checksum (const uint8_t *buf,
136	      const int len_udp,
137	      const uint8_t *src_addr,
138	      const uint8_t *dest_addr)
139{
140  uint16_t word16;
141  uint32_t sum = 0;
142  int i;
143
144  /* make 16 bit words out of every two adjacent 8 bit words and  */
145  /* calculate the sum of all 16 bit words */
146  for (i = 0; i < len_udp; i += 2){
147    word16 = ((buf[i] << 8) & 0xFF00) + ((i + 1 < len_udp) ? (buf[i+1] & 0xFF) : 0);
148    sum += word16;
149  }
150
151  /* add the UDP pseudo header which contains the IP source and destination addresses */
152  for (i = 0; i < 4; i += 2){
153    word16 =((src_addr[i] << 8) & 0xFF00) + (src_addr[i+1] & 0xFF);
154    sum += word16;
155  }
156  for (i = 0; i < 4; i += 2){
157    word16 =((dest_addr[i] << 8) & 0xFF00) + (dest_addr[i+1] & 0xFF);
158    sum += word16;
159  }
160
161  /* the protocol number and the length of the UDP packet */
162  sum += (uint16_t) OPENVPN_IPPROTO_UDP + (uint16_t) len_udp;
163
164  /* keep only the last 16 bits of the 32 bit calculated sum and add the carries */
165  while (sum >> 16)
166    sum = (sum & 0xFFFF) + (sum >> 16);
167
168  /* Take the one's complement of sum */
169  return ((uint16_t) ~sum);
170}
171
172in_addr_t
173dhcp_extract_router_msg (struct buffer *ipbuf)
174{
175  struct dhcp_full *df = (struct dhcp_full *) BPTR (ipbuf);
176  const int optlen = BLEN (ipbuf) - (sizeof (struct openvpn_iphdr) + sizeof (struct openvpn_udphdr) + sizeof (struct dhcp));
177
178  if (optlen >= 0
179      && df->ip.protocol == OPENVPN_IPPROTO_UDP
180      && df->udp.source == htons (BOOTPS_PORT)
181      && df->udp.dest == htons (BOOTPC_PORT)
182      && df->dhcp.op == BOOTREPLY)
183    {
184      const int message_type = get_dhcp_message_type (&df->dhcp, optlen);
185      if (message_type == DHCPACK || message_type == DHCPOFFER)
186	{
187	  /* get the router IP address while padding out all DHCP router options */
188	  const in_addr_t ret = do_extract (&df->dhcp, optlen);
189
190	  /* recompute the UDP checksum */
191	  df->udp.check = 0;
192	  df->udp.check = htons (udp_checksum ((uint8_t *) &df->udp,
193					       sizeof (struct openvpn_udphdr) + sizeof (struct dhcp) + optlen,
194					       (uint8_t *)&df->ip.saddr,
195					       (uint8_t *)&df->ip.daddr));
196
197	  /* only return the extracted Router address if DHCPACK */
198	  if (message_type == DHCPACK)
199	    {
200	      if (ret)
201		{
202		  struct gc_arena gc = gc_new ();
203		  msg (D_ROUTE, "Extracted DHCP router address: %s", print_in_addr_t (ret, 0, &gc));
204		  gc_free (&gc);
205		}
206
207	      return ret;
208	    }
209	}
210    }
211  return 0;
212}
213