├── .gitignore ├── README.md ├── build ├── config.conf ├── config.help ├── run └── src └── battlesnake ├── application └── Boot_Server.java ├── bootserver ├── Configuration.java ├── DHCP.java └── TFTP.java ├── logging ├── Entry.java ├── Listener.java └── Log.java ├── packets ├── DHCPPacket.java ├── IP.java ├── Packet.java ├── PayloadPacket.java ├── TFTPPacket.java ├── TFTPSession.java └── dhcpdefs │ ├── OptionDefinition.java │ └── OptionDefinitions.java ├── simpleservers ├── Server.java └── UDPServer.java └── utils ├── CaseInsensitiveHashMap.java ├── Input.java ├── Network.java └── Path.java /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | boot.exe 3 | boot.exe.jsmooth 4 | Windows_Enable_Router.reg 5 | *.7z 6 | *.xz 7 | *.jar 8 | .project 9 | .classpath 10 | .settings 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bootserver 2 | ========== 3 | 4 | Partial DHCP/BOOTP/TFTP server, sufficient for serving PXE network installers 5 | for most Linux distros. Written in Java so that it can run on 6 | Windows/Linux/Android/BSD without much fuss. 7 | 8 | On Linux/Mac: 9 | The `./build` script will compile the application, using `javac`. 10 | The `./run` script will then launch the application, using `java` 11 | Specify the `-d` switch to `./run` to launch in debug mode. 12 | * The server requires privileges to listen on ports <1024, so you will 13 | probably need to run as root. 14 | 15 | On Windows: 16 | The run/build scripts can probably be converted to `.cmd` batch files with 17 | minimal effort. 18 | Otherwise, either use Cygwin to provide `bash`, or add the source tree to an 19 | Eclipse project and compile/run from the IDE. 20 | This application was actually developed on Windows... 21 | 22 | On Android: 23 | The server requires root access, so you will need a rooted phone. 24 | Either launch it from Terminal Emulator, or write an nice little app that 25 | launches the server class as root, 26 | and create a nice interface to wrap `config.conf` while you're at it. 27 | 28 | See `config.help` and optionally the program source code for documentation on 29 | the `config.conf` file. 30 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ ! -d ./bin ] 3 | then mkdir bin 4 | fi 5 | javac -d bin `find ./src/ -name *.java` 6 | -------------------------------------------------------------------------------- /config.conf: -------------------------------------------------------------------------------- 1 | boot_file=ipxe.bin 2 | boot_file_size=0 3 | dhcp_authoritive=true 4 | dhcp_broadcasts_only=false 5 | dhcp_client_port=68 6 | dhcp_enabled=true 7 | dhcp_enforce_subnet=true 8 | dhcp_log_packets=false 9 | dhcp_opt_boot_server_address=0.0.0.0 10 | dhcp_opt_dns_domain= 11 | dhcp_opt_dns_server_address=0.0.0.0 12 | dhcp_opt_gateway_address=0.0.0.0 13 | dhcp_opt_time_server_address=0.0.0.0 14 | dhcp_pool_address=192.168.2.0 15 | dhcp_pool_length=10 16 | dhcp_pool_offset=200 17 | dhcp_reply_delay=0 18 | dhcp_server_port=67 19 | dhcp_subnet_mask=255.255.255.0 20 | network_address=192.168.0.100 21 | network_interface=eth1 22 | tftp_enabled=true 23 | tftp_log_packets=false 24 | tftp_root_folder=/srv/tftp 25 | tftp_server_port=69 26 | tftp_windows_symlink_hack=false 27 | -------------------------------------------------------------------------------- /config.help: -------------------------------------------------------------------------------- 1 | ## See src/battlesnake/bootserver/Configuration.java for the Configuration 2 | ## class, which this file was created from (with some regular expression 3 | ## translation) 4 | ## 5 | ## The configuration file (default: config.conf) is just a list of key=value 6 | ## lines with no comments or data types specified. Only IPv4 is supported 7 | ## currently, mainly out of laziness since I can't see a situation where you'd 8 | ## require IPv6 to communicate within a home/office network. 9 | ## 10 | ## ipaddr=xxx.xxx.xxx.xxx 11 | ## string="" 12 | ## boolean=true/false 13 | ## number= 14 | 15 | ## Device to bind to 16 | # Interface name (e.g. eth0) 17 | string network_interface 18 | # Interface address (e.g. 192.169.10.1) 19 | ipaddr network_address 20 | 21 | ## DHCP server parameters 22 | # Enable the DHCP service 23 | boolean dhcp_enabled 24 | # Log all DHCP packets 25 | boolean dhcp_log_packets 26 | # Authoritive DHCP server? If true, it will hijack packets messages intended 27 | # for other DHCP servers. Use if this is the only DHCP server on the network, 28 | # or if you don't mind breaking other people's connectivity 29 | boolean dhcp_authoritive = true; 30 | # The port that the server should listen on. Note: ports <1024 require 31 | # privileges on *n?x, so remember to run as root/sudo, or even better: 32 | # configure the network privileges properly to begin with... 33 | # Default = 67 (RFC 1541) 34 | number dhcp_server_port 35 | # The client port to send responses to. Default = 68 (RFC 1541) 36 | number dhcp_client_port 37 | # The address of the DHCP subnet (e.g. 192.168.10.0) 38 | ipaddr dhcp_pool_address = 0; 39 | # The offset for the first address of the DHCP pool (e.g. 100) 40 | number dhcp_pool_offset = 0; 41 | # The number of addresses in the pool (e.g. 20) 42 | number dhcp_pool_length = 0; 43 | # The subnet mask to issue (e.g. 255.255.255.0) 44 | netmask dhcp_subnet_mask = 0; 45 | # Whether to enforce the subnet (deny requests for addresses outside the 46 | # subnet) 47 | boolean dhcp_enforce_subnet = true; 48 | # Ignore packets that aren't broadcasted 49 | boolean dhcp_broadcasts_only = true; 50 | # Delay before replying 51 | number dhcp_reply_delay = 0; 52 | 53 | ## Other DHCP parameters 54 | # If an address = 127.0.0.1, it will be replaced by the interface address 55 | # which may be set in the field 56 | # Address of time server (optional) 57 | number dhcp_opt_time_server_address 58 | # Address of default gateway (optional, but net-installing Linux is pretty dead 59 | # without it) 60 | number dhcp_opt_gateway_address 61 | # Address of DNS server (optional, but see previous comment) 62 | number dhcp_opt_dns_server_address 63 | # DNS domain 64 | string dhcp_opt_dns_domain 65 | # Address of boot server (set to zero to use this server, if the TFTP service 66 | # is enabled) 67 | number dhcp_opt_boot_server_address 68 | 69 | ## TFTP server (note: this as NOTHING at all to do with FTP) 70 | # Enable the TFTP service 71 | boolean tftp_enabled 72 | # The port for the TFTP server to listen on. Default = 69 (RFC 783) 73 | number tftp_server_port 74 | # Log TFTP packets 75 | boolean tftp_log_packets 76 | 77 | ## TFTP root folder 78 | # Files and folders within this are accessible via the TFTP server, if enabled 79 | # e.g. /var/tftp 80 | string tftp_root_folder 81 | 82 | ## Windows symlink hack for TFTP server: 83 | ## 84 | ## Some Linux network install tarballs contain symbolic links, which 85 | ## obviously don't work on Windows are will just appear to be text files 86 | ## containing the path to the target file. This symlink hack attempts to 87 | ## identify and follow such files, allowing network installers for 88 | ## various Linux distributions to be served from a Windows host. 89 | ## 90 | ## The heuristic to detect these symlinks is as follows: 91 | ## - File size < 200 bytes 92 | ## - File contents ~= /^[A-Za-z0-9-_\.]+$/ 93 | ## 94 | ## The contents of a matching file are treated as a relative path and 95 | ## the link is followed by the server. 96 | ## 97 | # TODO: ensure that the path parser follows each path element as it 98 | # decodes them, so it can follow symlinks to directories. 99 | boolean tftp_windows_symlink_hack 100 | 101 | ## BOOTP/DHCP filename 102 | # e.g. "pxelinux.0" for typical Linux network installer (particularly Ubuntu) 103 | string boot_file = "pxelinux.0"; 104 | # If zero, the server will calculate the size automatically 105 | number boot_file_size = 0; 106 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [ "$1" == "-d" ] 3 | then DEBUG="-Xdebug" 4 | else DEBUG= 5 | fi 6 | sudo java $DEBUG -cp ./bin/ battlesnake/application/Boot_Server 7 | -------------------------------------------------------------------------------- /src/battlesnake/application/Boot_Server.java: -------------------------------------------------------------------------------- 1 | package battlesnake.application; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.InterfaceAddress; 6 | import java.net.NetworkInterface; 7 | import java.net.SocketException; 8 | import java.util.Collections; 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | import battlesnake.bootserver.Configuration; 13 | import battlesnake.bootserver.DHCP; 14 | import battlesnake.bootserver.TFTP; 15 | import battlesnake.logging.Entry; 16 | import battlesnake.logging.Listener; 17 | import battlesnake.logging.Log; 18 | import battlesnake.packets.IP; 19 | import battlesnake.utils.Input; 20 | import battlesnake.utils.Network; 21 | 22 | public class Boot_Server { 23 | 24 | /* Server configuration file */ 25 | static final String conf = "config.conf"; 26 | 27 | /* Network interface and address to bind to */ 28 | static NetworkInterface intf; 29 | static InterfaceAddress addr; 30 | 31 | /* 32 | * Lists the available network interfaces and asks the user to select 33 | * one 34 | */ 35 | static NetworkInterface GetConfigInterface() throws SocketException, IOException { 36 | Map choices = new HashMap(); 37 | for (NetworkInterface intf : Collections.list(NetworkInterface.getNetworkInterfaces())) { 38 | choices.put(intf.getName() + " (" + intf.getDisplayName() + ")", intf); 39 | } 40 | intf = Input.Choice("Available network interfaces", "Select network interface to bind to", choices, null); 41 | Configuration.network_interface = intf.getName(); 42 | return intf; 43 | } 44 | 45 | /* 46 | * Lists the addresses bound to the selected interface and asks the user 47 | * to select one 48 | */ 49 | static InterfaceAddress GetConfigAddress(NetworkInterface intf) throws SocketException, IOException { 50 | Map choices = new HashMap(); 51 | for (InterfaceAddress addr : intf.getInterfaceAddresses()) { 52 | choices.put(addr.getAddress().getHostAddress() + "/" + addr.getNetworkPrefixLength(), addr); 53 | } 54 | addr = Input.Choice("Addresses bound to this interface", "Select address to bind to", choices, null); 55 | Configuration.network_address = IP.BytesToInt(addr.getAddress().getAddress()); 56 | return addr; 57 | } 58 | 59 | /* Asks the user to choose the interface and the address to bind to */ 60 | static void GetConfig() throws Exception { 61 | NetworkInterface intf = GetConfigInterface(); 62 | InterfaceAddress addr = GetConfigAddress(intf); 63 | addr.toString(); 64 | } 65 | 66 | public static void main(String[] args) { 67 | new Boot_Server().Run(args); 68 | } 69 | 70 | /* Automatic configuration, fills in the blanks */ 71 | static void AutoConfig() { 72 | String app = "Auto-configure"; 73 | /* Default DHCP server parameters */ 74 | if (Configuration.dhcp_subnet_mask == 0) { 75 | Configuration.dhcp_subnet_mask = ~(0xffffffff >>> addr.getNetworkPrefixLength()); 76 | Log.Add(app, Log.TYPE_INFO, "dhcp_subnet_mask = " + IP.IntToStr(Configuration.dhcp_subnet_mask)); 77 | } 78 | if (Configuration.dhcp_pool_address == 0) { 79 | Configuration.dhcp_pool_address = Configuration.network_address & Configuration.dhcp_subnet_mask; 80 | Log.Add(app, Log.TYPE_INFO, "dhcp_pool_address = " + IP.IntToStr(Configuration.dhcp_pool_address)); 81 | } 82 | if (Configuration.dhcp_pool_offset == 0) { 83 | Configuration.dhcp_pool_offset = 20; 84 | Log.Add(app, Log.TYPE_INFO, "dhcp_pool_offset = " + Configuration.dhcp_pool_offset); 85 | } 86 | if (Configuration.dhcp_pool_length == 0) { 87 | Configuration.dhcp_pool_length = 20; 88 | Log.Add(app, Log.TYPE_INFO, "dhcp_pool_length = " + Configuration.dhcp_pool_length); 89 | } 90 | /* 91 | * Parses DHCP optional addresses, replacing 127.0.0.1 with the 92 | * server IP address 93 | */ 94 | if (Configuration.dhcp_opt_gateway_address == 0x7f000001) { 95 | Configuration.dhcp_opt_gateway_address = Configuration.network_address; 96 | Log.Add(app, Log.TYPE_INFO, "dhcp_opt_gateway_address = " + IP.IntToStr(Configuration.dhcp_opt_gateway_address)); 97 | } 98 | if (Configuration.dhcp_opt_dns_server_address == 0x7f000001) { 99 | Configuration.dhcp_opt_dns_server_address = Configuration.network_address; 100 | Log.Add(app, Log.TYPE_INFO, "dhcp_opt_dns_server_address = " + IP.IntToStr(Configuration.dhcp_opt_dns_server_address)); 101 | } 102 | if (Configuration.dhcp_opt_time_server_address == 0x7f000001) { 103 | Configuration.dhcp_opt_time_server_address = Configuration.network_address; 104 | Log.Add(app, Log.TYPE_INFO, "dhcp_opt_time_server_address = " + IP.IntToStr(Configuration.dhcp_opt_time_server_address)); 105 | } 106 | /* 107 | * Parses DHCP optional addresses, replacing enabled-but-not-set 108 | * (i.e. ==1) with the default gateway address 109 | */ 110 | int Gateway = Network.getGateway(); 111 | if ((Gateway & Configuration.dhcp_subnet_mask) != (Configuration.network_address & Configuration.dhcp_subnet_mask)) { 112 | Gateway = 0; 113 | } 114 | if (Gateway != 0) { 115 | if (Configuration.dhcp_opt_gateway_address == 1) { 116 | Configuration.dhcp_opt_gateway_address = Gateway; 117 | Log.Add(app, Log.TYPE_INFO, "dhcp_opt_gateway_address = " + IP.IntToStr(Configuration.dhcp_opt_gateway_address)); 118 | } 119 | if (Configuration.dhcp_opt_dns_server_address == 1) { 120 | Configuration.dhcp_opt_dns_server_address = Gateway; 121 | Log.Add(app, Log.TYPE_INFO, "dhcp_opt_dns_server_address = " + IP.IntToStr(Configuration.dhcp_opt_dns_server_address)); 122 | } 123 | if (Configuration.dhcp_opt_time_server_address == 1) { 124 | Configuration.dhcp_opt_time_server_address = Gateway; 125 | Log.Add(app, Log.TYPE_INFO, "dhcp_opt_time_server_address = " + IP.IntToStr(Configuration.dhcp_opt_time_server_address)); 126 | } 127 | } 128 | /* 129 | * Default TFTP root folder = /TFTP or \TFTP depending on 130 | * operating system 131 | */ 132 | if (Configuration.tftp_root_folder == "") { 133 | Configuration.tftp_root_folder = File.pathSeparator + "TFTP"; 134 | Log.Add(app, Log.TYPE_INFO, "tftp_root_folder = " + Configuration.tftp_root_folder); 135 | } 136 | } 137 | 138 | /* Load and validate the configuration */ 139 | private void prepareConfiguration() throws Exception { 140 | /* 141 | * Load server configuration 142 | */ 143 | Log.Add(this, Log.TYPE_INFO, "Loading configuration from " + conf); 144 | Log.Indent(); 145 | Configuration.Load(conf); 146 | Log.Unindent(); 147 | System.out.print("\n"); 148 | /* 149 | * Get interface and address from user 150 | */ 151 | GetConfig(); 152 | Log.Add(this, Log.TYPE_INFO, "Autoconfigure"); 153 | Log.Indent(); 154 | /* 155 | * Fill in the remaining blanks of the configuration 156 | */ 157 | AutoConfig(); 158 | Log.Unindent(); 159 | /* 160 | * Let the user know that they can save the configuration 161 | */ 162 | System.out.print("Press then to save the configuration.\n\n"); 163 | System.out.print("Press then to exit.\n\n"); 164 | /* 165 | * Ensure that we actually have a server to run 166 | */ 167 | if (!(Configuration.dhcp_enabled || Configuration.tftp_enabled)) 168 | throw new Exception("No servers are enabled in the configuration"); 169 | } 170 | 171 | /* Start the necessary server(s) */ 172 | private void startServers(DHCP dhcp, TFTP tftp) throws Exception { 173 | if (Configuration.dhcp_enabled) { 174 | dhcp.Start(true); 175 | } 176 | if (Configuration.tftp_enabled) { 177 | tftp.Start(true); 178 | } 179 | } 180 | 181 | /* Run until a server stops */ 182 | private void runServers(DHCP dhcp, TFTP tftp) throws Exception { 183 | while ((dhcp.getRunning() || !Configuration.dhcp_enabled) && (tftp.getRunning() || !Configuration.tftp_enabled)) { 184 | Thread.sleep(100); 185 | char c = Character.toUpperCase((char) System.in.read()); 186 | /* User wants to close the servers */ 187 | if (c == 'C') { 188 | dhcp.Stop(); 189 | tftp.Stop(); 190 | } 191 | /* User wants to save the configuration */ 192 | else if (c == 'S') { 193 | Configuration.Save(conf); 194 | Log.Add(this, Log.TYPE_INFO, "Configuration saved to " + conf); 195 | } 196 | /* User wants to see the DHCP map */ 197 | else if (c == 'M') { 198 | if (dhcp.getRunning()) { 199 | dhcp.ShowMaps(); 200 | } 201 | System.out.print("\n"); 202 | } 203 | } 204 | } 205 | 206 | private void Run(String[] args) { 207 | /* Create a log listener to copy log entries to STDERR */ 208 | Listener listener = new Listener() { 209 | @Override 210 | public void OnMessage(Entry msg) { 211 | System.err.print(msg.toString() + "\n"); 212 | } 213 | }; 214 | Log.Subscribe(listener); 215 | try { 216 | System.out.print("Mark's boot (DHCP+TFTP) server\n\n"); 217 | try { 218 | /* Load and validate the configuration */ 219 | prepareConfiguration(); 220 | /* Create the servers */ 221 | DHCP dhcp = new DHCP(); 222 | TFTP tftp = new TFTP(); 223 | /* Start the necessary server(s) */ 224 | startServers(dhcp, tftp); 225 | /* Run until a server stops */ 226 | runServers(dhcp, tftp); 227 | } 228 | /* Oops */ 229 | catch (Exception e) { 230 | boolean DEBUG = false; 231 | Log.Add(this, Log.TYPE_FATAL, "Error: " + e.getMessage()); 232 | if (DEBUG) 233 | e.printStackTrace(); 234 | } 235 | } 236 | finally { 237 | Log.Unsubscribe(listener); 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/battlesnake/bootserver/Configuration.java: -------------------------------------------------------------------------------- 1 | package battlesnake.bootserver; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.BufferedWriter; 5 | import java.io.FileReader; 6 | import java.io.FileWriter; 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.Modifier; 9 | import java.util.Map; 10 | import java.util.TreeMap; 11 | 12 | import battlesnake.logging.Log; 13 | import battlesnake.packets.IP; 14 | 15 | /* 16 | * Maintains the server configuration in static fields, and contains a 17 | * Serializer class which can load/save the contents of these fields to a 18 | * configuration file 19 | */ 20 | public class Configuration { 21 | /* Device to bind to */ 22 | public static String network_interface = ""; 23 | public static int network_address = 0; 24 | /* DHCP server parameters */ 25 | public static boolean dhcp_enabled = true; 26 | public static boolean dhcp_log_packets = false; 27 | public static boolean dhcp_authoritive = true; 28 | public static short dhcp_server_port = 67; 29 | public static short dhcp_client_port = 68; 30 | public static int dhcp_pool_address = 0; 31 | public static int dhcp_pool_offset = 0; 32 | public static int dhcp_pool_length = 0; 33 | public static int dhcp_subnet_mask = 0; 34 | public static boolean dhcp_enforce_subnet = true; 35 | public static boolean dhcp_broadcasts_only = true; 36 | public static int dhcp_reply_delay = 0; 37 | /* Other DHCP parameters */ 38 | public static int dhcp_opt_time_server_address = 0; 39 | public static int dhcp_opt_gateway_address = 0; 40 | public static int dhcp_opt_dns_server_address = 0; 41 | public static String dhcp_opt_dns_domain = ""; 42 | public static int dhcp_opt_boot_server_address = 0; 43 | /* TFTP server */ 44 | public static boolean tftp_enabled = true; 45 | public static short tftp_server_port = 69; 46 | public static boolean tftp_log_packets = false; 47 | /* TFTP root folder */ 48 | public static String tftp_root_folder = "/tftp"; 49 | /* 50 | * Windows symlink hack for TFTP server: 51 | * 52 | * Some Linux network install tarballs contain symbolic links, which 53 | * obviously don't work on Windows are will just appear to be text files 54 | * containing the path to the target file. This symlink hack attempts to 55 | * identify and follow such files, allowing network installers for 56 | * various Linux distributions to be served from a Windows host. 57 | * 58 | * The heuristic to detect these symlinks is as follows: 59 | * - File size < 200 bytes 60 | * - File contents ~= /^[A-Za-z0-9-_\.]+$/ 61 | * 62 | * The contents of a matching file are treated as a relative path and 63 | * the link is followed by the server. 64 | * 65 | * -TODO: ensure that the path parser follows each path element as it 66 | * decodes them, so it can follow symlinks to directories. 67 | */ 68 | public static boolean tftp_windows_symlink_hack = true; 69 | /* BOOTP/DHCP filename */ 70 | public static String boot_file = "pxelinux.0"; 71 | public static int boot_file_size = 0; 72 | 73 | public static boolean Load(String filename) { 74 | return Serializer.Load(filename); 75 | } 76 | 77 | public static boolean Save(String filename) { 78 | return Serializer.Save(filename); 79 | } 80 | 81 | /* Serializer for static fields */ 82 | private static class Serializer { 83 | 84 | private Map fields = new TreeMap(); 85 | 86 | /* Field represents an IP address (or something else with the dotted decimal format) */ 87 | static boolean isIp(String k, Class c) { 88 | return (k.endsWith("_address") || k.endsWith("net_mask")) && (c.equals(int.class) || c.equals(Integer.class)); 89 | } 90 | 91 | /* Save configuration to file */ 92 | public static boolean Save(String filename) { 93 | try { 94 | Serializer ser = Serializer.ReadSettingsFromObject(); 95 | BufferedWriter buf = new BufferedWriter(new FileWriter(filename)); 96 | for (Map.Entry f : ser.fields.entrySet()) { 97 | String k = f.getKey(); 98 | Object v = f.getValue(); 99 | /* Handle IP addresses */ 100 | if (isIp(k, v.getClass())) { 101 | v = IP.IntToStr((Integer) v); 102 | } 103 | buf.write(k + "=" + v.toString()); 104 | buf.newLine(); 105 | } 106 | buf.close(); 107 | return true; 108 | } 109 | catch (Exception e) { 110 | Log.Add("Configuration", Log.TYPE_ERR, e.getMessage()); 111 | return false; 112 | } 113 | } 114 | 115 | /* Load configuration from file */ 116 | public static boolean Load(String filename) { 117 | try { 118 | Serializer ser = Serializer.ReadSettingsFromObject(); 119 | BufferedReader buf = new BufferedReader(new FileReader(filename)); 120 | String s; 121 | while ((s = buf.readLine()) != null) { 122 | String[] f = s.split("=", 2); 123 | if (ser.fields.containsKey(f[0])) { 124 | ser.fields.put(f[0], f[1]); 125 | Log.Add("Configuration", Log.TYPE_INFO, f[0] + " = " + f[1]); 126 | } 127 | else { 128 | Log.Add("Configuration", Log.TYPE_ERR, "Configuration key " + f[0] + " not found"); 129 | } 130 | } 131 | buf.close(); 132 | ser.WriteSettingsToObject(); 133 | return true; 134 | } 135 | catch (Exception e) { 136 | Log.Add("Configuration", Log.TYPE_ERR, e.getMessage()); 137 | return false; 138 | } 139 | } 140 | 141 | /* Get the name of a field */ 142 | private static String fieldname(Field field) { 143 | return field.getName(); 144 | } 145 | 146 | /* Create a serializer and set the values to those of the configuration */ 147 | private static Serializer ReadSettingsFromObject() { 148 | Serializer me = new Serializer(); 149 | for (Field field : Configuration.class.getFields()) { 150 | try { 151 | if ((field.getModifiers() & Modifier.PUBLIC) != 0 && (field.getType().isPrimitive() || field.getType().equals(String.class))) { 152 | me.fields.put(fieldname(field), field.get(Configuration.class)); 153 | } 154 | } 155 | catch (Exception e) { 156 | Log.Add(me, Log.TYPE_ERR, e.getMessage()); 157 | } 158 | } 159 | return me; 160 | } 161 | 162 | /* Set the configuration */ 163 | private void WriteSettingsToObject() { 164 | for (Field field : Configuration.class.getFields()) { 165 | try { 166 | String k = fieldname(field); 167 | if (fields.containsKey(k)) { 168 | Object v = fields.get(k); 169 | // Handle IP addresses 170 | if (isIp(k, field.getType())) { 171 | v = IP.StrToInt((String) v); 172 | } 173 | Class c = field.getType(); 174 | if (c.equals(double.class)) { 175 | field.set(Configuration.class, Double.valueOf(v.toString())); 176 | } 177 | else if (c.equals(float.class)) { 178 | field.set(Configuration.class, Float.valueOf(v.toString())); 179 | } 180 | else if (c.equals(long.class)) { 181 | field.set(Configuration.class, Long.valueOf(v.toString())); 182 | } 183 | else if (c.equals(int.class)) { 184 | field.set(Configuration.class, Integer.valueOf(v.toString())); 185 | } 186 | else if (c.equals(short.class)) { 187 | field.set(Configuration.class, Short.valueOf(v.toString())); 188 | } 189 | else if (c.equals(byte.class)) { 190 | field.set(Configuration.class, Byte.valueOf(v.toString())); 191 | } 192 | else if (c.equals(boolean.class)) { 193 | field.set(Configuration.class, Boolean.valueOf(v.toString())); 194 | } 195 | else if (c.equals(char.class)) { 196 | field.set(Configuration.class, (char) (int) Integer.valueOf(v.toString())); 197 | } 198 | else if (c.equals(String.class)) { 199 | field.set(Configuration.class, v.toString()); 200 | } 201 | else { 202 | Log.Add("Configuration", Log.TYPE_ERR, "Unknown data type: " + c.getSimpleName()); 203 | } 204 | } 205 | } 206 | catch (Exception e) { 207 | Log.Add(this, Log.TYPE_ERR, e.getMessage()); 208 | } 209 | } 210 | } 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/battlesnake/bootserver/DHCP.java: -------------------------------------------------------------------------------- 1 | package battlesnake.bootserver; 2 | 3 | import java.io.File; 4 | import java.net.InetAddress; 5 | import java.net.InetSocketAddress; 6 | import java.net.NetworkInterface; 7 | import java.nio.ByteBuffer; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import battlesnake.logging.Log; 12 | import battlesnake.packets.DHCPPacket; 13 | import battlesnake.packets.IP; 14 | import battlesnake.simpleservers.UDPServer; 15 | import battlesnake.utils.Path; 16 | 17 | /* DHCP server */ 18 | public class DHCP extends UDPServer { 19 | 20 | /* Map of client name <==> address */ 21 | private Map clients = new HashMap(); 22 | 23 | /* 24 | * Allocate an address from the pool for client 25 | * identified by id, or 26 | * return its current address if one is assigned 27 | */ 28 | private int AllocateIP(String id) { 29 | int tryip; 30 | if (clients.containsKey(id)) 31 | return clients.get(id); 32 | else { 33 | for (int offset = Configuration.dhcp_pool_offset; offset < Configuration.dhcp_pool_offset + Configuration.dhcp_pool_length; offset++) { 34 | tryip = Configuration.dhcp_pool_address + offset; 35 | try { 36 | if (CheckIPAvail(tryip, "")) 37 | return tryip; 38 | } 39 | catch (Exception e) { 40 | /* 41 | * Dirty coding, ignores errors caused 42 | * by invalid addresses or unavailable 43 | * addresses 44 | */ 45 | } 46 | } 47 | return 0; 48 | } 49 | } 50 | 51 | /* Append the DHCP options in the reply packet with the boot parameters */ 52 | private void AppendBootOptions(DHCPPacket pkt, DHCPPacket rep) { 53 | /* 54 | * If a boot image does not exist, don't bother setting boot 55 | * parameters 56 | */ 57 | if (Configuration.boot_file != "") { 58 | /* Set boot image filename */ 59 | rep.AppendOptionNTS(DHCPPacket.OPTION_BOOTFILE_NAME, Configuration.boot_file); 60 | pkt.bootp_file = Configuration.boot_file + "\0"; 61 | /* 62 | * Set boot image size from configuration or otherwise 63 | * calculate it on the fly 64 | */ 65 | File bootfile = new File(Path.Join(new String[] { Configuration.tftp_root_folder, Configuration.boot_file })); 66 | if (Configuration.boot_file_size != 0) 67 | rep.AppendOption4B(DHCPPacket.OPTION_BOOTFILESIZE, Configuration.boot_file_size); 68 | else if (bootfile.exists()) 69 | rep.AppendOption4B(DHCPPacket.OPTION_BOOTFILESIZE, (int) bootfile.length()); 70 | /* Set address of boot server */ 71 | int bootaddr = 0; 72 | if (Configuration.tftp_enabled && Configuration.network_address != 0 73 | && (Configuration.dhcp_opt_boot_server_address == 0 || Configuration.dhcp_opt_boot_server_address == Configuration.network_address)) 74 | bootaddr = Configuration.network_address; 75 | else if (Configuration.dhcp_opt_boot_server_address != 0) 76 | bootaddr = Configuration.dhcp_opt_boot_server_address; 77 | /* Set address of TFTP server */ 78 | if (bootaddr != 0) { 79 | rep.AppendOption4B(DHCPPacket.OPTION_TFTP_SERVER, bootaddr); 80 | pkt.bootp_host = IP.IntToStr(bootaddr) + "\0"; 81 | } 82 | } 83 | } 84 | 85 | /* Append a client's identity to the reply packet */ 86 | private void AppendClientIdent(DHCPPacket pkt, DHCPPacket rep) { 87 | DHCPPacket.Option clident = pkt.Options(DHCPPacket.OPTION_CLIENT_IDENT); 88 | /* If the identity is available from the source packet, use it */ 89 | if (clident != null && clident.len > 0) 90 | rep.AppendOption(clident); 91 | else { 92 | byte[] clid = new byte[pkt.hlen]; 93 | for (int i = 0; i < pkt.hlen; i++) 94 | clid[i] = pkt.chaddr[i]; 95 | rep.AppendOption(DHCPPacket.OPTION_CLIENT_IDENT, clid); 96 | } 97 | } 98 | 99 | /* Append the lease options to the reply packet */ 100 | private void AppendLeaseOptions(DHCPPacket pkt, DHCPPacket rep) { 101 | rep.AppendOption4B(DHCPPacket.OPTION_SERVER_IDENT, Configuration.network_address); 102 | rep.AppendOption4B(DHCPPacket.OPTION_REBIND_TIME, 86400 * 10); 103 | rep.AppendOption4B(DHCPPacket.OPTION_RENEWAL_TIME, 86400 * 10); 104 | rep.AppendOption4B(DHCPPacket.OPTION_LEASE_TIME, 86400 * 10); 105 | } 106 | 107 | /* (Re-)assign an IP address to a client */ 108 | private void AssignIP(int ip, String id) { 109 | if (clients.containsKey(id)) 110 | if (clients.get(id) == ip) 111 | ; 112 | else 113 | Log.Add(this, Log.TYPE_INFO, "DHCP: Re-assigning " + IP.IntToStr(ip) + " to " + id); 114 | else 115 | Log.Add(this, Log.TYPE_INFO, "DHCP: Assigning " + IP.IntToStr(ip) + " to " + id); 116 | clients.put(id, ip); 117 | } 118 | 119 | /* The address to use for broadcasts */ 120 | private int BroadcastAddr() { 121 | return 0xffffffff; 122 | } 123 | 124 | /* Check if an IP address is available */ 125 | private boolean CheckIPAvail(int tryip, String clientHwAddress) { 126 | try { 127 | for (Map.Entry e : clients.entrySet()) 128 | if (e.getValue() == tryip) 129 | return e.getKey().equals(clientHwAddress); 130 | return tryip != Configuration.network_address 131 | && !InetAddress.getByAddress(IP.IntToBytes(tryip)).isReachable(NetworkInterface.getByName(Configuration.network_interface), 6, 400); 132 | } 133 | catch (Exception e) { 134 | return true; 135 | } 136 | } 137 | 138 | /* Validate an IP address (is it on our subnet?) */ 139 | private boolean CheckIPValid(int tryip) { 140 | if (Configuration.dhcp_enforce_subnet) 141 | return (tryip & Configuration.dhcp_subnet_mask) == (Configuration.dhcp_pool_address & Configuration.dhcp_subnet_mask); 142 | else 143 | return true; 144 | } 145 | 146 | /* Replace loopback address with server address */ 147 | private int ForwardLoopback(int ip) { 148 | if (ip == 0x7f000001) 149 | return Configuration.network_address; 150 | else 151 | return ip; 152 | } 153 | 154 | /* Return the address of the server */ 155 | @Override 156 | protected int getInterfaceAddress() { 157 | return Configuration.network_address; 158 | } 159 | 160 | /* Return the port that the server is listening on */ 161 | @Override 162 | protected int getPort() { 163 | return Configuration.dhcp_server_port; 164 | } 165 | 166 | /* Handles DHCP DISCOVER */ 167 | private boolean OnDiscover(DHCPPacket pkt, String clientHwAddress, String clientHumanReadableId, String clientHostname) throws Exception { 168 | if (Configuration.dhcp_log_packets) 169 | Log.Add(this, Log.TYPE_INFO, "DHCP Discover from " + clientHumanReadableId); 170 | /* Validate requested IP address */ 171 | Integer tryip = 0; 172 | tryip = pkt.Options(DHCPPacket.OPTION_REQUESTED_IP, null).data4B(); 173 | if (tryip != 0 && !CheckIPValid(tryip)) { 174 | Log.Add(this, Log.TYPE_WARN, "Reqested IP " + IP.IntToStr(tryip) + " is not on enforced subnet " + IP.IntToStr(Configuration.network_address & Configuration.dhcp_subnet_mask) 175 | + "/" + IP.IntToStr(Configuration.dhcp_subnet_mask) + " for " + clientHumanReadableId); 176 | if (Configuration.dhcp_authoritive) { 177 | DHCPPacket Nak = pkt.MakeResponse(DHCPPacket.MSGTYPE_DHCPNAK); 178 | Send(Nak); 179 | } 180 | return true; 181 | } 182 | if (tryip != 0 && !CheckIPAvail(tryip, clientHwAddress)) { 183 | Log.Add(this, Log.TYPE_WARN, "Reqested IP " + IP.IntToStr(tryip) + " is already in use so cannot be assigned to " + clientHumanReadableId); 184 | tryip = 0; 185 | } 186 | /* Allocate an IP address if needed */ 187 | if (tryip == 0) 188 | tryip = AllocateIP(clientHwAddress); 189 | if (tryip == 0) { 190 | Log.Add(this, Log.TYPE_ERR, "Failed to allocate IP for " + clientHumanReadableId); 191 | return false; 192 | } 193 | /* Store the IP in the assignment table */ 194 | AssignIP(tryip, clientHwAddress); 195 | /* Create offer (reply packet) */ 196 | DHCPPacket rep = pkt.MakeResponse(DHCPPacket.DHCP_REPLY); 197 | rep.yiaddr = tryip; 198 | rep.AppendOption1B(DHCPPacket.OPTION_DHCP_MESSAGE_TYPE, DHCPPacket.MSGTYPE_DHCPOFFER); 199 | ParseRequestList(pkt, rep); 200 | AppendBootOptions(pkt, rep); 201 | AppendLeaseOptions(pkt, rep); 202 | AppendClientIdent(pkt, rep); 203 | if (Configuration.dhcp_log_packets) 204 | Log.Add(this, Log.TYPE_INFO, "Sending DHCP offer to " + clientHumanReadableId); 205 | rep.flags = (short) 0x8000; 206 | /* Send reply */ 207 | Send(rep); 208 | return false; 209 | } 210 | 211 | /* Handles DHCP REQUEST */ 212 | private boolean OnRequest(DHCPPacket pkt, String clientHwAddress, String clientHumanReadableId, String clientHostname, int clientIpAddress) throws Exception { 213 | /* Get target address of (broadcasted) packet */ 214 | int target = pkt.Options(DHCPPacket.OPTION_SERVER_IDENT, null).data4B(); 215 | if (target == 0) 216 | target = pkt.siaddr; 217 | /* Ensure that the packet is not intended for another server */ 218 | if (target != Configuration.network_address && target != 0) { 219 | Log.Add(this, Log.TYPE_INFO, "DHCP Request from " + clientHumanReadableId + " to " + IP.IntToStr(target) + " (ignored)"); 220 | return false; 221 | } 222 | if (Configuration.dhcp_log_packets) 223 | Log.Add(this, Log.TYPE_INFO, "DHCP Request from " + clientHumanReadableId); 224 | /* Check requested IP address */ 225 | Integer tryip = clientIpAddress != 0 ? clientIpAddress : pkt.Options(DHCPPacket.OPTION_REQUESTED_IP, null).data4B(); 226 | /* Validate address */ 227 | if (tryip != 0) 228 | /* Is requested address not valid? */ 229 | if (!CheckIPValid(tryip)) { 230 | Log.Add(this, Log.TYPE_WARN, 231 | "Requested IP " + IP.IntToStr(tryip) + " is not on enforced subnet " + IP.IntToStr(Configuration.network_address & Configuration.dhcp_subnet_mask) 232 | + "/" + IP.IntToStr(Configuration.dhcp_subnet_mask) + " for " + clientHumanReadableId); 233 | /* 234 | * Only NACK if this server is authoritive, 235 | * otherwise let another server handle the 236 | * request 237 | */ 238 | if (Configuration.dhcp_authoritive) { 239 | DHCPPacket Nak = pkt.MakeResponse(DHCPPacket.MSGTYPE_DHCPNAK); 240 | Send(Nak); 241 | } 242 | return true; 243 | } 244 | /* Is requested address already taken? */ 245 | else if (!CheckIPAvail(tryip, clientHwAddress)) { 246 | Log.Add(this, Log.TYPE_WARN, "Requested IP " + IP.IntToStr(tryip) + " is already taken, requested by " + clientHumanReadableId); 247 | tryip = 0; 248 | } 249 | /* No valid IP address */ 250 | if (tryip == 0) { 251 | /* Create NACK */ 252 | DHCPPacket rep = pkt.MakeResponse(DHCPPacket.DHCP_REPLY); 253 | rep.AppendOption1B(DHCPPacket.OPTION_DHCP_MESSAGE_TYPE, DHCPPacket.MSGTYPE_DHCPNAK); 254 | AppendClientIdent(pkt, rep); 255 | if (Configuration.dhcp_log_packets) 256 | Log.Add(this, Log.TYPE_INFO, "Sending DHCP negative-acknowledge for to " + clientHumanReadableId); 257 | rep.flags = (short) 0x8000; 258 | /* Send NACK */ 259 | Send(rep); 260 | return false; 261 | } 262 | /* Success, reply to the request */ 263 | else { 264 | /* Store the address assignment */ 265 | AssignIP(tryip, clientHwAddress); 266 | /* Create acknowledgement */ 267 | DHCPPacket rep = pkt.MakeResponse(DHCPPacket.DHCP_REPLY); 268 | rep.yiaddr = tryip; 269 | rep.AppendOption1B(DHCPPacket.OPTION_DHCP_MESSAGE_TYPE, DHCPPacket.MSGTYPE_DHCPACK); 270 | ParseRequestList(pkt, rep); 271 | AppendBootOptions(pkt, rep); 272 | AppendLeaseOptions(pkt, rep); 273 | AppendClientIdent(pkt, rep); 274 | if (Configuration.dhcp_log_packets) 275 | Log.Add(this, Log.TYPE_INFO, "Sending DHCP acknowledge for IP " + IP.IntToStr(tryip) + " to " + clientHumanReadableId); 276 | rep.flags = (short) 0x8000; 277 | /* Send acknowledgement */ 278 | Send(rep); 279 | return false; 280 | } 281 | } 282 | 283 | /* Handles received DHCP packets */ 284 | @Override 285 | protected boolean OnReceive(InetSocketAddress sender, int remotePort, ByteBuffer data) throws Exception { 286 | /* Parse packet, ignore anything that isn't a REQUEST */ 287 | DHCPPacket pkt = new DHCPPacket(data); 288 | if (pkt.op != DHCPPacket.DHCP_REQUEST) 289 | return true; 290 | /* Get client hardware address */ 291 | String clientHwAddress = ""; 292 | for (int i = 0; i < pkt.hlen; i++) 293 | clientHwAddress += Integer.toHexString(0x100 | pkt.chaddr[i] & 0xff).substring(1); 294 | /* Client identification string for logging purposes */ 295 | String clientHumanReadableId = "client "; 306 | /* 307 | * Packet is not broadcasted: ignore it if the configuration 308 | * specifies to 309 | */ 310 | if ((pkt.flags & 0x8000) == 0 && Configuration.dhcp_broadcasts_only) { 311 | if (Configuration.dhcp_log_packets) 312 | Log.Add(this, Log.TYPE_INFO, "Ignoring packet due to unset broadcast bit, from " + clientHumanReadableId); 313 | return true; 314 | } 315 | /* 316 | * The seemingly redundant control logic (i.e. can only return 317 | * true) is in place for in case we want to add fail conditions 318 | * in future (kill the server by returning false) 319 | */ 320 | /* Packet type */ 321 | switch (pkt.MessageType()) { 322 | case DHCPPacket.MSGTYPE_DHCPDISCOVER: 323 | if (OnDiscover(pkt, clientHwAddress, clientHumanReadableId, clientHostname)) 324 | return true; 325 | break; 326 | case DHCPPacket.MSGTYPE_DHCPREQUEST: 327 | if (OnRequest(pkt, clientHwAddress, clientHumanReadableId, clientHostname, clientIpAddress)) 328 | return true; 329 | break; 330 | default: 331 | return true; 332 | } 333 | return true; 334 | } 335 | 336 | /* 337 | * Parses the request list of a DHCP packet, adding the requested 338 | * information to the reply packet 339 | */ 340 | private void ParseRequestList(DHCPPacket pkt, DHCPPacket rep) { 341 | /* Get the parameter request list */ 342 | byte[] prl = pkt.Options(DHCPPacket.OPTION_PARAM_REQUEST_LIST, null).data; 343 | /* Iterate over requests */ 344 | for (byte element : prl) 345 | /* Append response to reply packet */ 346 | switch (element) { 347 | case DHCPPacket.OPTION_SUBNET: 348 | rep.AppendOption4B(DHCPPacket.OPTION_SUBNET, Configuration.dhcp_subnet_mask); 349 | break; 350 | case DHCPPacket.OPTION_ROUTER_ADDR: 351 | if (Configuration.dhcp_opt_gateway_address != 0) 352 | rep.AppendOption4B(DHCPPacket.OPTION_ROUTER_ADDR, ForwardLoopback(Configuration.dhcp_opt_gateway_address)); 353 | break; 354 | case DHCPPacket.OPTION_DNS_SERVER: 355 | if (Configuration.dhcp_opt_dns_server_address != 0) 356 | rep.AppendOption4B(DHCPPacket.OPTION_ROUTER_ADDR, ForwardLoopback(Configuration.dhcp_opt_dns_server_address)); 357 | break; 358 | case DHCPPacket.OPTION_DNS_DOMAIN: 359 | if (Configuration.dhcp_opt_dns_domain != "") 360 | rep.AppendOptionNTS(DHCPPacket.OPTION_DNS_DOMAIN, Configuration.dhcp_opt_dns_domain); 361 | break; 362 | case DHCPPacket.OPTION_TIME_SERVER: 363 | if (Configuration.dhcp_opt_time_server_address != 0) 364 | rep.AppendOption4B(DHCPPacket.OPTION_TIME_SERVER, ForwardLoopback(Configuration.dhcp_opt_time_server_address)); 365 | break; 366 | } 367 | } 368 | 369 | /* Broadcasts a DHCP packet */ 370 | private void Send(DHCPPacket rep) throws Exception { 371 | if (Configuration.dhcp_reply_delay > 0) 372 | Thread.sleep(Configuration.dhcp_reply_delay); 373 | super.Send(Configuration.dhcp_client_port, InetAddress.getByAddress(IP.IntToBytes(BroadcastAddr())), rep.Encode()); 374 | } 375 | 376 | /* Dumps the IP table to STDOUT */ 377 | public void ShowMaps() { 378 | System.out.print("DHCP assignment table"); 379 | for (Map.Entry e : clients.entrySet()) 380 | System.out.print(e.getKey() + " => " + IP.IntToStr(e.getValue()) + "\n"); 381 | } 382 | } -------------------------------------------------------------------------------- /src/battlesnake/bootserver/TFTP.java: -------------------------------------------------------------------------------- 1 | package battlesnake.bootserver; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.net.InetSocketAddress; 8 | import java.nio.ByteBuffer; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | import battlesnake.logging.Log; 13 | import battlesnake.packets.IP; 14 | import battlesnake.packets.TFTPPacket; 15 | import battlesnake.packets.TFTPSession; 16 | import battlesnake.simpleservers.UDPServer; 17 | import battlesnake.utils.Path; 18 | 19 | /* TFTP server */ 20 | public class TFTP extends UDPServer { 21 | 22 | /* Log symbolic link traversals (Windows symlink hack only) */ 23 | private static boolean LogSymlinks = false; 24 | 25 | /* Get the port that the server is bound to */ 26 | @Override 27 | protected int getPort() { 28 | return Configuration.tftp_server_port; 29 | } 30 | 31 | /* Get the address that the server is bound to */ 32 | @Override 33 | protected int getInterfaceAddress() { 34 | return Configuration.network_address; 35 | } 36 | 37 | /* Sends a TFTP packet */ 38 | private void Send(InetSocketAddress Target, int Port, TFTPPacket rep) throws Exception { 39 | super.Send(Port, Target.getAddress(), rep.Encode()); 40 | } 41 | 42 | /* Handle a RRQ */ 43 | private TFTPPacket onReadRequest(TFTPPacket pkt, TFTPSession ses) throws Exception { 44 | /* Get target */ 45 | String targetPath = Path.ParseDots(Path.Slashify(pkt.filename), false, false); 46 | /* Window symlink hack */ 47 | if (Configuration.tftp_windows_symlink_hack) { 48 | String followedPath; 49 | while ((followedPath = FollowSymlink(ses.getRootPath(), targetPath)) != null) { 50 | if (LogSymlinks) 51 | Log.Add(this, Log.TYPE_INFO, "Followed symlink from \"" + targetPath + "\" to \"" + followedPath + "\"."); 52 | targetPath = followedPath; 53 | } 54 | } 55 | /* Open the file */ 56 | ses.openFile(ses.getRootPath() + targetPath, false); 57 | /* If packet has options, parse and acknowledge them */ 58 | if (pkt.hasOptions()) 59 | return onOptions(pkt, ses); 60 | /* Otherwise, start reading the file and send data back */ 61 | TFTPPacket rep = TFTPPacket.DATA(1, null); 62 | ses.readFile(rep); 63 | return rep; 64 | } 65 | 66 | /* Handle options */ 67 | private TFTPPacket onOptions(TFTPPacket pkt, TFTPSession ses) throws Exception { 68 | List k = new ArrayList(); 69 | List v = new ArrayList(); 70 | if (pkt.hasOption(TFTPPacket.OPTION_BLOCKSIZE)) { 71 | ses.setBlockSize(Integer.parseInt(pkt.getOption(TFTPPacket.OPTION_BLOCKSIZE))); 72 | k.add(TFTPPacket.OPTION_BLOCKSIZE); 73 | v.add(((Integer) ses.getBlockSize()).toString()); 74 | } 75 | if (pkt.hasOption(TFTPPacket.OPTION_FILESIZE)) { 76 | k.add(TFTPPacket.OPTION_FILESIZE); 77 | v.add(((Long) ses.getFileSize()).toString()); 78 | } 79 | return TFTPPacket.OACK(k.toArray(new String[] {}), v.toArray(new String[] {})); 80 | } 81 | 82 | /* Handle a WRQ */ 83 | private TFTPPacket onWriteRequest(TFTPPacket pkt, TFTPSession ses) throws Exception { 84 | return TFTPPacket.ERROR(TFTPPacket.ERROR_ACCESS_VIOLATION, "Writing is not implemented on this server"); 85 | } 86 | 87 | /* Handle data */ 88 | private TFTPPacket onData(TFTPPacket pkt, TFTPSession ses) throws Exception { 89 | return TFTPPacket.ERROR(TFTPPacket.ERROR_ACCESS_VIOLATION, "Writing is not implemented on this server"); 90 | } 91 | 92 | /* Handle a data acknowledge */ 93 | private TFTPPacket onAcknowledge(TFTPPacket pkt, TFTPSession ses) throws Exception { 94 | TFTPPacket rep = TFTPPacket.DATA(pkt.block_id + 1, null); 95 | ses.readFile(rep); 96 | return rep; 97 | } 98 | 99 | /* Follow a symbolic link (Windows symlink hack) */ 100 | /* 101 | * Note: do not call if the path is already valid, it will fail rather 102 | * than passing through 103 | */ 104 | private String FollowSymlink(String root, String rel) throws Exception { 105 | /* Get relative path */ 106 | rel = Path.ParseDots(Path.Slashify(rel), false, false); 107 | /* Split relative path into individual elements */ 108 | String[] rea = Path.SplitPath(rel); 109 | String pre = "", post = ""; 110 | /* 111 | * Rebuild path (TODO: implement following of double-symlinked 112 | * directories) 113 | */ 114 | for (int i = 0; i < rea.length; i++) { 115 | if (i > 0) 116 | pre += File.separator; 117 | pre += rea[i]; 118 | /* 119 | * If current part of path is a file, store the 120 | * remainder of the path and attempt to parse the file 121 | * as a symlink 122 | */ 123 | if (new File(root + pre).exists() && new File(root + pre).isFile()) { 124 | for (int j = i + 1; j < rea.length; j++) 125 | post += File.separator + rea[j]; 126 | break; 127 | } 128 | } 129 | /* Sanity check */ 130 | if (!new File(root + pre).exists() || !new File(root + pre).isFile()) 131 | return null; 132 | /* Open the file */ 133 | RandomAccessFile f = new RandomAccessFile(root + pre, "r"); 134 | try { 135 | /* File size check */ 136 | if (f.length() >= 200 || f.length() == 0) 137 | return null; 138 | /* 139 | * Read file a byte at a time, verifying chars are in 140 | * range 0x20..0x7F 141 | */ 142 | byte[] t = new byte[200]; 143 | int p = 0; 144 | int i; 145 | while ((i = f.read()) != -1) { 146 | if (i < 32 || i > 127) 147 | return null; 148 | t[p] = (byte) i; 149 | p++; 150 | } 151 | /* Clean the path and test it */ 152 | String s = Path.Slashify(new String(t).substring(0, p)); 153 | String base = new File(pre).getParent(); 154 | if (base == null) 155 | base = ""; 156 | base = Path.ParseDots(Path.IncludeTrailingSlash(base) + s, false, false); 157 | /* If path is a valid file, return it */ 158 | String full = base + post; 159 | if (s.length() > 0 && new File(root + full).isFile()) 160 | return full; 161 | /* Otherwise fail */ 162 | return null; 163 | } 164 | /* Close the file */ 165 | finally { 166 | f.close(); 167 | } 168 | } 169 | 170 | /* Handles received packets */ 171 | @Override 172 | protected boolean OnReceive(InetSocketAddress sender, int remotePort, ByteBuffer data) throws Exception { 173 | /* Get client ip address */ 174 | int addr = IP.BytesToInt(sender.getAddress().getAddress()); 175 | /* Get session */ 176 | TFTPSession ses = TFTPSession.Manager.FindOrStart(addr, remotePort); 177 | /* Parse packet */ 178 | TFTPPacket pkt = new TFTPPacket(data); 179 | /* Log received packet if configuration specifies to */ 180 | if (pkt.op != TFTPPacket.OP_DATA && pkt.op != TFTPPacket.OP_ACK && Configuration.tftp_log_packets) 181 | Log.Add(this, Log.TYPE_INFO, "TFTP " + TFTPPacket.GetOpText(pkt.op) + " received from " + IP.IntToStr(addr)); 182 | /* Generate a reply */ 183 | ses.heartbeat(); 184 | TFTPPacket rep = null; 185 | try { 186 | switch (pkt.op) { 187 | case TFTPPacket.OP_RRQ: 188 | rep = onReadRequest(pkt, ses); 189 | break; 190 | 191 | case TFTPPacket.OP_WRQ: 192 | rep = onWriteRequest(pkt, ses); 193 | break; 194 | case TFTPPacket.OP_OACK: 195 | rep = TFTPPacket.ACK(0); 196 | break; 197 | case TFTPPacket.OP_DATA: 198 | rep = onData(pkt, ses); 199 | break; 200 | case TFTPPacket.OP_ACK: 201 | if (!ses.endOfFile()) 202 | rep = onAcknowledge(pkt, ses); 203 | break; 204 | case TFTPPacket.OP_ERROR: 205 | default: 206 | rep = null; 207 | } 208 | } 209 | /* File not found */ 210 | catch (FileNotFoundException e) { 211 | rep = TFTPPacket.ERROR(TFTPPacket.ERROR_FILE_NOT_FOUND, e.getMessage()); 212 | } 213 | /* I/O error */ 214 | catch (IOException e) { 215 | rep = TFTPPacket.ERROR(TFTPPacket.ERROR_UNDEFINED, e.getMessage()); 216 | } 217 | /* Unknown error */ 218 | catch (Exception e) { 219 | rep = TFTPPacket.ERROR(TFTPPacket.ERROR_UNDEFINED, e.getMessage()); 220 | } 221 | /* Log and send reply */ 222 | if (rep != null) { 223 | /* Log reply packet */ 224 | if (rep.op == TFTPPacket.OP_RRQ || rep.op == TFTPPacket.OP_WRQ || rep.op == TFTPPacket.OP_OACK && Configuration.tftp_log_packets) 225 | Log.Add(this, Log.TYPE_INFO, "TFTP " + TFTPPacket.GetOpText(rep.op) + " sent to " + IP.IntToStr(addr)); 226 | if (rep.op == TFTPPacket.OP_ERROR && Configuration.tftp_log_packets) 227 | Log.Add(this, Log.TYPE_INFO, "TFTP " + TFTPPacket.GetOpText(rep.op) + " " + rep.ErrorCode + ":\"" + rep.ErrorMessage + "\" sent to " + IP.IntToStr(addr)); 228 | /* Send packet */ 229 | Send(sender, remotePort, rep); 230 | } 231 | return true; 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/battlesnake/logging/Entry.java: -------------------------------------------------------------------------------- 1 | package battlesnake.logging; 2 | 3 | import java.util.Date; 4 | 5 | /* A log entry */ 6 | public class Entry { 7 | public final String AppSource; 8 | public final Date When; 9 | public final byte Type; 10 | public final String Message; 11 | public final int Indent; 12 | 13 | public Entry(String AppSource, byte Type, String Message, int Indent) { 14 | this.AppSource = AppSource; 15 | this.When = new Date(); 16 | this.Type = Type; 17 | this.Message = Message; 18 | this.Indent = Indent; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | String AppSrc = AppSource + ((AppSource.length() < 8) ? '\t' : ""); 24 | String indents = ""; 25 | for (int i = 0; i < Indent; i++) 26 | indents += "\t"; 27 | return String.format("%1$TF %1$TT\t%2$s\t%3$s\t%4$s%5$s", When, AppSrc, 28 | Log.TypeStr(Type), indents, Message); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/battlesnake/logging/Listener.java: -------------------------------------------------------------------------------- 1 | package battlesnake.logging; 2 | 3 | /* A log observer */ 4 | public interface Listener { 5 | public void OnMessage(Entry msg); 6 | } 7 | -------------------------------------------------------------------------------- /src/battlesnake/logging/Log.java: -------------------------------------------------------------------------------- 1 | package battlesnake.logging; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | /* An enhanced logging class */ 8 | public class Log { 9 | 10 | public static final byte TYPE_INFO = 0, TYPE_WARN = 1, TYPE_ERR = 2, TYPE_FATAL = 3; 11 | 12 | /* Log entry type number to string */ 13 | public static String TypeStr(byte type) { 14 | if (type == TYPE_INFO) 15 | return "info"; 16 | else if (type == TYPE_WARN) 17 | return "warn"; 18 | else if (type == TYPE_ERR) 19 | return "error"; 20 | else if (type == TYPE_FATAL) 21 | return "fatal"; 22 | else 23 | return ""; 24 | } 25 | 26 | /* Maximum number of log entries to keep */ 27 | public static int MaxLength = 30; 28 | 29 | /* The log entries */ 30 | private static final List list = new ArrayList(); 31 | /* Maintains a list of observers listening for log entries */ 32 | private static final List onmsg = new LinkedList(); 33 | /* Log indentation level */ 34 | private static int indent = 0; 35 | 36 | /* Increase indentation level */ 37 | public static synchronized void Indent() { 38 | indent++; 39 | } 40 | 41 | /* Decrease indentation level */ 42 | public static synchronized void Unindent() { 43 | if (indent > 0) 44 | indent--; 45 | else 46 | Log.Add("Log", TYPE_WARN, "Too many unindent requests"); 47 | } 48 | 49 | /* Add an entry to the log */ 50 | public static synchronized void Add(String AppSource, byte Type, String Message) { 51 | Entry entry = new Entry(AppSource, Type, Message, indent); 52 | list.add(entry); 53 | while (Length() > MaxLength) 54 | list.remove(0); 55 | for (Listener listener : onmsg) 56 | listener.OnMessage(entry); 57 | } 58 | 59 | /* Add an entry to the log */ 60 | public static synchronized void Add(Object Source, byte Type, String Message) { 61 | Add(Source.getClass().getSimpleName(), Type, Message); 62 | } 63 | 64 | /* Register an observer */ 65 | public static synchronized void Subscribe(Listener listener) { 66 | onmsg.add(listener); 67 | } 68 | 69 | /* Unregister an observer */ 70 | public static synchronized void Unsubscribe(Listener listener) { 71 | onmsg.remove(listener); 72 | } 73 | 74 | /* Length of the log */ 75 | public static synchronized int Length() { 76 | return list.size(); 77 | 78 | } 79 | 80 | /* Get a log entry */ 81 | public static synchronized Entry get(int index) { 82 | return list.get(index); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/battlesnake/packets/DHCPPacket.java: -------------------------------------------------------------------------------- 1 | package battlesnake.packets; 2 | 3 | // See http://support.microsoft.com/kb/169289 and the RFCs 4 | 5 | // Note: DHCP op-codes are degenerate, use the to determine the nature of the packet. 6 | 7 | import java.nio.ByteBuffer; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import battlesnake.packets.dhcpdefs.OptionDefinitions; 12 | 13 | /* Parses and generates DHCP packets */ 14 | public class DHCPPacket extends Packet { 15 | 16 | /* Packet fields */ 17 | public byte op, htype, hlen, hops; 18 | public int xid; 19 | public short secs, flags; 20 | public int ciaddr, yiaddr, siaddr, giaddr; 21 | public byte[] chaddr = new byte[16]; 22 | public String bootp_host = ""; 23 | public String bootp_file = ""; 24 | public int cookie = MAGIC_COOKIE; 25 | public List