aboutsummaryrefslogtreecommitdiff
path: root/lib/libnet/pxelinux.c
blob: b32f23351cf0c7f31675bb8b9718cc454ff32dab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
/*****************************************************************************
 * pxelinux.cfg-style config file support.
 *
 * See https://www.syslinux.org/wiki/index.php?title=PXELINUX for information
 * about the pxelinux config file layout.
 *
 * Copyright 2018 Red Hat, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the BSD License which accompanies this distribution, and is
 * available at http://www.opensource.org/licenses/bsd-license.php
 *
 * Contributors:
 *     Thomas Huth, Red Hat Inc. - initial implementation
 *****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include "tftp.h"
#include "pxelinux.h"

/**
 * Call tftp() and report errors (excet "file-not-found" errors)
 */
static int pxelinux_tftp_load(filename_ip_t *fnip, void *buffer, int len,
                              int retries)
{
	tftp_err_t tftp_err;
	int rc, ecode;

	rc = tftp(fnip, buffer, len, retries, &tftp_err);

	if (rc > 0) {
		printf("\r  TFTP: Received %s (%d bytes)\n",
		       fnip->filename, rc);
	} else if (rc == -3) {
		/* Ignore file-not-found (since we are probing the files)
		 * and simply erase the "Receiving data:  0 KBytes" string */
		printf("\r                           \r");
	} else {
		const char *errstr = NULL;
		rc = tftp_get_error_info(fnip, &tftp_err, rc, &errstr, &ecode);
		if (errstr)
			printf("\r  TFTP error: %s\n", errstr);
	}

	return rc;
}

/**
 * Try to load a pxelinux.cfg file by probing the possible file names.
 * Note that this function will overwrite filename_ip_t->filename.
 */
static int pxelinux_load_cfg(filename_ip_t *fn_ip, uint8_t *mac, const char *uuid,
                             int retries, char *cfgbuf, int cfgbufsize)
{
	int rc;
	unsigned idx;
	char *baseptr;

	/* Did we get a usable base directory via DHCP? */
	if (fn_ip->pl_prefix) {
		idx = strlen(fn_ip->pl_prefix);
		/* Do we have enough space left to store a UUID file name? */
		if (idx > sizeof(fn_ip->filename) - 36) {
			puts("Error: pxelinux prefix is too long!");
			return -1;
		}
		strcpy(fn_ip->filename, fn_ip->pl_prefix);
		baseptr = &fn_ip->filename[idx];
	} else {
		/* Try to get a usable base directory from the DHCP bootfile name */
		baseptr = strrchr(fn_ip->filename, '/');
		if (!baseptr)
			baseptr = fn_ip->filename;
		else
			++baseptr;
		/* Check that we've got enough space to store "pxelinux.cfg/"
		 * and the UUID (which is the longest file name) there */
		if ((size_t)(baseptr - fn_ip->filename) > (sizeof(fn_ip->filename) - 50)) {
			puts("Error: The bootfile string is too long for "
			     "deriving the pxelinux.cfg file name from it.");
			return -1;
		}
		strcpy(baseptr, "pxelinux.cfg/");
		baseptr += strlen(baseptr);
	}

	puts("Trying pxelinux.cfg files...");

	/* Try to load config file according to file name in DHCP option 209 */
	if (fn_ip->pl_cfgfile) {
		if (strlen(fn_ip->pl_cfgfile) + strlen(fn_ip->filename)
		    > sizeof(fn_ip->filename)) {
			puts("Error: pxelinux.cfg prefix + filename too long!");
			return -1;
		}
		strcpy(baseptr, fn_ip->pl_cfgfile);
		rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);
		if (rc > 0) {
			return rc;
		}
	}

	/* Try to load config file with name based on the VM UUID */
	if (uuid) {
		strcpy(baseptr, uuid);
		rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);
		if (rc > 0) {
			return rc;
		}
	}

	/* Look for config file with MAC address in its name */
	sprintf(baseptr, "01-%02x-%02x-%02x-%02x-%02x-%02x",
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
	rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);
	if (rc > 0) {
		return rc;
	}

	/* Look for config file with IP address in its name */
	if (fn_ip->ip_version == 4) {
		sprintf(baseptr, "%02X%02X%02X%02X",
			(fn_ip->own_ip >> 24) & 0xff,
			(fn_ip->own_ip >> 16) & 0xff,
			(fn_ip->own_ip >> 8) & 0xff,
			fn_ip->own_ip & 0xff);
		for (idx = 0; idx <= 7; idx++) {
			baseptr[8 - idx] = 0;
			rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize,
			                        retries);
			if (rc > 0) {
				return rc;
			}
		}
	}

	/* Try "default" config file */
	strcpy(baseptr, "default");
	rc = pxelinux_tftp_load(fn_ip, cfgbuf, cfgbufsize, retries);

	return rc;
}

