Use paircursor for iteration where appropriate
[freeradius.git] / src / modules / proto_dhcp / dhcpclient.c
1 /*
2  * dhcpclient.c General radius packet debug tool.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2000,2006  The FreeRADIUS server project
21  * Copyright 2000  Miquel van Smoorenburg <miquels@cistron.nl>
22  * Copyright 2010  Alan DeKok <aland@ox.org>
23  */
24
25 RCSID("$Id$")
26
27 #include <freeradius-devel/libradius.h>
28 #include <freeradius-devel/conf.h>
29 #include <freeradius-devel/radpaths.h>
30 #include <freeradius-devel/dhcp.h>
31
32 #ifdef WITH_DHCP
33
34 #include <ctype.h>
35
36 #ifdef HAVE_GETOPT_H
37 #       include <getopt.h>
38 #endif
39
40 #include <assert.h>
41
42 static int success = 0;
43 static int retries = 3;
44 static float timeout = 5;
45
46 static int server_port = 0;
47 static int packet_code = 0;
48 static fr_ipaddr_t server_ipaddr;
49
50 static fr_ipaddr_t client_ipaddr;
51 static int client_port = 0;
52
53 static int sockfd;
54
55 static RADIUS_PACKET *request = NULL;
56 static RADIUS_PACKET *reply = NULL;
57
58 #define DHCP_CHADDR_LEN (16)
59 #define DHCP_SNAME_LEN  (64)
60 #define DHCP_FILE_LEN   (128)
61 #define DHCP_VEND_LEN   (308)
62 #define DHCP_OPTION_MAGIC_NUMBER (0x63825363)
63
64 char const *dhcpclient_version = "dhcpclient version " RADIUSD_VERSION_STRING
65 #ifdef RADIUSD_VERSION_COMMIT
66 " (git #" RADIUSD_VERSION_COMMIT ")"
67 #endif
68 ", built on " __DATE__ " at " __TIME__;
69
70 static void NEVER_RETURNS usage(void)
71 {
72         fprintf(stderr, "Usage: dhcpclient [options] server[:port] <command>\n");
73
74         fprintf(stderr, "  <command>    One of discover, request, offer\n");
75         fprintf(stderr, "  -c count    Send each packet 'count' times.\n");
76         fprintf(stderr, "  -d raddb    Set dictionary directory.\n");
77         fprintf(stderr, "  -f file     Read packets from file, not stdin.\n");
78         fprintf(stderr, "  -r retries  If timeout, retry sending the packet 'retries' times.\n");
79         fprintf(stderr, "  -v     Show program version information.\n");
80         fprintf(stderr, "  -x     Debugging mode.\n");
81
82         exit(1);
83 }
84
85
86 /*
87  *      Initialize the request.
88  */
89 static int request_init(char const *filename)
90 {
91         FILE *fp;
92         vp_cursor_t cursor;
93         VALUE_PAIR *vp;
94         int filedone = 0;
95
96         /*
97          *      Determine where to read the VP's from.
98          */
99         if (filename) {
100                 fp = fopen(filename, "r");
101                 if (!fp) {
102                         fprintf(stderr, "dhcpclient: Error opening %s: %s\n",
103                                 filename, strerror(errno));
104                         return 0;
105                 }
106         } else {
107                 fp = stdin;
108         }
109
110         request = rad_alloc(NULL, 0);
111
112         /*
113          *      Read the VP's.
114          */
115         request->vps = readvp2(NULL, fp, &filedone, "dhcpclient:");
116         if (!request->vps) {
117                 rad_free(&request);
118                 if (fp != stdin) fclose(fp);
119                 return 1;
120         }
121
122         /*
123          *      Fix / set various options
124          */
125         for (vp = paircursor(&cursor, &request->vps); vp; vp = pairnext(&cursor)) {
126                 switch (vp->da->attr) {
127                 default:
128                         break;
129                         
130                         /*
131                          *      Allow it to set the packet type in
132                          *      the attributes read from the file.
133                          */
134                 case PW_PACKET_TYPE:
135                         request->code = vp->vp_integer;
136                         break;
137                         
138                 case PW_PACKET_DST_PORT:
139                         request->dst_port = (vp->vp_integer & 0xffff);
140                         break;
141                         
142                 case PW_PACKET_DST_IP_ADDRESS:
143                         request->dst_ipaddr.af = AF_INET;
144                         request->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
145                         break;
146                         
147                 case PW_PACKET_DST_IPV6_ADDRESS:
148                         request->dst_ipaddr.af = AF_INET6;
149                         request->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
150                         break;
151                         
152                 case PW_PACKET_SRC_PORT:
153                         request->src_port = (vp->vp_integer & 0xffff);
154                         break;
155                         
156                 case PW_PACKET_SRC_IP_ADDRESS:
157                         request->src_ipaddr.af = AF_INET;
158                         request->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
159                         break;
160                         
161                 case PW_PACKET_SRC_IPV6_ADDRESS:
162                         request->src_ipaddr.af = AF_INET6;
163                         request->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
164                         break;
165                 } /* switch over the attribute */
166                         
167         } /* loop over the VP's we read in */
168
169         if (fp != stdin) fclose(fp);
170
171         /*
172          *      And we're done.
173          */
174         return 1;
175 }
176
177 static char const *dhcp_header_names[] = {
178         "DHCP-Opcode",
179         "DHCP-Hardware-Type",
180         "DHCP-Hardware-Address-Length",
181         "DHCP-Hop-Count",
182         "DHCP-Transaction-Id",
183         "DHCP-Number-of-Seconds",
184         "DHCP-Flags",
185         "DHCP-Client-IP-Address",
186         "DHCP-Your-IP-Address",
187         "DHCP-Server-IP-Address",
188         "DHCP-Gateway-IP-Address",
189         "DHCP-Client-Hardware-Address",
190         "DHCP-Server-Host-Name",
191         "DHCP-Boot-Filename",
192
193         NULL
194 };
195
196 static int dhcp_header_sizes[] = {
197         1, 1, 1, 1,
198         4, 2, 2, 4,
199         4, 4, 4,
200         DHCP_CHADDR_LEN,
201         DHCP_SNAME_LEN,
202         DHCP_FILE_LEN
203 };
204
205
206 static void print_hex(RADIUS_PACKET *packet)
207 {
208         int i, j;
209         const uint8_t *p, *a;
210
211         if (!packet->data) return;
212
213         if (packet->data_len < 244) {
214                 printf("Huh?\n");
215                 return;
216         }
217
218         printf("----------------------------------------------------------------------\n");
219         fflush(stdout);
220
221         p = packet->data;
222         for (i = 0; i < 14; i++) {
223                 printf("%s = 0x", dhcp_header_names[i]);
224                 for (j = 0; j < dhcp_header_sizes[i]; j++) {
225                         printf("%02x", p[j]);
226                         
227                 }
228                 printf("\n");
229                 p += dhcp_header_sizes[i];
230         }
231
232         /*
233          *      Magic number
234          */
235         printf("%02x %02x %02x %02x\n",
236                p[0], p[1], p[2], p[3]);
237         p += 4;
238
239         while (p < (packet->data + packet->data_len)) {
240
241                 if (*p == 0) break;
242                 if (*p == 255) break; /* end of options signifier */
243                 if ((p + 2) > (packet->data + packet->data_len)) break;
244
245                 printf("%02x  %02x  ", p[0], p[1]);
246                 a = p + 2;
247                 
248                 for (i = 0; i < p[1]; i++) {
249                         if ((i > 0) && ((i & 0x0f) == 0x00))
250                                 printf("\t\t");
251                         printf("%02x ", a[i]);
252                         if ((i & 0x0f) == 0x0f) printf("\n");
253                 }
254                 
255                 if ((p[1] & 0x0f) != 0x00) printf("\n");
256                 
257                 p += p[1] + 2;
258         }
259         printf("\n----------------------------------------------------------------------\n");
260         fflush(stdout);
261 }
262
263 int main(int argc, char **argv)
264 {
265         char *p;
266         int c;
267         char const *radius_dir = RADDBDIR;
268         char const *filename = NULL;
269
270         fr_debug_flag = 0;
271
272         while ((c = getopt(argc, argv, "d:f:hr:t:vx")) != EOF) switch(c) {
273                 case 'd':
274                         radius_dir = optarg;
275                         break;
276                 case 'f':
277                         filename = optarg;
278                         break;
279                 case 'r':
280                         if (!isdigit((int) *optarg))
281                                 usage();
282                         retries = atoi(optarg);
283                         if ((retries == 0) || (retries > 1000)) usage();
284                         break;
285                 case 't':
286                         if (!isdigit((int) *optarg))
287                                 usage();
288                         timeout = atof(optarg);
289                         break;
290                 case 'v':
291                         printf("%s\n", dhcpclient_version);
292                         exit(0);
293                         break;
294                 case 'x':
295                         fr_debug_flag++;
296                         fr_log_fp = stdout;
297                         break;
298                 case 'h':
299                 default:
300                         usage();
301                         break;
302         }
303         argc -= (optind - 1);
304         argv += (optind - 1);
305
306         if (argc < 2) usage();
307
308         if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
309                 fr_perror("dhcpclient");
310                 return 1;
311         }
312
313         /*
314          *      Resolve hostname.
315          */
316         server_ipaddr.af = AF_INET;
317         if (strcmp(argv[1], "-") != 0) {
318                 char const *hostname = argv[1];
319                 char const *portname = argv[1];
320                 char buffer[256];
321
322                 if (*argv[1] == '[') { /* IPv6 URL encoded */
323                         p = strchr(argv[1], ']');
324                         if ((size_t) (p - argv[1]) >= sizeof(buffer)) {
325                                 usage();
326                         }
327
328                         memcpy(buffer, argv[1] + 1, p - argv[1] - 1);
329                         buffer[p - argv[1] - 1] = '\0';
330
331                         hostname = buffer;
332                         portname = p + 1;
333
334                 }
335                 p = strchr(portname, ':');
336                 if (p && (strchr(p + 1, ':') == NULL)) {
337                         *p = '\0';
338                         portname = p + 1;
339                 } else {
340                         portname = NULL;
341                 }
342
343                 if (ip_hton(hostname, AF_INET, &server_ipaddr) < 0) {
344                         fprintf(stderr, "dhcpclient: Failed to find IP address for host %s: %s\n", hostname, strerror(errno));
345                         exit(1);
346                 }
347
348                 /*
349                  *      Strip port from hostname if needed.
350                  */
351                 if (portname) server_port = atoi(portname);
352         }
353
354         /*
355          *      See what kind of request we want to send.
356          */
357         if (strcmp(argv[2], "discover") == 0) {
358                 if (server_port == 0) server_port = 67;
359                 packet_code = PW_DHCP_DISCOVER;
360
361         } else if (strcmp(argv[2], "request") == 0) {
362                 if (server_port == 0) server_port = 67;
363                 packet_code = PW_DHCP_REQUEST;
364
365         } else if (strcmp(argv[2], "offer") == 0) {
366                 if (server_port == 0) server_port = 67;
367                 packet_code = PW_DHCP_OFFER;
368
369         } else if (isdigit((int) argv[2][0])) {
370                 if (server_port == 0) server_port = 67;
371                 packet_code = atoi(argv[2]);
372         } else {
373                 fprintf(stderr, "Unknown packet type %s\n", argv[2]);
374                 usage();
375         }
376
377         request_init(filename);
378
379         /*
380          *      No data read.  Die.
381          */
382         if (!request || !request->vps) {
383                 fprintf(stderr, "dhcpclient: Nothing to send.\n");
384                 exit(1);
385         }
386         request->code = packet_code;
387
388         /*
389          *      Bind to the first specified IP address and port.
390          *      This means we ignore later ones.
391          */
392         if (request->src_ipaddr.af == AF_UNSPEC) {
393                 memset(&client_ipaddr, 0, sizeof(client_ipaddr));
394                 client_ipaddr.af = server_ipaddr.af;
395                 client_port = 0;
396         } else {
397                 client_ipaddr = request->src_ipaddr;
398                 client_port = request->src_port;
399         }
400         sockfd = fr_socket(&client_ipaddr, client_port);
401         if (sockfd < 0) {
402                 fprintf(stderr, "dhcpclient: socket: %s\n", fr_strerror());
403                 exit(1);
404         }
405
406         request->sockfd = sockfd;
407         if (request->src_ipaddr.af == AF_UNSPEC) {
408                 request->src_ipaddr = client_ipaddr;
409                 request->src_port = client_port;
410         }
411         if (request->dst_ipaddr.af == AF_UNSPEC) {
412                 request->dst_ipaddr = server_ipaddr;
413                 request->dst_port = server_port;
414         }
415
416         /*
417          *      Encode the packet
418          */
419         if (fr_dhcp_encode(request) < 0) {
420                 fprintf(stderr, "dhcpclient: failed encoding: %s\n",
421                         fr_strerror());
422                 exit(1);
423         }
424         if (fr_debug_flag) print_hex(request);
425         
426         if (fr_dhcp_send(request) < 0) {
427                 fprintf(stderr, "dhcpclient: failed sending: %s\n",
428                         strerror(errno));
429                 exit(1);
430         }
431
432         reply = fr_dhcp_recv(sockfd);
433         if (!reply) {
434                 fprintf(stderr, "dhcpclient: no reply\n");
435                 exit(1);
436         }
437         if (fr_debug_flag) print_hex(reply);
438
439         if (fr_dhcp_decode(reply) < 0) {
440                 fprintf(stderr, "dhcpclient: failed decoding\n");
441                 return 1;
442         }
443
444         dict_free();
445
446         if (success) return 0;
447
448         return 1;
449 }
450
451 #endif  /* WITH_DHCP */