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