/**
 * Parse a pxelinux-style configuration file.
 * The discovered entries are filled into the "struct pl_cfg_entry entries[]"
 * array. Note that the callers must keep the cfg buffer valid as long as
 * they wish to access the "struct pl_cfg_entry" entries, since the pointers
 * in entries point to the original location in the cfg buffer area. The cfg
 * buffer is altered for this, too, e.g. terminating NUL-characters are put
 * into the right locations.
 * @param cfg          Pointer to the buffer with contents of the config file.
 *                     The caller must make sure that it is NUL-terminated.
 * @param cfgsize      Size of the cfg data (including the terminating NUL)
 * @param entries      Pointer to array where the results should be put into
 * @param max_entries  Number of available slots in the entries array
 * @param def_ent      Used to return the index of the default entry
 * @return             Number of valid entries
 */
int pxelinux_parse_cfg(char *cfg, int cfgsize, struct pl_cfg_entry *entries,
                       int max_entries, int *def_ent)
{
	int num_entries = 0;
	char *ptr = cfg, *nextptr, *eol, *arg;
	char *defaultlabel = NULL;

	*def_ent = 0;

	while (ptr < cfg + cfgsize && num_entries < max_entries) {
		eol = strchr(ptr, '\n');
		if (!eol) {
			eol = cfg + cfgsize - 1;
		}
		nextptr = eol + 1;
		do {
			*eol-- = '\0';	/* Remove spaces, tabs and returns */
		} while (eol >= ptr &&
		         (*eol == '\r' || *eol == ' ' || *eol == '\t'));
		while (*ptr == ' ' || *ptr == '\t') {
			ptr++;
		}
		if (*ptr == 0 || *ptr == '#') {
			goto nextline;	/* Ignore comments and empty lines */
		}
		arg = strchr(ptr, ' ');	/* Search space between cmnd and arg */
		if (!arg) {
			arg = strchr(ptr, '\t');
		}
		if (!arg) {
			printf("Failed to parse this line:\n %s\n", ptr);
			goto nextline;
		}
		*arg++ = 0;
		while (*arg == ' ' || *arg == '\t') {
			arg++;
		}
		if (!strcasecmp("default", ptr)) {
			defaultlabel = arg;
		} else if (!strcasecmp("label", ptr)) {
			entries[num_entries].label = arg;
			if (defaultlabel && !strcmp(arg, defaultlabel)) {
				*def_ent = num_entries;
			}
			num_entries++;
		} else if (!strcasecmp("kernel", ptr) && num_entries) {
			entries[num_entries - 1].kernel = arg;
		} else if (!strcasecmp("initrd", ptr) && num_entries) {
			entries[num_entries - 1].initrd = arg;
		} else if (!strcasecmp("append", ptr) && num_entries) {
			entries[num_entries - 1].append = arg;
		} else {
			printf("Command '%s' is not supported.\n", ptr);
		}
nextline:
		ptr = nextptr;
	}

	return num_entries;
}

/**
 * Try to load and parse a pxelinux-style configuration file.
 * @param fn_ip        must contain server and client IP information
 * @param mac          MAC address which should be used for probing
 * @param uuid         UUID which should be used for probing (can be NULL)
 * @param retries      Amount of TFTP retries before giving up
 * @param cfgbuf       Pointer to the buffer where config file should be loaded
 * @param cfgsize      Size of the cfgbuf buffer
 * @param entries      Pointer to array where the results should be put into
 * @param max_entries  Number of available slots in the entries array
 * @param def_ent      Used to return the index of the default entry
 * @return             Number of valid entries
 */
int 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)
{
	int rc;

	rc = pxelinux_load_cfg(fn_ip, mac, uuid, retries, cfgbuf, cfgsize - 1);
	if (rc < 0)
		return rc;
	assert(rc < cfgsize);

	cfgbuf[rc++] = '\0';	/* Make sure it is NUL-terminated */

	return pxelinux_parse_cfg(cfgbuf, rc, entries, max_entries, def_ent);
}