1 /*****************************************************************************
2 * pxelinux.cfg-style config file support.
3 *
4 * See https://www.syslinux.org/wiki/index.php?title=PXELINUX for information
5 * about the pxelinux config file layout.
6 *
7 * Copyright 2018 Red Hat, Inc.
8 *
9 * This program and the accompanying materials are made available under the
10 * terms of the BSD License which accompanies this distribution, and is
11 * available at http://www.opensource.org/licenses/bsd-license.php
12 *
13 * Contributors:
14 * Thomas Huth, Red Hat Inc. - initial implementation
15 *****************************************************************************/
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <assert.h>
20 #include "tftp.h"
21 #include "pxelinux.h"
22
23 /**
24 * Call tftp() and report errors (excet "file-not-found" errors)
25 */
pxelinux_tftp_load(filename_ip_t * fnip,void * buffer,int len,int retries)26 static int pxelinux_tftp_load(filename_ip_t *fnip, void *buffer, int len,
27 int retries)
28 {
29 tftp_err_t tftp_err;
30 int rc, ecode;
31
32 rc = tftp(fnip, buffer, len, retries, &tftp_err);
33
34 if (rc > 0) {
35 printf("\r TFTP: Received %s (%d bytes)\n",
36 fnip->filename, rc);
37 } else if (rc == -3) {
38 /* Ignore file-not-found (since we are probing the files)
39 * and simply erase the "Receiving data: 0 KBytes" string */
40 printf("\r \r");
41 } else {
42 const char *errstr = NULL;
43 rc = tftp_get_error_info(fnip, &tftp_err, rc, &errstr, &ecode);
44 if (errstr)
45 printf("\r TFTP error: %s\n", errstr);
46 }
47
48 return rc;
49 }
50
51 /**
52 * Try to load a pxelinux.cfg file by probing the possible file names.
53 * Note that this function will overwrite filename_ip_t->filename.
54 */
pxelinux_load_cfg(filename_ip_t * fn_ip,uint8_t * mac,const char * uuid,int retries,char * cfgbuf,int cfgbufsize)55 static int pxelinux_load_cfg(filename_ip_t *fn_ip, uint8_t *mac, const char *uuid,
56 int retries, char *cfgbuf, int cfgbufsize)
57 {
58 int rc;
59 unsigned idx;
60 char *baseptr;
61
62 /* Did we get a usable base directory via DHCP? */
63 if (fn_ip->pl_prefix) {
64 idx = strlen(fn_ip->pl_prefix);
65 /* Do we have enough space left to store a UUID file name? */
66 if (idx > sizeof(fn_ip->filename) - 36) {
67 puts("Error: pxelinux prefix is too long!");
68 return -1;
69 }
70 strcpy(fn_ip->filename, fn_ip->pl_prefix);
71 baseptr = &fn_ip->filename[idx];
72 } else {
73 /* Try to get a usable base directory from the DHCP bootfile name */
74 baseptr = strrchr(fn_ip->filename, '/');
75 if (!baseptr)
76 baseptr = fn_ip->filename;
77 else
78 ++baseptr;
79 /* Check that we've got enough space to store "pxelinux.cfg/"
80 * and the UUID (which is the longest file name) there */
81 if ((size_t)(baseptr - fn_ip->filename) > (sizeof(fn_ip->filename) - 50)) {
82 puts("Error: The bootfile string is too long for "
83 "deriving the pxelinux.cfg file name from it.");
84 return -1;
85 }
86 strcpy(baseptr, "pxelinux.cfg/");
87 baseptr += strlen(baseptr);
88 }
89
90 puts("Trying pxelinux.cfg files...");
91
92 /* Try to load config file according to file name in DHCP option 209 */
93 if (fn_ip->pl_cfgfile) {
94 if (strlen(fn_ip->pl_cfgfile) + strlen(fn_ip->filename)
95 > sizeof(fn_ip->filename)) {
96 puts("Error: pxelinux.cfg prefix + filename too long!");
97 return -1;
98 }
99 strcpy(baseptr, fn_ip->pl_cfgfile);
100 rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);
101 if (rc > 0) {
102 return rc;
103 }
104 }
105
106 /* Try to load config file with name based on the VM UUID */
107 if (uuid) {
108 strcpy(baseptr, uuid);
109 rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);
110 if (rc > 0) {
111 return rc;
112 }
113 }
114
115 /* Look for config file with MAC address in its name */
116 sprintf(baseptr, "01-%02x-%02x-%02x-%02x-%02x-%02x",
117 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
118 rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);
119 if (rc > 0) {
120 return rc;
121 }
122
123 /* Look for config file with IP address in its name */
124 if (fn_ip->ip_version == 4) {
125 sprintf(baseptr, "%02X%02X%02X%02X",
126 (fn_ip->own_ip >> 24) & 0xff,
127 (fn_ip->own_ip >> 16) & 0xff,
128 (fn_ip->own_ip >> 8) & 0xff,
129 fn_ip->own_ip & 0xff);
130 for (idx = 0; idx <= 7; idx++) {
131 baseptr[8 - idx] = 0;
132 rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize,
133 retries);
134 if (rc > 0) {
135 return rc;
136 }
137 }
138 }
139
140 /* Try "default" config file */
141 strcpy(baseptr, "default");
142 rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);
143
144 return rc;
145 }
146
147 /**
148 * Parse a pxelinux-style configuration file.
149 * The discovered entries are filled into the "struct pl_cfg_entry entries[]"
150 * array. Note that the callers must keep the cfg buffer valid as long as
151 * they wish to access the "struct pl_cfg_entry" entries, since the pointers
152 * in entries point to the original location in the cfg buffer area. The cfg
153 * buffer is altered for this, too, e.g. terminating NUL-characters are put
154 * into the right locations.
155 * @param cfg Pointer to the buffer with contents of the config file.
156 * The caller must make sure that it is NUL-terminated.
157 * @param cfgsize Size of the cfg data (including the terminating NUL)
158 * @param entries Pointer to array where the results should be put into
159 * @param max_entries Number of available slots in the entries array
160 * @param def_ent Used to return the index of the default entry
161 * @return Number of valid entries
162 */
pxelinux_parse_cfg(char * cfg,int cfgsize,struct pl_cfg_entry * entries,int max_entries,int * def_ent)163 int pxelinux_parse_cfg(char *cfg, int cfgsize, struct pl_cfg_entry *entries,
164 int max_entries, int *def_ent)
165 {
166 int num_entries = 0;
167 char *ptr = cfg, *nextptr, *eol, *arg;
168 char *defaultlabel = NULL;
169
170 *def_ent = 0;
171
172 while (ptr < cfg + cfgsize && num_entries < max_entries) {
173 eol = strchr(ptr, '\n');
174 if (!eol) {
175 eol = cfg + cfgsize - 1;
176 }
177 nextptr = eol + 1;
178 do {
179 *eol-- = '\0'; /* Remove spaces, tabs and returns */
180 } while (eol >= ptr &&
181 (*eol == '\r' || *eol == ' ' || *eol == '\t'));
182 while (*ptr == ' ' || *ptr == '\t') {
183 ptr++;
184 }
185 if (*ptr == 0 || *ptr == '#') {
186 goto nextline; /* Ignore comments and empty lines */
187 }
188 arg = strchr(ptr, ' '); /* Search space between cmnd and arg */
189 if (!arg) {
190 arg = strchr(ptr, '\t');
191 }
192 if (!arg) {
193 printf("Failed to parse this line:\n %s\n", ptr);
194 goto nextline;
195 }
196 *arg++ = 0;
197 while (*arg == ' ' || *arg == '\t') {
198 arg++;
199 }
200 if (!strcasecmp("default", ptr)) {
201 defaultlabel = arg;
202 } else if (!strcasecmp("label", ptr)) {
203 entries[num_entries].label = arg;
204 if (defaultlabel && !strcmp(arg, defaultlabel)) {
205 *def_ent = num_entries;
206 }
207 num_entries++;
208 } else if (!strcasecmp("kernel", ptr) && num_entries) {
209 entries[num_entries - 1].kernel = arg;
210 } else if (!strcasecmp("initrd", ptr) && num_entries) {
211 entries[num_entries - 1].initrd = arg;
212 } else if (!strcasecmp("append", ptr) && num_entries) {
213 entries[num_entries - 1].append = arg;
214 } else {
215 printf("Command '%s' is not supported.\n", ptr);
216 }
217 nextline:
218 ptr = nextptr;
219 }
220
221 return num_entries;
222 }
223
224 /**
225 * Try to load and parse a pxelinux-style configuration file.
226 * @param fn_ip must contain server and client IP information
227 * @param mac MAC address which should be used for probing
228 * @param uuid UUID which should be used for probing (can be NULL)
229 * @param retries Amount of TFTP retries before giving up
230 * @param cfgbuf Pointer to the buffer where config file should be loaded
231 * @param cfgsize Size of the cfgbuf buffer
232 * @param entries Pointer to array where the results should be put into
233 * @param max_entries Number of available slots in the entries array
234 * @param def_ent Used to return the index of the default entry
235 * @return Number of valid entries
236 */
pxelinux_load_parse_cfg(filename_ip_t * fn_ip,uint8_t * mac,const char * uuid,int retries,char * cfgbuf,int cfgsize,struct pl_cfg_entry * entries,int max_entries,int * def_ent)237 int pxelinux_load_parse_cfg(filename_ip_t *fn_ip, uint8_t *mac, const char *uuid,
238 int retries, char *cfgbuf, int cfgsize,
239 struct pl_cfg_entry *entries, int max_entries,
240 int *def_ent)
241 {
242 int rc;
243
244 rc = pxelinux_load_cfg(fn_ip, mac, uuid, retries, cfgbuf, cfgsize - 1);
245 if (rc < 0)
246 return rc;
247 assert(rc < cfgsize);
248
249 cfgbuf[rc++] = '\0'; /* Make sure it is NUL-terminated */
250
251 return pxelinux_parse_cfg(cfgbuf, rc, entries, max_entries, def_ent);
252 }
253