source: bootcd/isolinux/syslinux-6.03/core/fs/pxe/pxe.c

Last change on this file was e16e8f2, checked in by Edwin Eefting <edwin@datux.nl>, 3 years ago

bootstuff

  • Property mode set to 100644
File size: 16.3 KB
Line 
1#include <dprintf.h>
2#include <stdio.h>
3#include <string.h>
4#include <core.h>
5#include <fs.h>
6#include <fcntl.h>
7#include <sys/cpu.h>
8#include "pxe.h"
9#include "thread.h"
10#include "url.h"
11#include "tftp.h"
12#include <net.h>
13
14__lowmem t_PXENV_UNDI_GET_INFORMATION pxe_undi_info;
15__lowmem t_PXENV_UNDI_GET_IFACE_INFO  pxe_undi_iface;
16
17uint8_t MAC[MAC_MAX];              /* Actual MAC address */
18uint8_t MAC_len;                   /* MAC address len */
19uint8_t MAC_type;                  /* MAC address type */
20
21char boot_file[256];               /* From DHCP */
22char path_prefix[256];             /* From DHCP */
23
24bool have_uuid = false;
25
26/*
27 * Allocate a local UDP port structure and assign it a local port number.
28 * Return the inode pointer if success, or null if failure
29 */
30static struct inode *allocate_socket(struct fs_info *fs)
31{
32    struct inode *inode = alloc_inode(fs, 0, sizeof(struct pxe_pvt_inode));
33
34    if (!inode) {
35        malloc_error("socket structure");
36    } else {
37        inode->mode = DT_REG;   /* No other types relevant for PXE */
38    }
39
40    return inode;
41}
42
43void free_socket(struct inode *inode)
44{
45    struct pxe_pvt_inode *socket = PVT(inode);
46
47    free(socket->tftp_pktbuf);  /* If we allocated a buffer, free it now */
48    free_inode(inode);
49}
50
51static void pxe_close_file(struct file *file)
52{
53    struct inode *inode = file->inode;
54    struct pxe_pvt_inode *socket = PVT(inode);
55
56    if (!inode)
57        return;
58
59    if (!socket->tftp_goteof) {
60        socket->ops->close(inode);
61    }
62
63    free_socket(inode);
64}
65
66/*
67 * Tests an IP address in _ip_ for validity; return with 0 for bad, 1 for good.
68 * We used to refuse class E, but class E addresses are likely to become
69 * assignable unicast addresses in the near future.
70 *
71 */
72bool ip_ok(uint32_t ip)
73{
74    uint8_t ip_hi = (uint8_t)ip; /* First octet of the ip address */
75
76    if (ip == 0xffffffff ||     /* Refuse the all-ones address */
77        ip_hi == 0 ||           /* Refuse network zero */
78        ip_hi == 127 ||         /* Refuse the loopback network */
79        (ip_hi & 240) == 224)   /* Refuse class D */
80        return false;
81
82    return true;
83}
84
85
86/*
87 * Take an IP address (in network byte order) in _ip_ and
88 * output a dotted quad string to _dst_, returns the length
89 * of the dotted quad ip string.
90 *
91 */
92static int gendotquad(char *dst, uint32_t ip)
93{
94    return sprintf(dst, "%u.%u.%u.%u",
95                   ((const uint8_t *)&ip)[0],
96                   ((const uint8_t *)&ip)[1],
97                   ((const uint8_t *)&ip)[2],
98                   ((const uint8_t *)&ip)[3]);
99}
100
101/*
102 * the ASM pxenv function wrapper, return 1 if error, or 0
103 *
104 */
105__export int pxe_call(int opcode, void *data)
106{
107    static DECLARE_INIT_SEMAPHORE(pxe_sem, 1);
108    extern void pxenv(void);
109    com32sys_t regs;
110
111    sem_down(&pxe_sem, 0);
112
113#if 0
114    dprintf("pxe_call op %04x data %p\n", opcode, data);
115#endif
116
117    memset(&regs, 0, sizeof regs);
118    regs.ebx.w[0] = opcode;
119    regs.es       = SEG(data);
120    regs.edi.w[0] = OFFS(data);
121    call16(pxenv, &regs, &regs);
122
123    sem_up(&pxe_sem);
124
125    return regs.eflags.l & EFLAGS_CF;  /* CF SET if fail */
126}
127
128/*
129 * mangle a filename pointed to by _src_ into a buffer pointed
130 * to by _dst_; ends on encountering any whitespace.
131 *
132 * This deliberately does not attempt to do any conversion of
133 * pathname separators.
134 *
135 */
136static void pxe_mangle_name(char *dst, const char *src)
137{
138    size_t len = FILENAME_MAX-1;
139
140    while (len-- && not_whitespace(*src))
141        *dst++ = *src++;
142
143    *dst = '\0';
144}
145
146/*
147 * Read a single character from the specified pxe inode.
148 * Very useful for stepping through http streams and
149 * parsing their headers.
150 */
151int pxe_getc(struct inode *inode)
152{
153    struct pxe_pvt_inode *socket = PVT(inode);
154    unsigned char byte;
155
156    while (!socket->tftp_bytesleft) {
157        if (socket->tftp_goteof)
158            return -1;
159
160        socket->ops->fill_buffer(inode);
161    }
162
163    byte = *socket->tftp_dataptr;
164    socket->tftp_bytesleft -= 1;
165    socket->tftp_dataptr += 1;
166
167    return byte;
168}
169
170/*
171 * Get a fresh packet if the buffer is drained, and we haven't hit
172 * EOF yet.  The buffer should be filled immediately after draining!
173 */
174static void fill_buffer(struct inode *inode)
175{
176    struct pxe_pvt_inode *socket = PVT(inode);
177    if (socket->tftp_bytesleft || socket->tftp_goteof)
178        return;
179
180    return socket->ops->fill_buffer(inode);
181}
182
183
184/**
185 * getfssec: Get multiple clusters from a file, given the starting cluster.
186 * In this case, get multiple blocks from a specific TCP connection.
187 *
188 * @param: fs, the fs_info structure address, in pxe, we don't use this.
189 * @param: buf, buffer to store the read data
190 * @param: openfile, TFTP socket pointer
191 * @param: blocks, 512-byte block count; 0FFFFh = until end of file
192 *
193 * @return: the bytes read
194 *
195 */
196static uint32_t pxe_getfssec(struct file *file, char *buf,
197                             int blocks, bool *have_more)
198{
199    struct inode *inode = file->inode;
200    struct pxe_pvt_inode *socket = PVT(inode);
201    int count = blocks;
202    int chunk;
203    int bytes_read = 0;
204
205    count <<= TFTP_BLOCKSIZE_LG2;
206    while (count) {
207        fill_buffer(inode); /* If we have no 'fresh' buffer, get it */
208        if (!socket->tftp_bytesleft)
209            break;
210
211        chunk = count;
212        if (chunk > socket->tftp_bytesleft)
213            chunk = socket->tftp_bytesleft;
214        socket->tftp_bytesleft -= chunk;
215        memcpy(buf, socket->tftp_dataptr, chunk);
216        socket->tftp_dataptr += chunk;
217        buf += chunk;
218        bytes_read += chunk;
219        count -= chunk;
220    }
221
222
223    if (socket->tftp_bytesleft || (socket->tftp_filepos < inode->size)) {
224        fill_buffer(inode);
225        *have_more = 1;
226    } else if (socket->tftp_goteof) {
227        /*
228         * The socket is closed and the buffer drained; the caller will
229         * call close_file and therefore free the socket.
230         */
231        *have_more = 0;
232    }
233
234    return bytes_read;
235}
236
237/*
238 * Assign an IP address to a URL
239 */
240static void url_set_ip(struct url_info *url)
241{
242    url->ip = 0;
243    if (url->host)
244        url->ip = dns_resolv(url->host);
245    if (!url->ip)
246        url->ip = IPInfo.serverip;
247}
248
249/**
250 * Open the specified connection
251 *
252 * @param:filename, the file we wanna open
253 *
254 * @out: open_file_t structure, stores in file->open_file
255 * @out: the lenght of this file, stores in file->file_len
256 *
257 */
258static void __pxe_searchdir(const char *filename, int flags, struct file *file);
259extern uint16_t PXERetry;
260
261static void pxe_searchdir(const char *filename, int flags, struct file *file)
262{
263    int i = PXERetry;
264
265    do {
266        dprintf("PXE: file = %p, retries left = %d: ", file, i);
267        __pxe_searchdir(filename, flags, file);
268        dprintf("%s\n", file->inode ? "ok" : "failed");
269    } while (!file->inode && i--);
270}
271static void __pxe_searchdir(const char *filename, int flags, struct file *file)
272{
273    struct fs_info *fs = file->fs;
274    struct inode *inode;
275    char fullpath[2*FILENAME_MAX];
276#if GPXE
277    char urlsave[2*FILENAME_MAX];
278#endif
279    struct url_info url;
280    const struct url_scheme *us = NULL;
281    int redirect_count = 0;
282    bool found_scheme = false;
283
284    inode = file->inode = NULL;
285
286    while (filename) {
287        if (redirect_count++ > 5)
288            break;
289
290        strlcpy(fullpath, filename, sizeof fullpath);
291#if GPXE
292        strcpy(urlsave, fullpath);
293#endif
294        parse_url(&url, fullpath);
295        if (url.type == URL_SUFFIX) {
296            snprintf(fullpath, sizeof fullpath, "%s%s", fs->cwd_name, filename);
297#if GPXE
298            strcpy(urlsave, fullpath);
299#endif
300            parse_url(&url, fullpath);
301        }
302
303        inode = allocate_socket(fs);
304        if (!inode)
305            return;                     /* Allocation failure */
306       
307        url_set_ip(&url);
308       
309        filename = NULL;
310        found_scheme = false;
311        for (us = url_schemes; us->name; us++) {
312            if (!strcmp(us->name, url.scheme)) {
313                if ((flags & ~us->ok_flags & OK_FLAGS_MASK) == 0)
314                    us->open(&url, flags, inode, &filename);
315                found_scheme = true;
316                break;
317            }
318        }
319
320        /* filename here is set on a redirect */
321    }
322
323    if (!found_scheme) {
324#if GPXE
325        /* No URL scheme found, hand it to GPXE */
326        gpxe_open(inode, urlsave);
327#endif
328    }
329
330    if (inode->size) {
331        file->inode = inode;
332        file->inode->mode = (flags & O_DIRECTORY) ? DT_DIR : DT_REG;
333    } else {
334        free_socket(inode);
335    }
336
337    return;
338}
339
340
341/*
342 * Store standard filename prefix
343 */
344static void get_prefix(void)
345{
346    int len;
347    char *p;
348    char c;
349
350    if (!(DHCPMagic & 0x04)) {
351        /* No path prefix option, derive from boot file */
352
353        strlcpy(path_prefix, boot_file, sizeof path_prefix);
354        len = strlen(path_prefix);
355        p = &path_prefix[len - 1];
356       
357        while (len--) {
358            c = *p--;
359            c |= 0x20;
360           
361            c = (c >= '0' && c <= '9') ||
362                (c >= 'a' && c <= 'z') ||
363                (c == '.' || c == '-');
364            if (!c)
365                break;
366        };
367       
368        if (len < 0)
369            p --;
370       
371        *(p + 2) = 0;                /* Zero-terminate after delimiter */
372    }
373
374    ddprintf("TFTP prefix: %s\n", path_prefix);
375
376    if (url_type(path_prefix) == URL_SUFFIX) {
377        /*
378         * Construct a ::-style TFTP path.
379         *
380         * We may have moved out of the root directory at the time
381         * this function is invoked, but to maintain compatibility
382         * with versions of Syslinux < 5.00, path_prefix must be
383         * relative to "::".
384         */
385        p = strdup(path_prefix);
386        if (!p)
387            return;
388
389        snprintf(path_prefix, sizeof path_prefix, "::%s", p);
390        free(p);
391    }
392
393    chdir(path_prefix);
394}
395
396/*
397 * realpath for PXE
398 */
399static size_t pxe_realpath(struct fs_info *fs, char *dst, const char *src,
400                           size_t bufsize)
401{
402    return snprintf(dst, bufsize, "%s%s",
403                    url_type(src) == URL_SUFFIX ? fs->cwd_name : "", src);
404}
405
406/*
407 * chdir for PXE
408 */
409static int pxe_chdir(struct fs_info *fs, const char *src)
410{
411    /* The cwd for PXE is just a text prefix */
412    enum url_type path_type = url_type(src);
413
414    if (path_type == URL_SUFFIX)
415        strlcat(fs->cwd_name, src, sizeof fs->cwd_name);
416    else
417        strlcpy(fs->cwd_name, src, sizeof fs->cwd_name);
418    return 0;
419
420    dprintf("cwd = \"%s\"\n", fs->cwd_name);
421    return 0;
422}
423
424static int pxe_chdir_start(void)
425{
426        get_prefix();
427        return 0;
428}
429
430/* Load the config file, return -1 if failed, or 0 */
431static int pxe_open_config(struct com32_filedata *filedata)
432{
433    const char *cfgprefix = "pxelinux.cfg/";
434    const char *default_str = "default";
435    char *config_file;
436    char *last;
437    int tries = 8;
438
439    chdir(path_prefix);
440    if (DHCPMagic & 0x02) {
441        /* We got a DHCP option, try it first */
442        if (open_file(ConfigName, O_RDONLY, filedata) >= 0)
443            return 0;
444    }
445
446    /*
447     * Have to guess config file name ...
448     */
449    config_file = stpcpy(ConfigName, cfgprefix);
450
451    /* Try loading by UUID */
452    if (sysappend_strings[SYSAPPEND_SYSUUID]) {
453        strcpy(config_file, sysappend_strings[SYSAPPEND_SYSUUID]+8);
454        if (open_file(ConfigName, O_RDONLY, filedata) >= 0)
455            return 0;
456    }
457
458    /* Try loading by MAC address */
459    strcpy(config_file, sysappend_strings[SYSAPPEND_BOOTIF]+7);
460    if (open_file(ConfigName, O_RDONLY, filedata) >= 0)
461        return 0;
462
463    /* Nope, try hexadecimal IP prefixes... */
464    sprintf(config_file, "%08X", ntohl(IPInfo.myip));
465    last = &config_file[8];
466    while (tries) {
467        *last = '\0';        /* Zero-terminate string */
468        if (open_file(ConfigName, O_RDONLY, filedata) >= 0)
469            return 0;
470        last--;           /* Drop one character */
471        tries--;
472    };
473
474    /* Final attempt: "default" string */
475    strcpy(config_file, default_str);
476    if (open_file(ConfigName, O_RDONLY, filedata) >= 0)
477        return 0;
478
479    ddprintf("%-68s\n", "Unable to locate configuration file");
480    kaboom();
481}
482
483/*
484 * Generate the bootif string.
485 */
486static void make_bootif_string(void)
487{
488    static char bootif_str[7+3*(MAC_MAX+1)];
489    const uint8_t *src;
490    char *dst = bootif_str;
491    int i;
492
493    dst += sprintf(dst, "BOOTIF=%02x", MAC_type);
494    src = MAC;
495    for (i = MAC_len; i; i--)
496        dst += sprintf(dst, "-%02x", *src++);
497
498    sysappend_strings[SYSAPPEND_BOOTIF] = bootif_str;
499}
500
501/*
502 * Generate an ip=<client-ip>:<boot-server-ip>:<gw-ip>:<netmask>
503 * option into IPOption based on DHCP information in IPInfo.
504 *
505 */
506static void genipopt(void)
507{
508    static char ip_option[3+4*16];
509    const uint32_t *v = &IPInfo.myip;
510    char *p;
511    int i;
512
513    p = stpcpy(ip_option, "ip=");
514
515    for (i = 0; i < 4; i++) {
516        p += gendotquad(p, *v++);
517        *p++ = ':';
518    }
519    *--p = '\0';
520
521    sysappend_strings[SYSAPPEND_IP] = ip_option;
522}
523
524
525/* Generate ip= option and print the ip adress */
526static void ip_init(void)
527{
528    uint32_t ip = IPInfo.myip;
529    char dot_quad_buf[16];
530
531    genipopt();
532    gendotquad(dot_quad_buf, ip);
533
534    ip = ntohl(ip);
535    ddprintf("My IP address seems to be %08X %s\n", ip, dot_quad_buf);
536}
537
538/*
539 * Network-specific initialization
540 */
541static void network_init(void)
542{
543    net_parse_dhcp();
544
545    make_bootif_string();
546    /* If DMI and DHCP disagree, which one should we set? */
547    if (have_uuid)
548        sysappend_set_uuid(uuid);
549    ip_init();
550
551    /* print_sysappend(); */
552    /*
553     * Check to see if we got any PXELINUX-specific DHCP options; in particular,
554     * if we didn't get the magic enable, do not recognize any other options.
555     */
556    if ((DHCPMagic & 1) == 0)
557        DHCPMagic = 0;
558
559    net_core_init();
560}
561
562/*
563 * Initialize pxe fs
564 *
565 */
566static int pxe_fs_init(struct fs_info *fs)
567{
568    (void)fs;    /* drop the compile warning message */
569
570    /* Prepare for handling pxe interrupts */
571    pxe_init_isr();
572
573    /* This block size is actually arbitrary... */
574    fs->sector_shift = fs->block_shift = TFTP_BLOCKSIZE_LG2;
575    fs->sector_size  = fs->block_size  = 1 << TFTP_BLOCKSIZE_LG2;
576
577    /* Find the PXE stack */
578    if (pxe_init(false))
579        kaboom();
580
581    /* See if we also have a gPXE stack */
582    gpxe_init();
583
584    /* Network-specific initialization */
585    network_init();
586
587    /* Initialize network-card-specific idle handling */
588    pxe_idle_init();
589
590    /* Our name for the root */
591    strcpy(fs->cwd_name, "::");
592
593    return 0;
594}
595
596/*
597 * Look to see if we are on an EFI CSM system.  Some EFI
598 * CSM systems put the BEV stack in low memory, which means
599 * a return to the PXE stack will crash the system.  However,
600 * INT 18h works reliably, so in that case hack the stack and
601 * point the "return address" to an INT 18h instruction.
602 *
603 * Hack the stack instead of the much simpler "just invoke INT 18h
604 * if we want to reset", so that chainloading other NBPs will work.
605 *
606 * This manipulates the real-mode InitStack directly.  It relies on this
607 * *not* being a currently active stack, i.e. the former
608 * USE_PXE_PROVIDED_STACK no longer works.
609 *
610 * XXX: Disable this until we can find a better way to discriminate
611 * between BIOSes that are broken on BEV return and BIOSes which are
612 * broken on INT 18h.  Keying on the EFI CSM turns out to cause more
613 * problems than it solves.
614 */
615extern far_ptr_t InitStack;
616
617struct efi_struct {
618    uint32_t magic;
619    uint8_t  csum;
620    uint8_t  len;
621} __attribute__((packed));
622#define EFI_MAGIC (('$' << 24)+('E' << 16)+('F' << 8)+'I')
623
624static inline bool is_efi(const struct efi_struct *efi)
625{
626    /*
627     * We don't verify the checksum, because it seems some CSMs leave
628     * it at zero, sigh...
629     */
630    return (efi->magic == EFI_MAGIC) && (efi->len >= 83);
631}
632
633#if 0
634static void install_int18_hack(void)
635{
636    static const uint8_t int18_hack[] =
637    {
638        0xcd, 0x18,                     /* int $0x18 */
639        0xea, 0xf0, 0xff, 0x00, 0xf0,   /* ljmpw $0xf000,$0xfff0 */
640        0xf4                            /* hlt */
641    };
642    uint16_t *retcode;
643
644    retcode = GET_PTR(*(far_ptr_t *)((char *)GET_PTR(InitStack) + 44));
645
646    /* Don't do this if the return already points to int $0x18 */
647    if (*retcode != 0x18cd) {
648        uint32_t efi_ptr;
649        bool efi = false;
650
651        for (efi_ptr = 0xe0000 ; efi_ptr < 0x100000 ; efi_ptr += 16) {
652            if (is_efi((const struct efi_struct *)efi_ptr)) {
653                efi = true;
654                break;
655            }
656        }
657
658        if (efi) {
659            uint8_t *src = GET_PTR(InitStack);
660            uint8_t *dst = src - sizeof int18_hack;
661
662            memmove(dst, src, 52);
663            memcpy(dst+52, int18_hack, sizeof int18_hack);
664            InitStack.offs -= sizeof int18_hack;
665
666            /* Clobber the return address */
667            *(uint16_t *)(dst+44) = OFFS_WRT(dst+52, InitStack.seg);
668            *(uint16_t *)(dst+46) = InitStack.seg;
669        }
670    }
671}
672#endif
673
674static int pxe_readdir(struct file *file, struct dirent *dirent)
675{
676    struct inode *inode = file->inode;
677    struct pxe_pvt_inode *socket = PVT(inode);
678
679    if (socket->ops->readdir)
680        return socket->ops->readdir(inode, dirent);
681    else
682        return -1;              /* No such operation */
683}
684
685const struct fs_ops pxe_fs_ops = {
686    .fs_name       = "pxe",
687    .fs_flags      = FS_NODEV,
688    .fs_init       = pxe_fs_init,
689    .searchdir     = pxe_searchdir,
690    .chdir         = pxe_chdir,
691    .realpath      = pxe_realpath,
692    .getfssec      = pxe_getfssec,
693    .close_file    = pxe_close_file,
694    .mangle_name   = pxe_mangle_name,
695    .chdir_start   = pxe_chdir_start,
696    .open_config   = pxe_open_config,
697    .readdir       = pxe_readdir,
698    .fs_uuid       = NULL,
699};
Note: See TracBrowser for help on using the repository browser.