; "$Id: pxechain.asm,v 1.2 2007/12/16 08:15:39 jhutz Exp $" ; -*- fundamental -*- (asm-mode sucks) vim:noet:com=\:; ; **************************************************************************** ; ; pxechain.asm ; ; A comboot program to chain from PXELINUX to another PXE network ; bootstrap program (NBP). This improves on PXELINUX's built-in PXE ; chaining support by arranging for the server address and boot filename ; reported by the PXE stack to be those from which the new NBP was ; loaded, allowing PXELINUX to be used to select from multiple NBP's, ; such as gPXE, another PXELINUX(*), Windows RIS, and so on. ; ; (*) This seems unnecessary at first, but it is very helpful when ; selecting from among self-contained network boot images. ; ; Copyright (c) 2007 Carnegie Mellon University ; Copyright (C) 1994-2007 H. Peter Anvin ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation, Inc., 53 Temple Place Ste 330, ; Boston MA 02111-1307, USA; either version 2 of the License, or ; (at your option) any later version; incorporated herein by reference. ; ; **************************************************************************** ;%define DEBUG ;%define NO_RUN absolute 0 pspInt20: resw 1 pspNextP: resw 1 resb 124 pspCmdLen: resb 1 pspCmdArg: resb 127 section .text org 0x100 %ifdef DEBUG %macro MARK 1.nolist mov ah,0x02 mov dl,%1&0xff int 0x21 %if (%1 >> 8) & 0xff mov dl,(%1 >> 8) & 0xff int 0x21 %if (%1 >> 16) & 0xff mov dl,(%1 >> 16) & 0xff int 0x21 %if (%1 >> 24) & 0xff mov dl,(%1 >> 24) & 0xff int 0x21 %endif %endif %endif mov dl,' ' int 0x21 %endmacro %macro SHOWD 1.nolist mov al,%1 call print_dec mov ah,0x02 mov dl,' ' int 0x21 %endmacro %macro SHOWX 1.nolist mov bx,%1 call print_hex mov ah,0x02 mov dl,' ' int 0x21 %endmacro %else %macro MARK 1.nolist %endmacro %macro SHOWD 1.nolist %endmacro %macro SHOWX 1.nolist %endmacro %endif _start: MARK 'INIT' ; There should be exactly one command-line argument, which is of the form ; [[ipaddress]::]tftp_filename, just like filenames given to PXELINUX. ; Too few or too many arguments is an error. ; ; This code is based on mangle_name in pxelinux.asm parse_args: cld xor cx,cx mov cl,[pspCmdLen] dec cx mov si,pspCmdArg+1 and cx,cx je near usage ; no args is bad add si,cx dec si std .chomp: lodsb cmp al,' ' loopz .chomp inc cx cld mov [pspCmdLen],cl mov si,pspCmdArg+1 cmp word [si],'::' ; Leading ::? je near gotprefix dec cx jz noip MARK 'SCAN' .more: inc si cmp byte [si],' ' je near usage cmp word [si],'::' je near parse_ip loop .more noip: MARK 'NOIP' mov ax,0x0e ; get config file name int 0x22 mov si,bx %ifdef DEBUG mov ah,0x02 mov dl,'[' int 0x21 mov ax,0x02 int 0x22 mov ah,0x02 mov dl,']' int 0x21 mov dl,' ' int 0x21 %endif push ds push es pop ds pop es push es .find_prefix: lodsb and al,al jnz .find_prefix dec si mov cx,si sub cx,bx MARK 'LEN' SHOWD cl ; assume it's <256 for debugging dec si std .find_slash: lodsb cmp al,'/' je .slash loop .find_slash .slash: cmp cx,127 cld jna .copy_prefix pop ds jmp too_long .copy_prefix: SHOWD cl MARK 'PFX' mov si,bx mov di,tftp_filename mov bx,128 sub bx,cx rep movsb pop ds mov cl,[pspCmdLen] mov si,pspCmdArg+1 jmp prefix_done usage: xor cx,cx mov si,msg_usage jmp fail too_long: xor cx,cx mov si,msg_too_long jmp fail parse_ip: MARK 'PIP' mov di,si mov si,pspCmdArg+1 call parse_dotquad jc .notdq cmp si,di ; is it the same place? jne .notdq mov [tftp_siaddr],eax jmp gotprefix .notdq: MARK 'NDQ' mov si,di mov bx,pspCmdArg+1 mov ax,0x0010 ; DNS resolve int 0x22 and eax,eax jz noip mov [tftp_siaddr],eax gotprefix: MARK 'GOTP' dec cx ; skip the :: dec cx inc si inc si mov di,tftp_filename mov bx,128 prefix_done: SHOWD bl MARK 'LEFT' ; SI points at the filename, plus remaining arguments, ; CX contains their combined length. ; DI points to where the filename should be stored ; BX says how much space is left for the filename and NUL and cx,cx jz usage ; no args is bad .copy_filename: lodsb %ifdef DEBUG mov dl,al mov ah,0x2 int 0x21 %endif cmp al,' ' je usage dec bx jz too_long stosb loop .copy_filename xor eax,eax stosb ; get PXE cached data MARK 'GCI' mov ax,0x0009 ; call PXE stack mov bx,0x0071 ; PXENV_GET_CACHED_INFO mov di,PXECacheParms int 0x22 and eax,eax jz .fix_siaddr mov cx,[gci_status] mov si,msg_get_cache jmp fail .fix_siaddr: mov bx,[gci_bufferseg] mov es,bx mov bx,[gci_buffer] mov eax,[es:bx+12] ; save our address (ciaddr) mov [open_ciaddr],eax ; ... in case we have to do UDP open mov eax,[tftp_siaddr] and eax,eax jnz .replace_addr MARK 'ADDR' mov eax,[es:bx+20] ; siaddr mov [tftp_siaddr],eax jmp .addr_done .replace_addr: mov [es:bx+20],eax .addr_done: mov si,tftp_filename ; copy the new filename... lea di,[es:bx+108] ; to the "cached DHCP response" mov cx,128 rep movsb mov bx,ds ; restore es before proceeding mov es,bx ; print out what we are doing %ifdef DEBUG mov ah,0x02 ; write character mov dl,0x0d ; print a CRLF first int 0x21 mov dl,0x0a int 0x21 %endif mov ax,0x0002 ; write string mov bx,msg_booting int 0x22 mov ebx,[tftp_siaddr] call print_dotquad mov ah,0x02 ; write character mov dl,' ' int 0x21 mov ax,0x0002 ; write string mov bx,tftp_filename int 0x22 mov ah,0x02 ; write character mov dl,0x0d int 0x21 mov dl,0x0a int 0x21 %ifndef NO_RUN mov ax,0x0009 ; call PXE stack mov bx,0x0031 ; PXENV_UDP_CLOSE mov di,PXECloseParms int 0x22 mov cx,[close_status] mov si,msg_udp_close and ax,ax jnz fail mov ax,0x0009 ; call PXE stack mov bx,0x0073 ; PXENV_RESTART_TFTP mov di,PXERestartTFTPParms int 0x22 mov cx,[tftp_status] mov si,msg_rst_tftp call fail mov ax,0x0009 ; call PXE stack mov bx,0x0030 ; PXENV_UDP_OPEN mov di,PXEOpenParms int 0x22 mov cx,[open_status] mov si,msg_udp_open and ax,ax jnz fail ret %endif fail: MARK 'FAIL' SHOWX cs SHOWX ds SHOWX es SHOWX si %ifdef DEBUG mov ah,0x02 ; write character mov dl,0x0d ; print a CRLF first int 0x21 mov dl,0x0a int 0x21 %endif mov ax,0x0002 ; write string mov bx,msg_progname ; print our name int 0x22 mov bx,si ; ... the error message int 0x22 mov ah,0x02 ; write character jcxz .done mov dl,' ' ; ... and the error code, in [] int 0x21 mov dl,'[' int 0x21 mov bx,cx call print_hex mov ah,0x02 ; write character mov dl,']' int 0x21 .done: mov dl,0x0d ; and finally a CRLF int 0x21 mov dl,0x0a int 0x21 ret ; print_hex ; ; Take a 16-bit integer in BX and print it as 2 hex digits. ; Destroys AX and DL. ; print_hex: mov al,bh aam 16 cmp ah,10 jb .lt_a000 add ah,'A'-'0'-10 .lt_a000: add ah,'0' mov dl,ah mov ah,0x02 ; write character int 0x21 cmp al,10 jb .lt_a00 add al,'A'-'0'-10 .lt_a00: add al,'0' mov dl,al mov ah,0x02 ; write character int 0x21 mov al,bl aam 16 cmp ah,10 jb .lt_a0 add ah,'A'-'0'-10 .lt_a0: add ah,'0' mov dl,ah mov ah,0x02 ; write character int 0x21 cmp al,10 jb .lt_a add al,'A'-'0'-10 .lt_a: add al,'0' mov dl,al mov ah,0x02 ; write character int 0x21 ret ; print_dec ; ; Take an 8-bit integer in AL and print it in decimal. ; Destroys AX and DL. ; print_dec: cmp al,10 ; < 10? jb .lt10 ; If so, skip first 2 digits cmp al,100 ; < 100 jb .lt100 ; If so, skip first digit aam 100 ; Now AH = 100-digit; AL = remainder add ah,'0' mov dl,ah mov ah,0x02 int 0x21 .lt100: aam 10 ; Now AH = 10-digit; AL = remainder add ah,'0' mov dl,ah mov ah,0x02 int 0x21 .lt10: add al,'0' mov dl,al mov ah,0x02 int 0x21 ret ; print_dotquad ; ; Take an IP address (in network byte order) in EBX and print it ; as a dotted quad. ; Destroys EAX, EBX, ECX, EDX ; print_dotquad: mov cx,3 .octet: mov al,bl call print_dec jcxz .done mov ah,0x02 mov dl,'.' int 0x21 ror ebx,8 ; Move next char into LSB dec cx jmp .octet .done: ret ; parse_dotquad: ; Read a dot-quad pathname in DS:SI and output an IP ; address in EAX, with SI pointing to the first ; nonmatching character. ; ; Return CF=1 on error. ; ; No segment assumptions permitted. ; parse_dotquad: push cx mov cx,4 xor eax,eax .parseloop: mov ch,ah mov ah,al lodsb sub al,'0' jb .notnumeric cmp al,9 ja .notnumeric aad ; AL += 10 * AH; AH = 0; xchg ah,ch jmp .parseloop .notnumeric: cmp al,'.'-'0' pushf mov al,ah mov ah,ch xor ch,ch ror eax,8 popf jne .error loop .parseloop jmp .done .error: loop .realerror ; If CX := 1 then we're done clc jmp .done .realerror: stc .done: dec si ; CF unchanged! pop cx ret section .data msg_booting: db 'TFTP boot: ',0 msg_progname: db 'pxechain: ',0 msg_usage: db 'usage: pxechain.cbt [[ipaddress]::]filename',0dh,0ah,0 msg_too_long: db 'pxechain: filename is too long (max 127)',0dh,0ah,0 msg_get_cache: db 'PXENV_GET_CACHED_INFO',0 msg_rst_tftp: db 'PXENV_RESTART_TFTP',0 msg_udp_close: db 'PXENV_UDP_CLOSE',0 msg_udp_open: db 'PXENV_UDP_OPEN',0 PXECacheParms: gci_status: dw 0 gci_packettype: dw 3 ; PXENV_PACKET_TYPE_CACHED_REPLY gci_buffersize: dw 0 gci_buffer: dw 0 gci_bufferseg: dw 0 gci_bufferlim: dw 0 PXERestartTFTPParms: tftp_status: dw 0 tftp_filename: times 128 db 0 tftp_bufsize: dd 0x00090000 ; available memory for NBP tftp_bufaddr: dd 0x00007c00 ; PXE NBP load address tftp_siaddr: dd 0 tftp_giaddr: dd 0 tftp_mcaddr: dd 0 tftp_mcport: dw 0 tftp_msport: dw 0 tftp_timeout: dw 0 tftp_reopendly: dw 0 PXECloseParms: close_status: dw 0 PXEOpenParms: open_status: dw 0 open_ciaddr: dd 0