dhcpclient: Add a short description to help output
[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         fprintf(stderr, "Send a DHCP request with provided RADIUS attrs and output response.\n");
74
75         fprintf(stderr, "  <command>              One of discover, request, offer, decline, release, inform.\n");
76         fprintf(stderr, "  -d <directory>         Set the directory where the dictionaries are stored (defaults to " RADDBDIR ").\n");
77         fprintf(stderr, "  -D <dictdir>           Set main dictionary directory (defaults to " DICTDIR ").\n");
78         fprintf(stderr, "  -f <file>              Read packets from file, not stdin.\n");
79         fprintf(stderr, "  -t <timeout>           Wait 'timeout' seconds for a reply (may be a floating point number).\n");
80         fprintf(stderr, "  -v                     Show program version information.\n");
81         fprintf(stderr, "  -x                     Debugging mode.\n");
82
83         exit(1);
84 }
85
86
87 /*
88  *      Initialize the request.
89  */
90 static int request_init(char const *filename)
91 {
92         FILE *fp;
93         vp_cursor_t cursor;
94         VALUE_PAIR *vp;
95         bool filedone = false;
96
97         /*
98          *      Determine where to read the VP's from.
99          */
100         if (filename) {
101                 fp = fopen(filename, "r");
102                 if (!fp) {
103                         fprintf(stderr, "dhcpclient: Error opening %s: %s\n",
104                                 filename, fr_syserror(errno));
105                         return 0;
106                 }
107         } else {
108                 fp = stdin;
109         }
110
111         request = rad_alloc(NULL, false);
112
113         /*
114          *      Read the VP's.
115          */
116         if (readvp2(NULL, &request->vps, fp, &filedone) < 0) {
117                 fr_perror("dhcpclient");
118                 rad_free(&request);
119                 if (fp != stdin) fclose(fp);
120                 return 1;
121         }
122
123         /*
124          *      Fix / set various options
125          */
126         for (vp = fr_cursor_init(&cursor, &request->vps); vp; vp = fr_cursor_next(&cursor)) {
127                 switch (vp->da->attr) {
128                 default:
129                         break;
130
131                         /*
132                          *      Allow it to set the packet type in
133                          *      the attributes read from the file.
134                          */
135                 case PW_PACKET_TYPE:
136                         request->code = vp->vp_integer;
137                         break;
138
139                 case PW_PACKET_DST_PORT:
140                         request->dst_port = (vp->vp_integer & 0xffff);
141                         break;
142
143                 case PW_PACKET_DST_IP_ADDRESS:
144                         request->dst_ipaddr.af = AF_INET;
145                         request->dst_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
146                         break;
147
148                 case PW_PACKET_DST_IPV6_ADDRESS:
149                         request->dst_ipaddr.af = AF_INET6;
150                         request->dst_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
151                         break;
152
153                 case PW_PACKET_SRC_PORT:
154                         request->src_port = (vp->vp_integer & 0xffff);
155                         break;
156
157                 case PW_PACKET_SRC_IP_ADDRESS:
158                         request->src_ipaddr.af = AF_INET;
159                         request->src_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;
160                         break;
161
162                 case PW_PACKET_SRC_IPV6_ADDRESS:
163                         request->src_ipaddr.af = AF_INET6;
164                         request->src_ipaddr.ipaddr.ip6addr = vp->vp_ipv6addr;
165                         break;
166                 } /* switch over the attribute */
167
168         } /* loop over the VP's we read in */
169
170         if (fp != stdin) fclose(fp);
171
172         /*
173          *      And we're done.
174          */
175         return 1;
176 }
177
178 static char const *dhcp_header_names[] = {
179         "DHCP-Opcode",
180         "DHCP-Hardware-Type",
181         "DHCP-Hardware-Address-Length",
182         "DHCP-Hop-Count",
183         "DHCP-Transaction-Id",
184         "DHCP-Number-of-Seconds",
185         "DHCP-Flags",
186         "DHCP-Client-IP-Address",
187         "DHCP-Your-IP-Address",
188         "DHCP-Server-IP-Address",
189         "DHCP-Gateway-IP-Address",
190         "DHCP-Client-Hardware-Address",
191         "DHCP-Server-Host-Name",
192         "DHCP-Boot-Filename",
193
194         NULL
195 };
196
197 static int dhcp_header_sizes[] = {
198         1, 1, 1, 1,
199         4, 2, 2, 4,
200         4, 4, 4,
201         DHCP_CHADDR_LEN,
202         DHCP_SNAME_LEN,
203         DHCP_FILE_LEN
204 };
205
206
207 static void print_hex(RADIUS_PACKET *packet)
208 {
209         int i, j;
210         uint8_t const *p, *a;
211
212         if (!packet->data) return;
213
214         if (packet->data_len < 244) {
215                 printf("Huh?\n");
216                 return;
217         }
218
219         printf("----------------------------------------------------------------------\n");
220         fflush(stdout);
221
222         p = packet->data;
223         for (i = 0; i < 14; i++) {
224                 printf("%s = 0x", dhcp_header_names[i]);
225                 for (j = 0; j < dhcp_header_sizes[i]; j++) {
226                         printf("%02x", p[j]);
227
228                 }
229                 printf("\n");
230                 p += dhcp_header_sizes[i];
231         }
232
233         /*
234          *      Magic number
235          */
236         printf("%02x %02x %02x %02x\n",
237                p[0], p[1], p[2], p[3]);
238         p += 4;
239
240         while (p < (packet->data + packet->data_len)) {
241
242                 if (*p == 0) break;
243                 if (*p == 255) break; /* end of options signifier */
244                 if ((p + 2) > (packet->data + packet->data_len)) break;
245
246                 printf("%02x  %02x  ", p[0], p[1]);
247                 a = p + 2;
248
249                 for (i = 0; i < p[1]; i++) {
250                         if ((i > 0) && ((i & 0x0f) == 0x00))
251                                 printf("\t\t");
252                         printf("%02x ", a[i]);
253                         if ((i & 0x0f) == 0x0f) printf("\n");
254                 }
255
256                 if ((p[1] & 0x0f) != 0x00) printf("\n");
257
258                 p += p[1] + 2;
259         }
260         printf("\n----------------------------------------------------------------------\n");
261         fflush(stdout);
262 }
263
264 int main(int argc, char **argv)
265 {
266         char *p;
267         int c;
268         char const *radius_dir = RADDBDIR;
269         char const *dict_dir = DICTDIR;
270         char const *filename = NULL;
271         DICT_ATTR const *da;
272
273         fr_debug_flag = 0;
274
275         while ((c = getopt(argc, argv, "d:D:f:hr:t:vx")) != EOF) switch (c) {
276                 case 'D':
277                         dict_dir = optarg;
278                         break;
279
280                 case 'd':
281                         radius_dir = optarg;
282                         break;
283                 case 'f':
284                         filename = optarg;
285                         break;
286                 case 'r':
287                         if (!isdigit((int) *optarg))
288                                 usage();
289                         retries = atoi(optarg);
290                         if ((retries == 0) || (retries > 1000)) usage();
291                         break;
292                 case 't':
293                         if (!isdigit((int) *optarg))
294                                 usage();
295                         timeout = atof(optarg);
296                         break;
297                 case 'v':
298                         printf("%s\n", dhcpclient_version);
299                         exit(0);
300                         break;
301                 case 'x':
302                         fr_debug_flag++;
303                         fr_log_fp = stdout;
304                         break;
305                 case 'h':
306                 default:
307                         usage();
308                         break;
309         }
310         argc -= (optind - 1);
311         argv += (optind - 1);
312
313         if (argc < 2) usage();
314
315         if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
316                 fr_perror("dhcpclient");
317                 return 1;
318         }
319
320         /*
321          *      Ensure that dictionary.dhcp is loaded.
322          */
323         da = dict_attrbyname("DHCP-Message-Type");
324         if (!da) {
325                 if (dict_read(dict_dir, "dictionary.dhcp") < 0) {
326                         fprintf(stderr, "Failed reading dictionary.dhcp: %s",
327                                 fr_strerror());
328                         return -1;
329                 }
330
331                 if (dict_read(dict_dir, "dictionary.freeradius.internal") < 0) {
332                         fprintf(stderr, "Failed reading dictionary.freeradius.internal: %s",
333                                 fr_strerror());
334                         return -1;
335                 }
336         }
337
338         /*
339          *      Resolve hostname.
340          */
341         server_ipaddr.af = AF_INET;
342         if (strcmp(argv[1], "-") != 0) {
343                 char const *hostname = argv[1];
344                 char const *portname = argv[1];
345                 char buffer[256];
346
347                 if (*argv[1] == '[') { /* IPv6 URL encoded */
348                         p = strchr(argv[1], ']');
349                         if ((size_t) (p - argv[1]) >= sizeof(buffer)) {
350                                 usage();
351                         }
352
353                         memcpy(buffer, argv[1] + 1, p - argv[1] - 1);
354                         buffer[p - argv[1] - 1] = '\0';
355
356                         hostname = buffer;
357                         portname = p + 1;
358
359                 }
360                 p = strchr(portname, ':');
361                 if (p && (strchr(p + 1, ':') == NULL)) {
362                         *p = '\0';
363                         portname = p + 1;
364                 } else {
365                         portname = NULL;
366                 }
367
368                 if (ip_hton(&server_ipaddr, AF_INET, hostname, false) < 0) {
369                         fprintf(stderr, "dhcpclient: Failed to find IP address for host %s: %s\n", hostname, fr_syserror(errno));
370                         exit(1);
371                 }
372
373                 /*
374                  *      Strip port from hostname if needed.
375                  */
376                 if (portname) server_port = atoi(portname);
377         }
378
379         /*
380          *      See what kind of request we want to send.
381          */
382         if (strcmp(argv[2], "discover") == 0) {
383                 if (server_port == 0) server_port = 67;
384                 packet_code = PW_DHCP_DISCOVER;
385
386         } else if (strcmp(argv[2], "request") == 0) {
387                 if (server_port == 0) server_port = 67;
388                 packet_code = PW_DHCP_REQUEST;
389
390         } else if (strcmp(argv[2], "offer") == 0) {
391                 if (server_port == 0) server_port = 67;
392                 packet_code = PW_DHCP_OFFER;
393
394         } else if (strcmp(argv[2], "decline") == 0) {
395                 if (server_port == 0) server_port = 67;
396                 packet_code = PW_DHCP_DECLINE;
397
398         } else if (strcmp(argv[2], "release") == 0) {
399                 if (server_port == 0) server_port = 67;
400                 packet_code = PW_DHCP_RELEASE;
401
402         } else if (strcmp(argv[2], "inform") == 0) {
403                 if (server_port == 0) server_port = 67;
404                 packet_code = PW_DHCP_INFORM;
405
406         } else if (isdigit((int) argv[2][0])) {
407                 if (server_port == 0) server_port = 67;
408                 packet_code = atoi(argv[2]);
409         } else {
410                 fprintf(stderr, "Unknown packet type %s\n", argv[2]);
411                 usage();
412         }
413
414         request_init(filename);
415
416         /*
417          *      No data read.  Die.
418          */
419         if (!request || !request->vps) {
420                 fprintf(stderr, "dhcpclient: Nothing to send.\n");
421                 exit(1);
422         }
423         request->code = packet_code;
424
425         /*
426          *      Bind to the first specified IP address and port.
427          *      This means we ignore later ones.
428          */
429         if (request->src_ipaddr.af == AF_UNSPEC) {
430                 memset(&client_ipaddr, 0, sizeof(client_ipaddr));
431                 client_ipaddr.af = server_ipaddr.af;
432                 client_port = 0;
433         } else {
434                 client_ipaddr = request->src_ipaddr;
435                 client_port = request->src_port;
436         }
437         sockfd = fr_socket(&client_ipaddr, client_port);
438         if (sockfd < 0) {
439                 fprintf(stderr, "dhcpclient: socket: %s\n", fr_strerror());
440                 exit(1);
441         }
442
443         /*
444          *      Set option 'receive timeout' on socket.
445          *      Note: in case of a timeout, the error will be "Resource temporarily unavailable".
446          */
447         struct timeval tv;
448         tv.tv_sec = (time_t)timeout;
449         tv.tv_usec = (uint64_t)(timeout * 1000000) - (tv.tv_sec * 1000000);
450         if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval)) == -1) {
451                 fprintf(stderr, "dhcpclient: failed setting socket timeout: %s\n",
452                         fr_syserror(errno));
453                 exit(1);
454         }
455
456         request->sockfd = sockfd;
457         if (request->src_ipaddr.af == AF_UNSPEC) {
458                 request->src_ipaddr = client_ipaddr;
459                 request->src_port = client_port;
460         }
461         if (request->dst_ipaddr.af == AF_UNSPEC) {
462                 request->dst_ipaddr = server_ipaddr;
463                 request->dst_port = server_port;
464         }
465
466         /*
467          *      Encode the packet
468          */
469         if (fr_dhcp_encode(request) < 0) {
470                 fprintf(stderr, "dhcpclient: failed encoding: %s\n",
471                         fr_strerror());
472                 exit(1);
473         }
474         if (fr_debug_flag) print_hex(request);
475
476         if (fr_dhcp_send(request) < 0) {
477                 fprintf(stderr, "dhcpclient: failed sending: %s\n",
478                         fr_syserror(errno));
479                 exit(1);
480         }
481
482         reply = fr_dhcp_recv(sockfd);
483         if (!reply) {
484                 fprintf(stderr, "dhcpclient: Error receiving reply %s\n", fr_strerror());
485                 exit(1);
486         }
487         if (fr_debug_flag) print_hex(reply);
488
489         if (fr_dhcp_decode(reply) < 0) {
490                 fprintf(stderr, "dhcpclient: failed decoding\n");
491                 return 1;
492         }
493
494         dict_free();
495
496         if (success) return 0;
497
498         return 1;
499 }
500
501 #endif  /* WITH_DHCP */