d94fca8cfb313ef12b545cc125e44218401ccc28
[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 uint16_t 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 uint16_t 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 #" STRINGIFY(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, decline, release, inform.\n");
75         fprintf(stderr, "  -d <directory>         Set the directory where the dictionaries are stored (defaults to " RADDBDIR ").\n");
76         fprintf(stderr, "  -D <dictdir>           Set main dictionary directory (defaults to " DICTDIR ").\n");
77         fprintf(stderr, "  -f <file>              Read packets from file, not stdin.\n");
78         fprintf(stderr, "  -t <timeout>           Wait 'timeout' seconds for a reply (may be a floating point number).\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         bool filedone = false;
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, fr_syserror(errno));
104                         return 0;
105                 }
106         } else {
107                 fp = stdin;
108         }
109
110         request = rad_alloc(NULL, false);
111
112         /*
113          *      Read the VP's.
114          */
115         if (readvp2(NULL, &request->vps, fp, &filedone) < 0) {
116                 fr_perror("dhcpclient");
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 = fr_cursor_init(&cursor, &request->vps); vp; vp = fr_cursor_next(&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         uint8_t const *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 *dict_dir = DICTDIR;
269         char const *filename = NULL;
270         DICT_ATTR const *da;
271
272         fr_debug_flag = 0;
273
274         while ((c = getopt(argc, argv, "d:D:f:hr:t:vx")) != EOF) switch (c) {
275                 case 'D':
276                         dict_dir = optarg;
277                         break;
278
279                 case 'd':
280                         radius_dir = optarg;
281                         break;
282                 case 'f':
283                         filename = optarg;
284                         break;
285                 case 'r':
286                         if (!isdigit((int) *optarg))
287                                 usage();
288                         retries = atoi(optarg);
289                         if ((retries == 0) || (retries > 1000)) usage();
290                         break;
291                 case 't':
292                         if (!isdigit((int) *optarg))
293                                 usage();
294                         timeout = atof(optarg);
295                         break;
296                 case 'v':
297                         printf("%s\n", dhcpclient_version);
298                         exit(0);
299                         break;
300                 case 'x':
301                         fr_debug_flag++;
302                         fr_log_fp = stdout;
303                         break;
304                 case 'h':
305                 default:
306                         usage();
307                         break;
308         }
309         argc -= (optind - 1);
310         argv += (optind - 1);
311
312         if (argc < 2) usage();
313
314         if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
315                 fr_perror("dhcpclient");
316                 return 1;
317         }
318
319         /*
320          *      Ensure that dictionary.dhcp is loaded.
321          */
322         da = dict_attrbyname("DHCP-Message-Type");
323         if (!da) {
324                 if (dict_read(dict_dir, "dictionary.dhcp") < 0) {
325                         fprintf(stderr, "Failed reading dictionary.dhcp: %s",
326                                 fr_strerror());
327                         return -1;
328                 }
329
330                 if (dict_read(dict_dir, "dictionary.freeradius.internal") < 0) {
331                         fprintf(stderr, "Failed reading dictionary.freeradius.internal: %s",
332                                 fr_strerror());
333                         return -1;
334                 }
335         }
336
337         /*
338          *      Resolve hostname.
339          */
340         server_ipaddr.af = AF_INET;
341         if (strcmp(argv[1], "-") != 0) {
342                 char const *hostname = argv[1];
343                 char const *portname = argv[1];
344                 char buffer[256];
345
346                 if (*argv[1] == '[') { /* IPv6 URL encoded */
347                         p = strchr(argv[1], ']');
348                         if ((size_t) (p - argv[1]) >= sizeof(buffer)) {
349                                 usage();
350                         }
351
352                         memcpy(buffer, argv[1] + 1, p - argv[1] - 1);
353                         buffer[p - argv[1] - 1] = '\0';
354
355                         hostname = buffer;
356                         portname = p + 1;
357
358                 }
359                 p = strchr(portname, ':');
360                 if (p && (strchr(p + 1, ':') == NULL)) {
361                         *p = '\0';
362                         portname = p + 1;
363                 } else {
364                         portname = NULL;
365                 }
366
367                 if (ip_hton(&server_ipaddr, AF_INET, hostname, false) < 0) {
368                         fprintf(stderr, "dhcpclient: Failed to find IP address for host %s: %s\n", hostname, fr_syserror(errno));
369                         exit(1);
370                 }
371
372                 /*
373                  *      Strip port from hostname if needed.
374                  */
375                 if (portname) server_port = atoi(portname);
376         }
377
378         /*
379          *      See what kind of request we want to send.
380          */
381         if (strcmp(argv[2], "discover") == 0) {
382                 if (server_port == 0) server_port = 67;
383                 packet_code = PW_DHCP_DISCOVER;
384
385         } else if (strcmp(argv[2], "request") == 0) {
386                 if (server_port == 0) server_port = 67;
387                 packet_code = PW_DHCP_REQUEST;
388
389         } else if (strcmp(argv[2], "offer") == 0) {
390                 if (server_port == 0) server_port = 67;
391                 packet_code = PW_DHCP_OFFER;
392
393         } else if (strcmp(argv[2], "decline") == 0) {
394                 if (server_port == 0) server_port = 67;
395                 packet_code = PW_DHCP_DECLINE;
396
397         } else if (strcmp(argv[2], "release") == 0) {
398                 if (server_port == 0) server_port = 67;
399                 packet_code = PW_DHCP_RELEASE;
400
401         } else if (strcmp(argv[2], "inform") == 0) {
402                 if (server_port == 0) server_port = 67;
403                 packet_code = PW_DHCP_INFORM;
404
405         } else if (isdigit((int) argv[2][0])) {
406                 if (server_port == 0) server_port = 67;
407                 packet_code = atoi(argv[2]);
408         } else {
409                 fprintf(stderr, "Unknown packet type %s\n", argv[2]);
410                 usage();
411         }
412
413         request_init(filename);
414
415         /*
416          *      No data read.  Die.
417          */
418         if (!request || !request->vps) {
419                 fprintf(stderr, "dhcpclient: Nothing to send.\n");
420                 exit(1);
421         }
422         request->code = packet_code;
423
424         /*
425          *      Bind to the first specified IP address and port.
426          *      This means we ignore later ones.
427          */
428         if (request->src_ipaddr.af == AF_UNSPEC) {
429                 memset(&client_ipaddr, 0, sizeof(client_ipaddr));
430                 client_ipaddr.af = server_ipaddr.af;
431                 client_port = 0;
432         } else {
433                 client_ipaddr = request->src_ipaddr;
434                 client_port = request->src_port;
435         }
436         sockfd = fr_socket(&client_ipaddr, client_port);
437         if (sockfd < 0) {
438                 fprintf(stderr, "dhcpclient: socket: %s\n", fr_strerror());
439                 exit(1);
440         }
441
442         /*
443          *      Set option 'receive timeout' on socket.
444          *      Note: in case of a timeout, the error will be "Resource temporarily unavailable".
445          */
446         struct timeval tv;
447         tv.tv_sec = (time_t)timeout;
448         tv.tv_usec = (uint64_t)(timeout * 1000000) - (tv.tv_sec * 1000000);
449         if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) == -1) {
450                 fprintf(stderr, "dhcpclient: failed setting socket timeout: %s\n",
451                         fr_syserror(errno));
452                 exit(1);
453         }
454
455         request->sockfd = sockfd;
456         if (request->src_ipaddr.af == AF_UNSPEC) {
457                 request->src_ipaddr = client_ipaddr;
458                 request->src_port = client_port;
459         }
460         if (request->dst_ipaddr.af == AF_UNSPEC) {
461                 request->dst_ipaddr = server_ipaddr;
462                 request->dst_port = server_port;
463         }
464
465         /*
466          *      Encode the packet
467          */
468         if (fr_dhcp_encode(request) < 0) {
469                 fprintf(stderr, "dhcpclient: failed encoding: %s\n",
470                         fr_strerror());
471                 exit(1);
472         }
473         if (fr_debug_flag) print_hex(request);
474
475         if (fr_dhcp_send(request) < 0) {
476                 fprintf(stderr, "dhcpclient: failed sending: %s\n",
477                         fr_syserror(errno));
478                 exit(1);
479         }
480
481         reply = fr_dhcp_recv(sockfd);
482         if (!reply) {
483                 fprintf(stderr, "dhcpclient: Error receiving reply %s\n", fr_strerror());
484                 exit(1);
485         }
486         if (fr_debug_flag) print_hex(reply);
487
488         if (fr_dhcp_decode(reply) < 0) {
489                 fprintf(stderr, "dhcpclient: failed decoding\n");
490                 return 1;
491         }
492
493         dict_free();
494
495         if (success) return 0;
496
497         return 1;
498 }
499
500 #endif  /* WITH_DHCP */