| /***************************************************************************** |
| * 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); |
| } |