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
17 uint8_t MAC[MAC_MAX]; /* Actual MAC address */
18 uint8_t MAC_len; /* MAC address len */
19 uint8_t MAC_type; /* MAC address type */
20
21 char boot_file[256]; /* From DHCP */
22 char path_prefix[256]; /* From DHCP */
23
24 bool 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 */
allocate_socket(struct fs_info * fs)30 static 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
free_socket(struct inode * inode)43 void 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
pxe_close_file(struct file * file)51 static 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 */
ip_ok(uint32_t ip)72 bool 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 */
gendotquad(char * dst,uint32_t ip)92 static 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 */
pxe_call(int opcode,void * data)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(®s, 0, sizeof regs);
118 regs.ebx.w[0] = opcode;
119 regs.es = SEG(data);
120 regs.edi.w[0] = OFFS(data);
121 call16(pxenv, ®s, ®s);
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 */
pxe_mangle_name(char * dst,const char * src)136 static 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 */
pxe_getc(struct inode * inode)151 int 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 */
fill_buffer(struct inode * inode)174 static 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 */
pxe_getfssec(struct file * file,char * buf,int blocks,bool * have_more)196 static 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 */
url_set_ip(struct url_info * url)240 static 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 */
258 static void __pxe_searchdir(const char *filename, int flags, struct file *file);
259 extern uint16_t PXERetry;
260
pxe_searchdir(const char * filename,int flags,struct file * file)261 static 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 }
__pxe_searchdir(const char * filename,int flags,struct file * file)271 static 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 */
get_prefix(void)344 static 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 */
pxe_realpath(struct fs_info * fs,char * dst,const char * src,size_t bufsize)399 static 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 */
pxe_chdir(struct fs_info * fs,const char * src)409 static 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
pxe_chdir_start(void)424 static 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 */
pxe_open_config(struct com32_filedata * filedata)431 static 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 */
make_bootif_string(void)486 static 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 */
genipopt(void)506 static 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 */
ip_init(void)526 static 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 */
network_init(void)541 static 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 */
pxe_fs_init(struct fs_info * fs)566 static 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 */
615 extern far_ptr_t InitStack;
616
617 struct 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
is_efi(const struct efi_struct * efi)624 static 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
634 static 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
pxe_readdir(struct file * file,struct dirent * dirent)674 static 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
685 const 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 };
700