dhcpc: let server know we don't like oversized packets.
[oweals/busybox.git] / networking / udhcp / options.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * options.c -- DHCP server option packet tools
4  * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
5  */
6
7 #include "common.h"
8 #include "dhcpd.h"
9 #include "options.h"
10
11
12 /* Supported options are easily added here */
13 const struct dhcp_option dhcp_options[] = {
14         /* opt_name[12] flags                                   code */
15         {"subnet",      OPTION_IP | OPTION_REQ,                 0x01},   /* DHCP_SUBNET         */
16         {"timezone",    OPTION_S32,                             0x02},   /* DHCP_TIME_OFFSET    */
17         {"router",      OPTION_IP | OPTION_LIST | OPTION_REQ,   0x03},   /* DHCP_ROUTER         */
18         {"timesvr",     OPTION_IP | OPTION_LIST,                0x04},   /* DHCP_TIME_SERVER    */
19         {"namesvr",     OPTION_IP | OPTION_LIST,                0x05},   /* DHCP_NAME_SERVER    */
20         {"dns",         OPTION_IP | OPTION_LIST | OPTION_REQ,   0x06},   /* DHCP_DNS_SERVER     */
21         {"logsvr",      OPTION_IP | OPTION_LIST,                0x07},   /* DHCP_LOG_SERVER     */
22         {"cookiesvr",   OPTION_IP | OPTION_LIST,                0x08},   /* DHCP_COOKIE_SERVER  */
23         {"lprsvr",      OPTION_IP | OPTION_LIST,                0x09},   /* DHCP_LPR_SERVER     */
24         {"hostname",    OPTION_STRING | OPTION_REQ,             0x0c},   /* DHCP_HOST_NAME      */
25         {"bootsize",    OPTION_U16,                             0x0d},   /* DHCP_BOOT_SIZE      */
26         {"domain",      OPTION_STRING | OPTION_LIST | OPTION_REQ, 0x0f}, /* DHCP_DOMAIN_NAME    */
27         {"swapsvr",     OPTION_IP,                              0x10},   /* DHCP_SWAP_SERVER    */
28         {"rootpath",    OPTION_STRING,                          0x11},   /* DHCP_ROOT_PATH      */
29         {"ipttl",       OPTION_U8,                              0x17},   /* DHCP_IP_TTL         */
30         {"mtu",         OPTION_U16,                             0x1a},   /* DHCP_MTU            */
31         {"broadcast",   OPTION_IP | OPTION_REQ,                 0x1c},   /* DHCP_BROADCAST      */
32         {"nisdomain",   OPTION_STRING | OPTION_REQ,             0x28},   /* DHCP_NTP_SERVER     */
33         {"nissrv",      OPTION_IP | OPTION_LIST | OPTION_REQ,   0x29},   /* DHCP_WINS_SERVER    */
34         {"ntpsrv",      OPTION_IP | OPTION_LIST | OPTION_REQ,   0x2a},   /* DHCP_REQUESTED_IP   */
35         {"wins",        OPTION_IP | OPTION_LIST,                0x2c},   /* DHCP_LEASE_TIME     */
36         {"requestip",   OPTION_IP,                              0x32},   /* DHCP_OPTION_OVER    */
37         {"lease",       OPTION_U32,                             0x33},   /* DHCP_MESSAGE_TYPE   */
38         {"dhcptype",    OPTION_U8,                              0x35},   /* DHCP_SERVER_ID      */
39         {"serverid",    OPTION_IP,                              0x36},   /* DHCP_PARAM_REQ      */
40         {"message",     OPTION_STRING,                          0x38},   /* DHCP_MESSAGE        */
41 // TODO: 1) some options should not be parsed & passed to script -
42 // maxsize sure should not, since it cannot appear in server responses!
43 // grep for opt_name is fix the mess.
44 // 2) Using fixed-sized char[] vector wastes space.
45         {"maxsize",     OPTION_U16,                             0x39},   /* DHCP_MAX_SIZE       */
46         {"vendorclass", OPTION_STRING,                          0x3C},   /* DHCP_VENDOR         */
47         {"clientid",    OPTION_STRING,                          0x3D},   /* DHCP_CLIENT_ID      */
48         {"tftp",        OPTION_STRING,                          0x42},
49         {"bootfile",    OPTION_STRING,                          0x43},
50         {"userclass",   OPTION_STRING,                          0x4D},
51 #if ENABLE_FEATURE_RFC3397
52         {"search",      OPTION_STR1035 | OPTION_LIST | OPTION_REQ, 0x77},
53 #endif
54         /* MSIE's "Web Proxy Autodiscovery Protocol" support */
55         {"wpad",        OPTION_STRING,                          0xfc},
56         {} /* zero-padded terminating entry */
57 };
58
59
60 /* Lengths of the different option types */
61 const unsigned char option_lengths[] ALIGN1 = {
62         [OPTION_IP] =      4,
63         [OPTION_IP_PAIR] = 8,
64         [OPTION_BOOLEAN] = 1,
65         [OPTION_STRING] =  1,
66 #if ENABLE_FEATURE_RFC3397
67         [OPTION_STR1035] = 1,
68 #endif
69         [OPTION_U8] =      1,
70         [OPTION_U16] =     2,
71         [OPTION_S16] =     2,
72         [OPTION_U32] =     4,
73         [OPTION_S32] =     4
74 };
75
76
77 /* get an option with bounds checking (warning, not aligned). */
78 uint8_t *get_option(struct dhcpMessage *packet, int code)
79 {
80         int i, length;
81         uint8_t *optionptr;
82         int over = 0;
83         int curr = OPTION_FIELD;
84
85         optionptr = packet->options;
86         i = 0;
87         length = sizeof(packet->options);
88         while (1) {
89                 if (i >= length) {
90                         bb_error_msg("bogus packet, option fields too long");
91                         return NULL;
92                 }
93                 if (optionptr[i + OPT_CODE] == code) {
94                         if (i + 1 + optionptr[i + OPT_LEN] >= length) {
95                                 bb_error_msg("bogus packet, option fields too long");
96                                 return NULL;
97                         }
98                         return optionptr + i + 2;
99                 }
100                 switch (optionptr[i + OPT_CODE]) {
101                 case DHCP_PADDING:
102                         i++;
103                         break;
104                 case DHCP_OPTION_OVER:
105                         if (i + 1 + optionptr[i + OPT_LEN] >= length) {
106                                 bb_error_msg("bogus packet, option fields too long");
107                                 return NULL;
108                         }
109                         over = optionptr[i + 3];
110                         i += optionptr[OPT_LEN] + 2;
111                         break;
112                 case DHCP_END:
113                         if (curr == OPTION_FIELD && (over & FILE_FIELD)) {
114                                 optionptr = packet->file;
115                                 i = 0;
116                                 length = sizeof(packet->file);
117                                 curr = FILE_FIELD;
118                         } else if (curr == FILE_FIELD && (over & SNAME_FIELD)) {
119                                 optionptr = packet->sname;
120                                 i = 0;
121                                 length = sizeof(packet->sname);
122                                 curr = SNAME_FIELD;
123                         } else
124                                 return NULL;
125                         break;
126                 default:
127                         i += optionptr[OPT_LEN + i] + 2;
128                 }
129         }
130         return NULL;
131 }
132
133
134 /* return the position of the 'end' option (no bounds checking) */
135 int end_option(uint8_t *optionptr)
136 {
137         int i = 0;
138
139         while (optionptr[i] != DHCP_END) {
140                 if (optionptr[i] == DHCP_PADDING) i++;
141                 else i += optionptr[i + OPT_LEN] + 2;
142         }
143         return i;
144 }
145
146
147 /* add an option string to the options (an option string contains an option code,
148  * length, then data) */
149 int add_option_string(uint8_t *optionptr, uint8_t *string)
150 {
151         int end = end_option(optionptr);
152
153         /* end position + string length + option code/length + end option */
154         if (end + string[OPT_LEN] + 2 + 1 >= DHCP_OPTIONS_BUFSIZE) {
155                 bb_error_msg("option 0x%02x did not fit into the packet",
156                                 string[OPT_CODE]);
157                 return 0;
158         }
159         DEBUG("adding option 0x%02x", string[OPT_CODE]);
160         memcpy(optionptr + end, string, string[OPT_LEN] + 2);
161         optionptr[end + string[OPT_LEN] + 2] = DHCP_END;
162         return string[OPT_LEN] + 2;
163 }
164
165
166 /* add a one to four byte option to a packet */
167 int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data)
168 {
169         const struct dhcp_option *dh;
170
171         for (dh = dhcp_options; dh->code; dh++) {
172                 if (dh->code == code) {
173                         uint8_t option[6], len;
174
175                         option[OPT_CODE] = code;
176                         len = option_lengths[dh->flags & TYPE_MASK];
177                         option[OPT_LEN] = len;
178                         if (BB_BIG_ENDIAN) data <<= 8 * (4 - len);
179                         /* This memcpy is for broken processors which can't
180                          * handle a simple unaligned 32-bit assignment */
181                         memcpy(&option[OPT_DATA], &data, 4);
182                         return add_option_string(optionptr, option);
183                 }
184         }
185
186         bb_error_msg("cannot add option 0x%02x", code);
187         return 0;
188 }