aboutsummaryrefslogtreecommitdiff
path: root/networking
diff options
context:
space:
mode:
authorBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
committerBjørn Mork <bjorn@mork.no>2015-05-15 10:20:47 +0200
commit73b16af8feec390afbabd9356d6e5e83c0390838 (patch)
tree3730020ba2f9caeb9d7815a975af51830b51ce11 /networking
busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2busybox-1.13.3
Signed-off-by: Bjørn Mork <bjorn@mork.no>
Diffstat (limited to 'networking')
-rw-r--r--networking/Config.in921
-rw-r--r--networking/Kbuild44
-rw-r--r--networking/arp.c497
-rw-r--r--networking/arping.c401
-rw-r--r--networking/brctl.c272
-rw-r--r--networking/dnsd.c380
-rw-r--r--networking/ether-wake.c276
-rw-r--r--networking/ftpgetput.c325
-rw-r--r--networking/hostname.c93
-rw-r--r--networking/httpd.c2432
-rw-r--r--networking/httpd_indexcgi.c342
-rw-r--r--networking/httpd_post_upload.txt76
-rw-r--r--networking/ifconfig.c540
-rw-r--r--networking/ifenslave.c597
-rw-r--r--networking/ifupdown.c1313
-rw-r--r--networking/inetd.c1592
-rw-r--r--networking/interface.c1282
-rw-r--r--networking/ip.c123
-rw-r--r--networking/ipcalc.c190
-rw-r--r--networking/isrv.c338
-rw-r--r--networking/isrv.h41
-rw-r--r--networking/isrv_identd.c147
-rw-r--r--networking/libiproute/Kbuild64
-rw-r--r--networking/libiproute/ip_common.h41
-rw-r--r--networking/libiproute/ip_parse_common_args.c84
-rw-r--r--networking/libiproute/ipaddress.c784
-rw-r--r--networking/libiproute/iplink.c306
-rw-r--r--networking/libiproute/iproute.c907
-rw-r--r--networking/libiproute/iprule.c334
-rw-r--r--networking/libiproute/iptunnel.c580
-rw-r--r--networking/libiproute/libnetlink.c409
-rw-r--r--networking/libiproute/libnetlink.h55
-rw-r--r--networking/libiproute/ll_addr.c79
-rw-r--r--networking/libiproute/ll_map.c200
-rw-r--r--networking/libiproute/ll_map.h21
-rw-r--r--networking/libiproute/ll_proto.c127
-rw-r--r--networking/libiproute/ll_types.c205
-rw-r--r--networking/libiproute/rt_names.c349
-rw-r--r--networking/libiproute/rt_names.h35
-rw-r--r--networking/libiproute/rtm_map.c118
-rw-r--r--networking/libiproute/rtm_map.h18
-rw-r--r--networking/libiproute/utils.c324
-rw-r--r--networking/libiproute/utils.h96
-rw-r--r--networking/nameif.c232
-rw-r--r--networking/nc.c201
-rw-r--r--networking/nc_bloaty.c832
-rw-r--r--networking/netstat.c712
-rw-r--r--networking/nslookup.c155
-rw-r--r--networking/ping.c812
-rw-r--r--networking/pscan.c154
-rw-r--r--networking/route.c699
-rw-r--r--networking/slattach.c245
-rw-r--r--networking/tc.c545
-rw-r--r--networking/tcpudp.c608
-rw-r--r--networking/tcpudp_perhost.c65
-rw-r--r--networking/tcpudp_perhost.h37
-rw-r--r--networking/telnet.c649
-rw-r--r--networking/telnetd.c610
-rw-r--r--networking/tftp.c752
-rw-r--r--networking/traceroute.c1349
-rw-r--r--networking/udhcp/Config.in122
-rw-r--r--networking/udhcp/Kbuild25
-rw-r--r--networking/udhcp/arpping.c116
-rw-r--r--networking/udhcp/clientpacket.c271
-rw-r--r--networking/udhcp/clientsocket.c108
-rw-r--r--networking/udhcp/common.c11
-rw-r--r--networking/udhcp/common.h110
-rw-r--r--networking/udhcp/dhcpc.c646
-rw-r--r--networking/udhcp/dhcpc.h56
-rw-r--r--networking/udhcp/dhcpd.c272
-rw-r--r--networking/udhcp/dhcpd.h125
-rw-r--r--networking/udhcp/dhcprelay.c314
-rw-r--r--networking/udhcp/domain_codec.c205
-rw-r--r--networking/udhcp/dumpleases.c66
-rw-r--r--networking/udhcp/files.c413
-rw-r--r--networking/udhcp/leases.c149
-rw-r--r--networking/udhcp/options.c234
-rw-r--r--networking/udhcp/options.h121
-rw-r--r--networking/udhcp/packet.c238
-rw-r--r--networking/udhcp/script.c239
-rw-r--r--networking/udhcp/serverpacket.c266
-rw-r--r--networking/udhcp/signalpipe.c82
-rw-r--r--networking/udhcp/socket.c111
-rw-r--r--networking/udhcp/static_leases.c99
-rw-r--r--networking/vconfig.c161
-rw-r--r--networking/wget.c836
-rw-r--r--networking/zcip.c566
87 files changed, 31977 insertions, 0 deletions
diff --git a/networking/Config.in b/networking/Config.in
new file mode 100644
index 0000000..95f8942
--- /dev/null
+++ b/networking/Config.in
@@ -0,0 +1,921 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+menu "Networking Utilities"
+
+config FEATURE_IPV6
+ bool "Enable IPv6 support"
+ default n
+ help
+ Enable IPv6 support in busybox.
+ This adds IPv6 support in the networking applets.
+
+config FEATURE_PREFER_IPV4_ADDRESS
+ bool "Preferentially use IPv4 addresses from DNS queries"
+ default y
+ depends on FEATURE_IPV6
+ help
+ Use IPv4 address of network host if it has one.
+
+ If this option is off, the first returned address will be used.
+ This may cause problems when your DNS server is IPv6-capable and
+ is returning IPv6 host addresses too. If IPv6 address
+ precedes IPv4 one in DNS reply, busybox network applets
+ (e.g. wget) will use IPv6 address. On an IPv6-incapable host
+ or network applets will fail to connect to the host
+ using IPv6 address.
+
+config VERBOSE_RESOLUTION_ERRORS
+ bool "Verbose resolution errors"
+ default n
+ help
+ Enable if you are not satisfied with simplistic
+ "can't resolve 'hostname.com'" and want to know more.
+ This may increase size of your executable a bit.
+
+config ARP
+ bool "arp"
+ default n
+ help
+ Manipulate the system ARP cache.
+
+config ARPING
+ bool "arping"
+ default n
+ help
+ Ping hosts by ARP packets.
+
+config BRCTL
+ bool "brctl"
+ default n
+ help
+ Manage ethernet bridges.
+ Supports addbr/delbr and addif/delif.
+
+config FEATURE_BRCTL_FANCY
+ bool "Fancy options"
+ default n
+ depends on BRCTL
+ help
+ Add support for extended option like:
+ setageing, setfd, sethello, setmaxage,
+ setpathcost, setportprio, setbridgeprio,
+ stp
+ This adds about 600 bytes.
+
+config FEATURE_BRCTL_SHOW
+ bool "Support show, showmac and showstp"
+ default n
+ depends on BRCTL && FEATURE_BRCTL_FANCY
+ help
+ Add support for option which prints the current config:
+ showmacs, showstp, show
+
+config DNSD
+ bool "dnsd"
+ default n
+ help
+ Small and static DNS server daemon.
+
+config ETHER_WAKE
+ bool "ether-wake"
+ default n
+ help
+ Send a magic packet to wake up sleeping machines.
+
+config FAKEIDENTD
+ bool "fakeidentd"
+ default n
+ select FEATURE_SYSLOG
+ help
+ fakeidentd listens on the ident port and returns a predefined
+ fake value on any query.
+
+config FTPGET
+ bool "ftpget"
+ default n
+ help
+ Retrieve a remote file via FTP.
+
+config FTPPUT
+ bool "ftpput"
+ default n
+ help
+ Store a remote file via FTP.
+
+config FEATURE_FTPGETPUT_LONG_OPTIONS
+ bool "Enable long options in ftpget/ftpput"
+ default n
+ depends on GETOPT_LONG && (FTPGET || FTPPUT)
+ help
+ Support long options for the ftpget/ftpput applet.
+
+config HOSTNAME
+ bool "hostname"
+ default n
+ help
+ Show or set the system's host name.
+
+config HTTPD
+ bool "httpd"
+ default n
+ help
+ Serve web pages via an HTTP server.
+
+config FEATURE_HTTPD_RANGES
+ bool "Support 'Ranges:' header"
+ default n
+ depends on HTTPD
+ help
+ Makes httpd emit "Accept-Ranges: bytes" header and understand
+ "Range: bytes=NNN-[MMM]" header. Allows for resuming interrupted
+ downloads, seeking in multimedia players etc.
+
+config FEATURE_HTTPD_USE_SENDFILE
+ bool "Use sendfile system call"
+ default n
+ depends on HTTPD
+ help
+ When enabled, httpd will use the kernel sendfile() function
+ instead of read/write loop.
+
+config FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+ bool "Support reloading of global config file on HUP signal"
+ default n
+ depends on HTTPD
+ help
+ This option enables processing of SIGHUP to reload cached
+ configuration settings.
+
+config FEATURE_HTTPD_SETUID
+ bool "Enable -u <user> option"
+ default n
+ depends on HTTPD
+ help
+ This option allows the server to run as a specific user
+ rather than defaulting to the user that starts the server.
+ Use of this option requires special privileges to change to a
+ different user.
+
+config FEATURE_HTTPD_BASIC_AUTH
+ bool "Enable Basic http Authentication"
+ default y
+ depends on HTTPD
+ help
+ Utilizes password settings from /etc/httpd.conf for basic
+ authentication on a per url basis.
+
+config FEATURE_HTTPD_AUTH_MD5
+ bool "Support MD5 crypted passwords for http Authentication"
+ default n
+ depends on FEATURE_HTTPD_BASIC_AUTH
+ help
+ Enables basic per URL authentication from /etc/httpd.conf
+ using md5 passwords.
+
+config FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+ bool "Support loading additional MIME types at run-time"
+ default n
+ depends on HTTPD
+ help
+ This option enables support for additional MIME types at
+ run-time to be specified in the configuration file.
+
+config FEATURE_HTTPD_CGI
+ bool "Support Common Gateway Interface (CGI)"
+ default y
+ depends on HTTPD
+ help
+ This option allows scripts and executables to be invoked
+ when specific URLs are requested.
+
+config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ bool "Support for running scripts through an interpreter"
+ default n
+ depends on FEATURE_HTTPD_CGI
+ help
+ This option enables support for running scripts through an
+ interpreter. Turn this on if you want PHP scripts to work
+ properly. You need to supply an additional line in your httpd
+ config file:
+ *.php:/path/to/your/php
+
+config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
+ bool "Set REMOTE_PORT environment variable for CGI"
+ default n
+ depends on FEATURE_HTTPD_CGI
+ help
+ Use of this option can assist scripts in generating
+ references that contain a unique port number.
+
+config FEATURE_HTTPD_ENCODE_URL_STR
+ bool "Enable -e option (useful for CGIs written as shell scripts)"
+ default y
+ depends on HTTPD
+ help
+ This option allows html encoding of arbitrary strings for display
+ by the browser. Output goes to stdout.
+ For example, httpd -e "<Hello World>" produces
+ "&#60Hello&#32World&#62".
+
+config FEATURE_HTTPD_ERROR_PAGES
+ bool "Support for custom error pages"
+ default n
+ depends on HTTPD
+ help
+ This option allows you to define custom error pages in
+ the configuration file instead of the default HTTP status
+ error pages. For instance, if you add the line:
+ E404:/path/e404.html
+ in the config file, the server will respond the specified
+ '/path/e404.html' file instead of the terse '404 NOT FOUND'
+ message.
+
+config FEATURE_HTTPD_PROXY
+ bool "Support for reverse proxy"
+ default n
+ depends on HTTPD
+ help
+ This option allows you to define URLs that will be forwarded
+ to another HTTP server. To setup add the following line to the
+ configuration file
+ P:/url/:http://hostname[:port]/new/path/
+ Then a request to /url/myfile will be forwarded to
+ http://hostname[:port]/new/path/myfile.
+
+config IFCONFIG
+ bool "ifconfig"
+ default n
+ help
+ Ifconfig is used to configure the kernel-resident network interfaces.
+
+config FEATURE_IFCONFIG_STATUS
+ bool "Enable status reporting output (+7k)"
+ default y
+ depends on IFCONFIG
+ help
+ If ifconfig is called with no arguments it will display the status
+ of the currently active interfaces.
+
+config FEATURE_IFCONFIG_SLIP
+ bool "Enable slip-specific options \"keepalive\" and \"outfill\""
+ default n
+ depends on IFCONFIG
+ help
+ Allow "keepalive" and "outfill" support for SLIP. If you're not
+ planning on using serial lines, leave this unchecked.
+
+config FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
+ bool "Enable options \"mem_start\", \"io_addr\", and \"irq\""
+ default n
+ depends on IFCONFIG
+ help
+ Allow the start address for shared memory, start address for I/O,
+ and/or the interrupt line used by the specified device.
+
+config FEATURE_IFCONFIG_HW
+ bool "Enable option \"hw\" (ether only)"
+ default y
+ depends on IFCONFIG
+ help
+ Set the hardware address of this interface, if the device driver
+ supports this operation. Currently, we only support the 'ether'
+ class.
+
+config FEATURE_IFCONFIG_BROADCAST_PLUS
+ bool "Set the broadcast automatically"
+ default n
+ depends on IFCONFIG
+ help
+ Setting this will make ifconfig attempt to find the broadcast
+ automatically if the value '+' is used.
+
+config IFENSLAVE
+ bool "ifenslave"
+ default n
+ help
+ Userspace application to bind several interfaces
+ to a logical interface (use with kernel bonding driver).
+
+config IFUPDOWN
+ bool "ifupdown"
+ default n
+ help
+ Activate or deactivate the specified interfaces. This applet makes
+ use of either "ifconfig" and "route" or the "ip" command to actually
+ configure network interfaces. Therefore, you will probably also want
+ to enable either IFCONFIG and ROUTE, or enable
+ FEATURE_IFUPDOWN_IP and the various IP options. Of
+ course you could use non-busybox versions of these programs, so
+ against my better judgement (since this will surely result in plenty
+ of support questions on the mailing list), I do not force you to
+ enable these additional options. It is up to you to supply either
+ "ifconfig", "route" and "run-parts" or the "ip" command, either
+ via busybox or via standalone utilities.
+
+config IFUPDOWN_IFSTATE_PATH
+ string "Absolute path to ifstate file"
+ default "/var/run/ifstate"
+ depends on IFUPDOWN
+ help
+ ifupdown keeps state information in a file called ifstate.
+ Typically it is located in /var/run/ifstate, however
+ some distributions tend to put it in other places
+ (debian, for example, uses /etc/network/run/ifstate).
+ This config option defines location of ifstate.
+
+config FEATURE_IFUPDOWN_IP
+ bool "Use ip applet"
+ default n
+ depends on IFUPDOWN
+ help
+ Use the iproute "ip" command to implement "ifup" and "ifdown", rather
+ than the default of using the older 'ifconfig' and 'route' utilities.
+
+config FEATURE_IFUPDOWN_IP_BUILTIN
+ bool "Use busybox ip applet"
+ default y
+ depends on FEATURE_IFUPDOWN_IP
+ select IP
+ select FEATURE_IP_ADDRESS
+ select FEATURE_IP_LINK
+ select FEATURE_IP_ROUTE
+ help
+ Use the busybox iproute "ip" applet to implement "ifupdown".
+
+ If left disabled, you must install the full-blown iproute2
+ utility or the "ifup" and "ifdown" applets will not work.
+
+config FEATURE_IFUPDOWN_IFCONFIG_BUILTIN
+ bool "Use busybox ifconfig and route applets"
+ default y
+ depends on IFUPDOWN && !FEATURE_IFUPDOWN_IP
+ select IFCONFIG
+ select ROUTE
+ help
+ Use the busybox iproute "ifconfig" and "route" applets to
+ implement the "ifup" and "ifdown" utilities.
+
+ If left disabled, you must install the full-blown ifconfig
+ and route utilities, or the "ifup" and "ifdown" applets will not
+ work.
+
+config FEATURE_IFUPDOWN_IPV4
+ bool "Support for IPv4"
+ default y
+ depends on IFUPDOWN
+ help
+ If you want ifup/ifdown to talk IPv4, leave this on.
+
+config FEATURE_IFUPDOWN_IPV6
+ bool "Support for IPv6"
+ default n
+ depends on IFUPDOWN && FEATURE_IPV6
+ help
+ If you need support for IPv6, turn this option on.
+
+### UNUSED
+###config FEATURE_IFUPDOWN_IPX
+### bool "Support for IPX"
+### default n
+### depends on IFUPDOWN
+### help
+### If this option is selected you can use busybox to work with IPX
+### networks.
+
+config FEATURE_IFUPDOWN_MAPPING
+ bool "Enable mapping support"
+ default n
+ depends on IFUPDOWN
+ help
+ This enables support for the "mapping" stanza, unless you have
+ a weird network setup you don't need it.
+
+config FEATURE_IFUPDOWN_EXTERNAL_DHCP
+ bool "Support for external dhcp clients"
+ default n
+ depends on IFUPDOWN
+ help
+ This enables support for the external dhcp clients. Clients are
+ tried in the following order: dhcpcd, dhclient, pump and udhcpc.
+ Otherwise, if udhcpc applet is enabled, it is used.
+ Otherwise, ifup/ifdown will have no support for DHCP.
+
+config INETD
+ bool "inetd"
+ default n
+ select FEATURE_SYSLOG
+ help
+ Internet superserver daemon
+
+config FEATURE_INETD_SUPPORT_BUILTIN_ECHO
+ bool "Support echo service"
+ default y
+ depends on INETD
+ help
+ Echo received data internal inetd service
+
+config FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+ bool "Support discard service"
+ default y
+ depends on INETD
+ help
+ Internet /dev/null internal inetd service
+
+config FEATURE_INETD_SUPPORT_BUILTIN_TIME
+ bool "Support time service"
+ default y
+ depends on INETD
+ help
+ Return 32 bit time since 1900 internal inetd service
+
+config FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+ bool "Support daytime service"
+ default y
+ depends on INETD
+ help
+ Return human-readable time internal inetd service
+
+config FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+ bool "Support chargen service"
+ default y
+ depends on INETD
+ help
+ Familiar character generator internal inetd service
+
+config FEATURE_INETD_RPC
+ bool "Support RPC services"
+ default n
+ depends on INETD
+ select FEATURE_HAVE_RPC
+ help
+ Support Sun-RPC based services
+
+config IP
+ bool "ip"
+ default n
+ help
+ The "ip" applet is a TCP/IP interface configuration and routing
+ utility. You generally don't need "ip" to use busybox with
+ TCP/IP.
+
+config FEATURE_IP_ADDRESS
+ bool "ip address"
+ default y
+ depends on IP
+ help
+ Address manipulation support for the "ip" applet.
+
+config FEATURE_IP_LINK
+ bool "ip link"
+ default y
+ depends on IP
+ help
+ Configure network devices with "ip".
+
+config FEATURE_IP_ROUTE
+ bool "ip route"
+ default y
+ depends on IP
+ help
+ Add support for routing table management to "ip".
+
+config FEATURE_IP_TUNNEL
+ bool "ip tunnel"
+ default n
+ depends on IP
+ help
+ Add support for tunneling commands to "ip".
+
+config FEATURE_IP_RULE
+ bool "ip rule"
+ default n
+ depends on IP
+ help
+ Add support for rule commands to "ip".
+
+config FEATURE_IP_SHORT_FORMS
+ bool "Support short forms of ip commands"
+ default n
+ depends on IP
+ help
+ Also support short-form of ip <OBJECT> commands:
+ ip addr -> ipaddr
+ ip link -> iplink
+ ip route -> iproute
+ ip tunnel -> iptunnel
+ ip rule -> iprule
+
+ Say N unless you desparately need the short form of the ip
+ object commands.
+
+config FEATURE_IP_RARE_PROTOCOLS
+ bool "Support displaying rarely used link types"
+ default n
+ depends on IP
+ help
+ If you are not going to use links of type "frad", "econet",
+ "bif" etc, you probably don't need to enable this.
+ Ethernet, wireless, infrared, ppp/slip, ip tunnelling
+ link types are supported without this option selected.
+
+config IPADDR
+ bool
+ default y
+ depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ADDRESS
+
+config IPLINK
+ bool
+ default y
+ depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_LINK
+
+config IPROUTE
+ bool
+ default y
+ depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ROUTE
+
+config IPTUNNEL
+ bool
+ default y
+ depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_TUNNEL
+
+config IPRULE
+ bool
+ default y
+ depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_RULE
+
+config IPCALC
+ bool "ipcalc"
+ default n
+ help
+ ipcalc takes an IP address and netmask and calculates the
+ resulting broadcast, network, and host range.
+
+config FEATURE_IPCALC_FANCY
+ bool "Fancy IPCALC, more options, adds 1 kbyte"
+ default y
+ depends on IPCALC
+ help
+ Adds the options hostname, prefix and silent to the output of
+ "ipcalc".
+
+config FEATURE_IPCALC_LONG_OPTIONS
+ bool "Enable long options"
+ default n
+ depends on IPCALC && GETOPT_LONG
+ help
+ Support long options for the ipcalc applet.
+
+config NAMEIF
+ bool "nameif"
+ default n
+ select FEATURE_SYSLOG
+ help
+ nameif is used to rename network interface by its MAC address.
+ Renamed interfaces MUST be in the down state.
+ It is possible to use a file (default: /etc/mactab)
+ with list of new interface names and MACs.
+ Maximum interface name length: IFNAMSIZ = 16
+ File fields are separated by space or tab.
+ File format:
+ # Comment
+ new_interface_name XX:XX:XX:XX:XX:XX
+
+config FEATURE_NAMEIF_EXTENDED
+ bool "Extended nameif"
+ default n
+ depends on NAMEIF
+ help
+ This extends the nameif syntax to support the bus_info and driver
+ checks. The syntax is compatible to the normal nameif.
+ File format:
+ new_interface_name driver=asix bus=usb-0000:00:08.2-3
+ new_interface_name bus=usb-0000:00:08.2-3 00:80:C8:38:91:B5
+ new_interface_name mac=00:80:C8:38:91:B5
+ new_interface_name 00:80:C8:38:91:B5
+
+config NC
+ bool "nc"
+ default n
+ help
+ A simple Unix utility which reads and writes data across network
+ connections.
+
+config NC_SERVER
+ bool "Netcat server options (-l)"
+ default n
+ depends on NC
+ help
+ Allow netcat to act as a server.
+
+config NC_EXTRA
+ bool "Netcat extensions (-eiw and filename)"
+ default n
+ depends on NC
+ help
+ Add -e (support for executing the rest of the command line after
+ making or receiving a successful connection), -i (delay interval for
+ lines sent), -w (timeout for initial connection).
+
+config NETSTAT
+ bool "netstat"
+ default n
+ help
+ netstat prints information about the Linux networking subsystem.
+
+config FEATURE_NETSTAT_WIDE
+ bool "Enable wide netstat output"
+ default n
+ depends on NETSTAT
+ help
+ Add support for wide columns. Useful when displaying IPv6 addresses
+ (-W option).
+
+config FEATURE_NETSTAT_PRG
+ bool "Enable PID/Program name output"
+ default n
+ depends on NETSTAT
+ help
+ Add support for -p flag to print out PID and program name.
+ +700 bytes of code.
+
+config NSLOOKUP
+ bool "nslookup"
+ default n
+ help
+ nslookup is a tool to query Internet name servers.
+
+config PING
+ bool "ping"
+ default n
+ help
+ ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to
+ elicit an ICMP ECHO_RESPONSE from a host or gateway.
+
+config PING6
+ bool "ping6"
+ default n
+ depends on FEATURE_IPV6 && PING
+ help
+ This will give you a ping that can talk IPv6.
+
+config FEATURE_FANCY_PING
+ bool "Enable fancy ping output"
+ default y
+ depends on PING
+ help
+ Make the output from the ping applet include statistics, and at the
+ same time provide full support for ICMP packets.
+
+config PSCAN
+ bool "pscan"
+ default n
+ help
+ Simple network port scanner.
+
+config ROUTE
+ bool "route"
+ default n
+ help
+ Route displays or manipulates the kernel's IP routing tables.
+
+config SLATTACH
+ bool "slattach"
+ default n
+ help
+ slattach is a small utility to attach network interfaces to serial
+ lines.
+
+#config TC
+# bool "tc"
+# default n
+# help
+# show / manipulate traffic control settings
+#
+#config FEATURE_TC_INGRESS
+# def_bool n
+# depends on TC
+
+config TELNET
+ bool "telnet"
+ default n
+ help
+ Telnet is an interface to the TELNET protocol, but is also commonly
+ used to test other simple protocols.
+
+config FEATURE_TELNET_TTYPE
+ bool "Pass TERM type to remote host"
+ default y
+ depends on TELNET
+ help
+ Setting this option will forward the TERM environment variable to the
+ remote host you are connecting to. This is useful to make sure that
+ things like ANSI colors and other control sequences behave.
+
+config FEATURE_TELNET_AUTOLOGIN
+ bool "Pass USER type to remote host"
+ default y
+ depends on TELNET
+ help
+ Setting this option will forward the USER environment variable to the
+ remote host you are connecting to. This is useful when you need to
+ log into a machine without telling the username (autologin). This
+ option enables `-a' and `-l USER' arguments.
+
+config TELNETD
+ bool "telnetd"
+ default n
+ select FEATURE_SYSLOG
+ help
+ A daemon for the TELNET protocol, allowing you to log onto the host
+ running the daemon. Please keep in mind that the TELNET protocol
+ sends passwords in plain text. If you can't afford the space for an
+ SSH daemon and you trust your network, you may say 'y' here. As a
+ more secure alternative, you should seriously consider installing the
+ very small Dropbear SSH daemon instead:
+ http://matt.ucc.asn.au/dropbear/dropbear.html
+
+ Note that for busybox telnetd to work you need several things:
+ First of all, your kernel needs:
+ UNIX98_PTYS=y
+ DEVPTS_FS=y
+
+ Next, you need a /dev/pts directory on your root filesystem:
+
+ $ ls -ld /dev/pts
+ drwxr-xr-x 2 root root 0 Sep 23 13:21 /dev/pts/
+
+ Next you need the pseudo terminal master multiplexer /dev/ptmx:
+
+ $ ls -la /dev/ptmx
+ crw-rw-rw- 1 root tty 5, 2 Sep 23 13:55 /dev/ptmx
+
+ Any /dev/ttyp[0-9]* files you may have can be removed.
+ Next, you need to mount the devpts filesystem on /dev/pts using:
+
+ mount -t devpts devpts /dev/pts
+
+ You need to be sure that Busybox has LOGIN and
+ FEATURE_SUID enabled. And finally, you should make
+ certain that Busybox has been installed setuid root:
+
+ chown root.root /bin/busybox
+ chmod 4755 /bin/busybox
+
+ with all that done, telnetd _should_ work....
+
+
+config FEATURE_TELNETD_STANDALONE
+ bool "Support standalone telnetd (not inetd only)"
+ default n
+ depends on TELNETD
+ help
+ Selecting this will make telnetd able to run standalone.
+
+config TFTP
+ bool "tftp"
+ default n
+ help
+ This enables the Trivial File Transfer Protocol client program. TFTP
+ is usually used for simple, small transfers such as a root image
+ for a network-enabled bootloader.
+
+config TFTPD
+ bool "tftpd"
+ default n
+ help
+ This enables the Trivial File Transfer Protocol server program.
+ It expects that stdin is a datagram socket and a packet
+ is already pending on it. It will exit after one transfer.
+ In other words: it should be run from inetd in nowait mode,
+ or from udpsvd. Example: "udpsvd -E 0 69 tftpd DIR"
+
+config FEATURE_TFTP_GET
+ bool "Enable \"get\" command"
+ default y
+ depends on TFTP || TFTPD
+ help
+ Add support for the GET command within the TFTP client. This allows
+ a client to retrieve a file from a TFTP server.
+ Also enable upload support in tftpd, if tftpd is selected.
+
+config FEATURE_TFTP_PUT
+ bool "Enable \"put\" command"
+ default y
+ depends on TFTP || TFTPD
+ help
+ Add support for the PUT command within the TFTP client. This allows
+ a client to transfer a file to a TFTP server.
+ Also enable download support in tftpd, if tftpd is selected.
+
+config FEATURE_TFTP_BLOCKSIZE
+ bool "Enable \"blksize\" protocol option"
+ default n
+ depends on TFTP || TFTPD
+ help
+ Allow tftp to specify block size, and tftpd to understand
+ "blksize" option.
+
+config TFTP_DEBUG
+ bool "Enable debug"
+ default n
+ depends on TFTP
+ help
+ Enable debug settings for tftp. This is useful if you're running
+ into problems with tftp as the protocol doesn't help you much when
+ you run into problems.
+
+config TRACEROUTE
+ bool "traceroute"
+ default n
+ help
+ Utility to trace the route of IP packets
+
+config FEATURE_TRACEROUTE_VERBOSE
+ bool "Enable verbose output"
+ default n
+ depends on TRACEROUTE
+ help
+ Add some verbosity to traceroute. This includes amongst other things
+ hostnames and ICMP response types.
+
+config FEATURE_TRACEROUTE_SOURCE_ROUTE
+ bool "Enable loose source route"
+ default n
+ depends on TRACEROUTE
+ help
+ Add option to specify a loose source route gateway
+ (8 maximum).
+
+config FEATURE_TRACEROUTE_USE_ICMP
+ bool "Use ICMP instead of UDP"
+ default n
+ depends on TRACEROUTE
+ help
+ Add feature to allow for ICMP ECHO instead of UDP datagrams.
+
+source networking/udhcp/Config.in
+
+config VCONFIG
+ bool "vconfig"
+ default n
+ help
+ Creates, removes, and configures VLAN interfaces
+
+config WGET
+ bool "wget"
+ default n
+ help
+ wget is a utility for non-interactive download of files from HTTP,
+ HTTPS, and FTP servers.
+
+config FEATURE_WGET_STATUSBAR
+ bool "Enable a nifty process meter (+2k)"
+ default y
+ depends on WGET
+ help
+ Enable the transfer progress bar for wget transfers.
+
+config FEATURE_WGET_AUTHENTICATION
+ bool "Enable HTTP authentication"
+ default y
+ depends on WGET
+ help
+ Support authenticated HTTP transfers.
+
+config FEATURE_WGET_LONG_OPTIONS
+ bool "Enable long options"
+ default n
+ depends on WGET && GETOPT_LONG
+ help
+ Support long options for the wget applet.
+
+config ZCIP
+ bool "zcip"
+ default n
+ select FEATURE_SYSLOG
+ help
+ ZCIP provides ZeroConf IPv4 address selection, according to RFC 3927.
+ It's a daemon that allocates and defends a dynamically assigned
+ address on the 169.254/16 network, requiring no system administrator.
+
+ See http://www.zeroconf.org for further details, and "zcip.script"
+ in the busybox examples.
+
+config TCPSVD
+ bool "tcpsvd"
+ default n
+ help
+ tcpsvd listens on a TCP port and runs a program for each new
+ connection.
+
+config UDPSVD
+ bool "udpsvd"
+ default n
+ help
+ udpsvd listens on an UDP port and runs a program for each new
+ connection.
+
+endmenu
diff --git a/networking/Kbuild b/networking/Kbuild
new file mode 100644
index 0000000..63d0745
--- /dev/null
+++ b/networking/Kbuild
@@ -0,0 +1,44 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2, see the file LICENSE in this tarball.
+
+lib-y:=
+lib-$(CONFIG_ARP) += arp.o interface.o
+lib-$(CONFIG_ARPING) += arping.o
+lib-$(CONFIG_BRCTL) += brctl.o
+lib-$(CONFIG_DNSD) += dnsd.o
+lib-$(CONFIG_ETHER_WAKE) += ether-wake.o
+lib-$(CONFIG_FAKEIDENTD) += isrv_identd.o isrv.o
+lib-$(CONFIG_FTPGET) += ftpgetput.o
+lib-$(CONFIG_FTPPUT) += ftpgetput.o
+lib-$(CONFIG_HOSTNAME) += hostname.o
+lib-$(CONFIG_HTTPD) += httpd.o
+lib-$(CONFIG_IFCONFIG) += ifconfig.o interface.o
+lib-$(CONFIG_IFENSLAVE) += ifenslave.o interface.o
+lib-$(CONFIG_IFUPDOWN) += ifupdown.o
+lib-$(CONFIG_INETD) += inetd.o
+lib-$(CONFIG_IP) += ip.o
+lib-$(CONFIG_IPCALC) += ipcalc.o
+lib-$(CONFIG_NAMEIF) += nameif.o
+lib-$(CONFIG_NC) += nc.o
+lib-$(CONFIG_NETSTAT) += netstat.o
+lib-$(CONFIG_NSLOOKUP) += nslookup.o
+lib-$(CONFIG_PING) += ping.o
+lib-$(CONFIG_PING6) += ping.o
+lib-$(CONFIG_PSCAN) += pscan.o
+lib-$(CONFIG_ROUTE) += route.o
+lib-$(CONFIG_SLATTACH) += slattach.o
+lib-$(CONFIG_TC) += tc.o
+lib-$(CONFIG_TELNET) += telnet.o
+lib-$(CONFIG_TELNETD) += telnetd.o
+lib-$(CONFIG_TFTP) += tftp.o
+lib-$(CONFIG_TFTPD) += tftp.o
+lib-$(CONFIG_TRACEROUTE) += traceroute.o
+lib-$(CONFIG_VCONFIG) += vconfig.o
+lib-$(CONFIG_WGET) += wget.o
+lib-$(CONFIG_ZCIP) += zcip.o
+
+lib-$(CONFIG_TCPSVD) += tcpudp.o tcpudp_perhost.o
+lib-$(CONFIG_UDPSVD) += tcpudp.o tcpudp_perhost.o
diff --git a/networking/arp.c b/networking/arp.c
new file mode 100644
index 0000000..e2c5bbb
--- /dev/null
+++ b/networking/arp.c
@@ -0,0 +1,497 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * arp.c - Manipulate the system ARP cache
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Author: Fred N. van Kempen, <waltje at uwalt.nl.mugnet.org>
+ * Busybox port: Paul van Gool <pvangool at mimotech.com>
+ *
+ * modified for getopt32 by Arne Bernin <arne [at] alamut.de>
+ */
+
+#include "libbb.h"
+#include "inet_common.h"
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/ether.h>
+#include <netpacket/packet.h>
+
+#define DEBUG 0
+
+#define DFLT_AF "inet"
+#define DFLT_HW "ether"
+
+#define ARP_OPT_A (0x1)
+#define ARP_OPT_p (0x2)
+#define ARP_OPT_H (0x4)
+#define ARP_OPT_t (0x8)
+#define ARP_OPT_i (0x10)
+#define ARP_OPT_a (0x20)
+#define ARP_OPT_d (0x40)
+#define ARP_OPT_n (0x80) /* do not resolve addresses */
+#define ARP_OPT_D (0x100) /* HW-address is devicename */
+#define ARP_OPT_s (0x200)
+#define ARP_OPT_v (0x400 * DEBUG) /* debugging output flag */
+
+
+static const struct aftype *ap; /* current address family */
+static const struct hwtype *hw; /* current hardware type */
+static int sockfd; /* active socket descriptor */
+static smallint hw_set; /* flag if hw-type was set (-H) */
+static const char *device = ""; /* current device */
+
+static const char options[] ALIGN1 =
+ "pub\0"
+ "priv\0"
+ "temp\0"
+ "trail\0"
+ "dontpub\0"
+ "auto\0"
+ "dev\0"
+ "netmask\0";
+
+/* Delete an entry from the ARP cache. */
+/* Called only from main, once */
+static int arp_del(char **args)
+{
+ char *host;
+ struct arpreq req;
+ struct sockaddr sa;
+ int flags = 0;
+ int err;
+
+ memset(&req, 0, sizeof(req));
+
+ /* Resolve the host name. */
+ host = *args;
+ if (ap->input(host, &sa) < 0) {
+ bb_herror_msg_and_die("%s", host);
+ }
+
+ /* If a host has more than one address, use the correct one! */
+ memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr));
+
+ if (hw_set)
+ req.arp_ha.sa_family = hw->type;
+
+ req.arp_flags = ATF_PERM;
+ args++;
+ while (*args != NULL) {
+ switch (index_in_strings(options, *args)) {
+ case 0: /* "pub" */
+ flags |= 1;
+ args++;
+ break;
+ case 1: /* "priv" */
+ flags |= 2;
+ args++;
+ break;
+ case 2: /* "temp" */
+ req.arp_flags &= ~ATF_PERM;
+ args++;
+ break;
+ case 3: /* "trail" */
+ req.arp_flags |= ATF_USETRAILERS;
+ args++;
+ break;
+ case 4: /* "dontpub" */
+#ifdef HAVE_ATF_DONTPUB
+ req.arp_flags |= ATF_DONTPUB;
+#else
+ bb_error_msg("feature ATF_DONTPUB is not supported");
+#endif
+ args++;
+ break;
+ case 5: /* "auto" */
+#ifdef HAVE_ATF_MAGIC
+ req.arp_flags |= ATF_MAGIC;
+#else
+ bb_error_msg("feature ATF_MAGIC is not supported");
+#endif
+ args++;
+ break;
+ case 6: /* "dev" */
+ if (*++args == NULL)
+ bb_show_usage();
+ device = *args;
+ args++;
+ break;
+ case 7: /* "netmask" */
+ if (*++args == NULL)
+ bb_show_usage();
+ if (strcmp(*args, "255.255.255.255") != 0) {
+ host = *args;
+ if (ap->input(host, &sa) < 0) {
+ bb_herror_msg_and_die("%s", host);
+ }
+ memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr));
+ req.arp_flags |= ATF_NETMASK;
+ }
+ args++;
+ break;
+ default:
+ bb_show_usage();
+ break;
+ }
+ }
+ if (flags == 0)
+ flags = 3;
+
+ strncpy(req.arp_dev, device, sizeof(req.arp_dev));
+
+ err = -1;
+
+ /* Call the kernel. */
+ if (flags & 2) {
+ if (option_mask32 & ARP_OPT_v)
+ bb_error_msg("SIOCDARP(nopub)");
+ err = ioctl(sockfd, SIOCDARP, &req);
+ if (err < 0) {
+ if (errno == ENXIO) {
+ if (flags & 1)
+ goto nopub;
+ printf("No ARP entry for %s\n", host);
+ return -1;
+ }
+ bb_perror_msg_and_die("SIOCDARP(priv)");
+ }
+ }
+ if ((flags & 1) && err) {
+ nopub:
+ req.arp_flags |= ATF_PUBL;
+ if (option_mask32 & ARP_OPT_v)
+ bb_error_msg("SIOCDARP(pub)");
+ if (ioctl(sockfd, SIOCDARP, &req) < 0) {
+ if (errno == ENXIO) {
+ printf("No ARP entry for %s\n", host);
+ return -1;
+ }
+ bb_perror_msg_and_die("SIOCDARP(pub)");
+ }
+ }
+ return 0;
+}
+
+/* Get the hardware address to a specified interface name */
+static void arp_getdevhw(char *ifname, struct sockaddr *sa,
+ const struct hwtype *hwt)
+{
+ struct ifreq ifr;
+ const struct hwtype *xhw;
+
+ strcpy(ifr.ifr_name, ifname);
+ ioctl_or_perror_and_die(sockfd, SIOCGIFHWADDR, &ifr,
+ "cant get HW-Address for '%s'", ifname);
+ if (hwt && (ifr.ifr_hwaddr.sa_family != hw->type)) {
+ bb_error_msg_and_die("protocol type mismatch");
+ }
+ memcpy(sa, &(ifr.ifr_hwaddr), sizeof(struct sockaddr));
+
+ if (option_mask32 & ARP_OPT_v) {
+ xhw = get_hwntype(ifr.ifr_hwaddr.sa_family);
+ if (!xhw || !xhw->print) {
+ xhw = get_hwntype(-1);
+ }
+ bb_error_msg("device '%s' has HW address %s '%s'",
+ ifname, xhw->name,
+ xhw->print((unsigned char *) &ifr.ifr_hwaddr.sa_data));
+ }
+}
+
+/* Set an entry in the ARP cache. */
+/* Called only from main, once */
+static int arp_set(char **args)
+{
+ char *host;
+ struct arpreq req;
+ struct sockaddr sa;
+ int flags;
+
+ memset(&req, 0, sizeof(req));
+
+ host = *args++;
+ if (ap->input(host, &sa) < 0) {
+ bb_herror_msg_and_die("%s", host);
+ }
+ /* If a host has more than one address, use the correct one! */
+ memcpy(&req.arp_pa, &sa, sizeof(struct sockaddr));
+
+ /* Fetch the hardware address. */
+ if (*args == NULL) {
+ bb_error_msg_and_die("need hardware address");
+ }
+ if (option_mask32 & ARP_OPT_D) {
+ arp_getdevhw(*args++, &req.arp_ha, hw_set ? hw : NULL);
+ } else {
+ if (hw->input(*args++, &req.arp_ha) < 0) {
+ bb_error_msg_and_die("invalid hardware address");
+ }
+ }
+
+ /* Check out any modifiers. */
+ flags = ATF_PERM | ATF_COM;
+ while (*args != NULL) {
+ switch (index_in_strings(options, *args)) {
+ case 0: /* "pub" */
+ flags |= ATF_PUBL;
+ args++;
+ break;
+ case 1: /* "priv" */
+ flags &= ~ATF_PUBL;
+ args++;
+ break;
+ case 2: /* "temp" */
+ flags &= ~ATF_PERM;
+ args++;
+ break;
+ case 3: /* "trail" */
+ flags |= ATF_USETRAILERS;
+ args++;
+ break;
+ case 4: /* "dontpub" */
+#ifdef HAVE_ATF_DONTPUB
+ flags |= ATF_DONTPUB;
+#else
+ bb_error_msg("feature ATF_DONTPUB is not supported");
+#endif
+ args++;
+ break;
+ case 5: /* "auto" */
+#ifdef HAVE_ATF_MAGIC
+ flags |= ATF_MAGIC;
+#else
+ bb_error_msg("feature ATF_MAGIC is not supported");
+#endif
+ args++;
+ break;
+ case 6: /* "dev" */
+ if (*++args == NULL)
+ bb_show_usage();
+ device = *args;
+ args++;
+ break;
+ case 7: /* "netmask" */
+ if (*++args == NULL)
+ bb_show_usage();
+ if (strcmp(*args, "255.255.255.255") != 0) {
+ host = *args;
+ if (ap->input(host, &sa) < 0) {
+ bb_herror_msg_and_die("%s", host);
+ }
+ memcpy(&req.arp_netmask, &sa, sizeof(struct sockaddr));
+ flags |= ATF_NETMASK;
+ }
+ args++;
+ break;
+ default:
+ bb_show_usage();
+ break;
+ }
+ }
+
+ /* Fill in the remainder of the request. */
+ req.arp_flags = flags;
+
+ strncpy(req.arp_dev, device, sizeof(req.arp_dev));
+
+ /* Call the kernel. */
+ if (option_mask32 & ARP_OPT_v)
+ bb_error_msg("SIOCSARP()");
+ xioctl(sockfd, SIOCSARP, &req);
+ return 0;
+}
+
+
+/* Print the contents of an ARP request block. */
+static void
+arp_disp(const char *name, char *ip, int type, int arp_flags,
+ char *hwa, char *mask, char *dev)
+{
+ static const int arp_masks[] = {
+ ATF_PERM, ATF_PUBL,
+#ifdef HAVE_ATF_MAGIC
+ ATF_MAGIC,
+#endif
+#ifdef HAVE_ATF_DONTPUB
+ ATF_DONTPUB,
+#endif
+ ATF_USETRAILERS,
+ };
+ static const char arp_labels[] ALIGN1 = "PERM\0""PUP\0"
+#ifdef HAVE_ATF_MAGIC
+ "AUTO\0"
+#endif
+#ifdef HAVE_ATF_DONTPUB
+ "DONTPUB\0"
+#endif
+ "TRAIL\0"
+ ;
+
+ const struct hwtype *xhw;
+
+ xhw = get_hwntype(type);
+ if (xhw == NULL)
+ xhw = get_hwtype(DFLT_HW);
+
+ printf("%s (%s) at ", name, ip);
+
+ if (!(arp_flags & ATF_COM)) {
+ if (arp_flags & ATF_PUBL)
+ printf("* ");
+ else
+ printf("<incomplete> ");
+ } else {
+ printf("%s [%s] ", hwa, xhw->name);
+ }
+
+ if (arp_flags & ATF_NETMASK)
+ printf("netmask %s ", mask);
+
+ print_flags_separated(arp_masks, arp_labels, arp_flags, " ");
+ printf(" on %s\n", dev);
+}
+
+/* Display the contents of the ARP cache in the kernel. */
+/* Called only from main, once */
+static int arp_show(char *name)
+{
+ const char *host;
+ const char *hostname;
+ FILE *fp;
+ struct sockaddr sa;
+ int type, flags;
+ int num;
+ unsigned entries = 0, shown = 0;
+ char ip[128];
+ char hwa[128];
+ char mask[128];
+ char line[128];
+ char dev[128];
+
+ host = NULL;
+ if (name != NULL) {
+ /* Resolve the host name. */
+ if (ap->input(name, &sa) < 0) {
+ bb_herror_msg_and_die("%s", name);
+ }
+ host = xstrdup(ap->sprint(&sa, 1));
+ }
+ fp = xfopen_for_read("/proc/net/arp");
+ /* Bypass header -- read one line */
+ fgets(line, sizeof(line), fp);
+
+ /* Read the ARP cache entries. */
+ while (fgets(line, sizeof(line), fp)) {
+
+ mask[0] = '-'; mask[1] = '\0';
+ dev[0] = '-'; dev[1] = '\0';
+ /* All these strings can't overflow
+ * because fgets above reads limited amount of data */
+ num = sscanf(line, "%s 0x%x 0x%x %s %s %s\n",
+ ip, &type, &flags, hwa, mask, dev);
+ if (num < 4)
+ break;
+
+ entries++;
+ /* if the user specified hw-type differs, skip it */
+ if (hw_set && (type != hw->type))
+ continue;
+
+ /* if the user specified address differs, skip it */
+ if (host && strcmp(ip, host) != 0)
+ continue;
+
+ /* if the user specified device differs, skip it */
+ if (device[0] && strcmp(dev, device) != 0)
+ continue;
+
+ shown++;
+ /* This IS ugly but it works -be */
+ hostname = "?";
+ if (!(option_mask32 & ARP_OPT_n)) {
+ if (ap->input(ip, &sa) < 0)
+ hostname = ip;
+ else
+ hostname = ap->sprint(&sa, (option_mask32 & ARP_OPT_n) | 0x8000);
+ if (strcmp(hostname, ip) == 0)
+ hostname = "?";
+ }
+
+ arp_disp(hostname, ip, type, flags, hwa, mask, dev);
+ }
+ if (option_mask32 & ARP_OPT_v)
+ printf("Entries: %d\tSkipped: %d\tFound: %d\n",
+ entries, entries - shown, shown);
+
+ if (!shown) {
+ if (hw_set || host || device[0])
+ printf("No match found in %d entries\n", entries);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP) {
+ free((char*)host);
+ fclose(fp);
+ }
+ return 0;
+}
+
+int arp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int arp_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *hw_type = "ether";
+ const char *protocol;
+
+ /* Initialize variables... */
+ ap = get_aftype(DFLT_AF);
+ if (!ap)
+ bb_error_msg_and_die("%s: %s not supported", DFLT_AF, "address family");
+
+ getopt32(argv, "A:p:H:t:i:adnDsv", &protocol, &protocol,
+ &hw_type, &hw_type, &device);
+ argv += optind;
+ if (option_mask32 & ARP_OPT_A || option_mask32 & ARP_OPT_p) {
+ ap = get_aftype(protocol);
+ if (ap == NULL)
+ bb_error_msg_and_die("%s: unknown %s", protocol, "address family");
+ }
+ if (option_mask32 & ARP_OPT_A || option_mask32 & ARP_OPT_p) {
+ hw = get_hwtype(hw_type);
+ if (hw == NULL)
+ bb_error_msg_and_die("%s: unknown %s", hw_type, "hardware type");
+ hw_set = 1;
+ }
+ //if (option_mask32 & ARP_OPT_i)... -i
+
+ if (ap->af != AF_INET) {
+ bb_error_msg_and_die("%s: kernel only supports 'inet'", ap->name);
+ }
+
+ /* If no hw type specified get default */
+ if (!hw) {
+ hw = get_hwtype(DFLT_HW);
+ if (!hw)
+ bb_error_msg_and_die("%s: %s not supported", DFLT_HW, "hardware type");
+ }
+
+ if (hw->alen <= 0) {
+ bb_error_msg_and_die("%s: %s without ARP support",
+ hw->name, "hardware type");
+ }
+ sockfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+ /* Now see what we have to do here... */
+ if (option_mask32 & (ARP_OPT_d|ARP_OPT_s)) {
+ if (argv[0] == NULL)
+ bb_error_msg_and_die("need host name");
+ if (option_mask32 & ARP_OPT_s)
+ return arp_set(argv);
+ return arp_del(argv);
+ }
+ //if (option_mask32 & ARP_OPT_a) - default
+ return arp_show(argv[0]);
+}
diff --git a/networking/arping.c b/networking/arping.c
new file mode 100644
index 0000000..021dc86
--- /dev/null
+++ b/networking/arping.c
@@ -0,0 +1,401 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * arping.c - Ping hosts by ARP requests/replies
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Author: Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ * Busybox port: Nick Fedchik <nick@fedchik.org.ua>
+ */
+
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <netpacket/packet.h>
+
+#include "libbb.h"
+
+/* We don't expect to see 1000+ seconds delay, unsigned is enough */
+#define MONOTONIC_US() ((unsigned)monotonic_us())
+
+enum {
+ DAD = 1,
+ UNSOLICITED = 2,
+ ADVERT = 4,
+ QUIET = 8,
+ QUIT_ON_REPLY = 16,
+ BCAST_ONLY = 32,
+ UNICASTING = 64
+};
+
+struct globals {
+ struct in_addr src;
+ struct in_addr dst;
+ struct sockaddr_ll me;
+ struct sockaddr_ll he;
+ int sock_fd;
+
+ int count; // = -1;
+ unsigned last;
+ unsigned timeout_us;
+ unsigned start;
+
+ unsigned sent;
+ unsigned brd_sent;
+ unsigned received;
+ unsigned brd_recv;
+ unsigned req_recv;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define src (G.src )
+#define dst (G.dst )
+#define me (G.me )
+#define he (G.he )
+#define sock_fd (G.sock_fd )
+#define count (G.count )
+#define last (G.last )
+#define timeout_us (G.timeout_us)
+#define start (G.start )
+#define sent (G.sent )
+#define brd_sent (G.brd_sent )
+#define received (G.received )
+#define brd_recv (G.brd_recv )
+#define req_recv (G.req_recv )
+#define INIT_G() do { \
+ count = -1; \
+} while (0)
+
+// If GNUisms are not available...
+//static void *mempcpy(void *_dst, const void *_src, int n)
+//{
+// memcpy(_dst, _src, n);
+// return (char*)_dst + n;
+//}
+
+static int send_pack(struct in_addr *src_addr,
+ struct in_addr *dst_addr, struct sockaddr_ll *ME,
+ struct sockaddr_ll *HE)
+{
+ int err;
+ unsigned char buf[256];
+ struct arphdr *ah = (struct arphdr *) buf;
+ unsigned char *p = (unsigned char *) (ah + 1);
+
+ ah->ar_hrd = htons(ARPHRD_ETHER);
+ ah->ar_pro = htons(ETH_P_IP);
+ ah->ar_hln = ME->sll_halen;
+ ah->ar_pln = 4;
+ ah->ar_op = option_mask32 & ADVERT ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST);
+
+ p = mempcpy(p, &ME->sll_addr, ah->ar_hln);
+ p = mempcpy(p, src_addr, 4);
+
+ if (option_mask32 & ADVERT)
+ p = mempcpy(p, &ME->sll_addr, ah->ar_hln);
+ else
+ p = mempcpy(p, &HE->sll_addr, ah->ar_hln);
+
+ p = mempcpy(p, dst_addr, 4);
+
+ err = sendto(sock_fd, buf, p - buf, 0, (struct sockaddr *) HE, sizeof(*HE));
+ if (err == p - buf) {
+ last = MONOTONIC_US();
+ sent++;
+ if (!(option_mask32 & UNICASTING))
+ brd_sent++;
+ }
+ return err;
+}
+
+static void finish(void) NORETURN;
+static void finish(void)
+{
+ if (!(option_mask32 & QUIET)) {
+ printf("Sent %u probe(s) (%u broadcast(s))\n"
+ "Received %u repl%s"
+ " (%u request(s), %u broadcast(s))\n",
+ sent, brd_sent,
+ received, (received == 1) ? "ies" : "y",
+ req_recv, brd_recv);
+ }
+ if (option_mask32 & DAD)
+ exit(!!received);
+ if (option_mask32 & UNSOLICITED)
+ exit(EXIT_SUCCESS);
+ exit(!received);
+}
+
+static void catcher(void)
+{
+ unsigned now;
+
+ now = MONOTONIC_US();
+ if (start == 0)
+ start = now;
+
+ if (count == 0 || (timeout_us && (now - start) > timeout_us))
+ finish();
+
+ /* count < 0 means "infinite count" */
+ if (count > 0)
+ count--;
+
+ if (last == 0 || (now - last) > 500000) {
+ send_pack(&src, &dst, &me, &he);
+ if (count == 0 && (option_mask32 & UNSOLICITED))
+ finish();
+ }
+ alarm(1);
+}
+
+static bool recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM)
+{
+ struct arphdr *ah = (struct arphdr *) buf;
+ unsigned char *p = (unsigned char *) (ah + 1);
+ struct in_addr src_ip, dst_ip;
+
+ /* Filter out wild packets */
+ if (FROM->sll_pkttype != PACKET_HOST
+ && FROM->sll_pkttype != PACKET_BROADCAST
+ && FROM->sll_pkttype != PACKET_MULTICAST)
+ return false;
+
+ /* Only these types are recognised */
+ if (ah->ar_op != htons(ARPOP_REQUEST) && ah->ar_op != htons(ARPOP_REPLY))
+ return false;
+
+ /* ARPHRD check and this darned FDDI hack here :-( */
+ if (ah->ar_hrd != htons(FROM->sll_hatype)
+ && (FROM->sll_hatype != ARPHRD_FDDI || ah->ar_hrd != htons(ARPHRD_ETHER)))
+ return false;
+
+ /* Protocol must be IP. */
+ if (ah->ar_pro != htons(ETH_P_IP)
+ || (ah->ar_pln != 4)
+ || (ah->ar_hln != me.sll_halen)
+ || (len < (int)(sizeof(*ah) + 2 * (4 + ah->ar_hln))))
+ return false;
+
+ memcpy(&src_ip, p + ah->ar_hln, 4);
+ memcpy(&dst_ip, p + ah->ar_hln + 4 + ah->ar_hln, 4);
+
+ if (dst.s_addr != src_ip.s_addr)
+ return false;
+ if (!(option_mask32 & DAD)) {
+ if ((src.s_addr != dst_ip.s_addr)
+ || (memcmp(p + ah->ar_hln + 4, &me.sll_addr, ah->ar_hln)))
+ return false;
+ } else {
+ /* DAD packet was:
+ src_ip = 0 (or some src)
+ src_hw = ME
+ dst_ip = tested address
+ dst_hw = <unspec>
+
+ We fail, if receive request/reply with:
+ src_ip = tested_address
+ src_hw != ME
+ if src_ip in request was not zero, check
+ also that it matches to dst_ip, otherwise
+ dst_ip/dst_hw do not matter.
+ */
+ if ((memcmp(p, &me.sll_addr, me.sll_halen) == 0)
+ || (src.s_addr && src.s_addr != dst_ip.s_addr))
+ return false;
+ }
+ if (!(option_mask32 & QUIET)) {
+ int s_printed = 0;
+
+ printf("%scast re%s from %s [%s]",
+ FROM->sll_pkttype == PACKET_HOST ? "Uni" : "Broad",
+ ah->ar_op == htons(ARPOP_REPLY) ? "ply" : "quest",
+ inet_ntoa(src_ip),
+ ether_ntoa((struct ether_addr *) p));
+ if (dst_ip.s_addr != src.s_addr) {
+ printf("for %s ", inet_ntoa(dst_ip));
+ s_printed = 1;
+ }
+ if (memcmp(p + ah->ar_hln + 4, me.sll_addr, ah->ar_hln)) {
+ if (!s_printed)
+ printf("for ");
+ printf("[%s]",
+ ether_ntoa((struct ether_addr *) p + ah->ar_hln + 4));
+ }
+
+ if (last) {
+ unsigned diff = MONOTONIC_US() - last;
+ printf(" %u.%03ums\n", diff / 1000, diff % 1000);
+ } else {
+ printf(" UNSOLICITED?\n");
+ }
+ fflush(stdout);
+ }
+ received++;
+ if (FROM->sll_pkttype != PACKET_HOST)
+ brd_recv++;
+ if (ah->ar_op == htons(ARPOP_REQUEST))
+ req_recv++;
+ if (option_mask32 & QUIT_ON_REPLY)
+ finish();
+ if (!(option_mask32 & BCAST_ONLY)) {
+ memcpy(he.sll_addr, p, me.sll_halen);
+ option_mask32 |= UNICASTING;
+ }
+ return true;
+}
+
+int arping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int arping_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *device = "eth0";
+ char *source = NULL;
+ char *target;
+ unsigned char *packet;
+ char *err_str;
+
+ INIT_G();
+
+ sock_fd = xsocket(AF_PACKET, SOCK_DGRAM, 0);
+
+ // Drop suid root privileges
+ // Need to remove SUID_NEVER from applets.h for this to work
+ //xsetuid(getuid());
+
+ err_str = xasprintf("interface %s %%s", device);
+ {
+ unsigned opt;
+ char *str_timeout;
+
+ /* Dad also sets quit_on_reply.
+ * Advert also sets unsolicited.
+ */
+ opt_complementary = "=1:Df:AU:c+";
+ opt = getopt32(argv, "DUAqfbc:w:I:s:",
+ &count, &str_timeout, &device, &source);
+ if (opt & 0x80) /* -w: timeout */
+ timeout_us = xatou_range(str_timeout, 0, INT_MAX/2000000) * 1000000 + 500000;
+ //if (opt & 0x200) /* -s: source */
+ option_mask32 &= 0x3f; /* set respective flags */
+ }
+
+ target = argv[optind];
+
+ xfunc_error_retval = 2;
+
+ {
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name) - 1);
+ /* We use ifr.ifr_name in error msg so that problem
+ * with truncated name will be visible */
+ ioctl_or_perror_and_die(sock_fd, SIOCGIFINDEX, &ifr, err_str, "not found");
+ me.sll_ifindex = ifr.ifr_ifindex;
+
+ xioctl(sock_fd, SIOCGIFFLAGS, (char *) &ifr);
+
+ if (!(ifr.ifr_flags & IFF_UP)) {
+ bb_error_msg_and_die(err_str, "is down");
+ }
+ if (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)) {
+ bb_error_msg(err_str, "is not ARPable");
+ return (option_mask32 & DAD ? 0 : 2);
+ }
+ }
+
+ /* if (!inet_aton(target, &dst)) - not needed */ {
+ len_and_sockaddr *lsa;
+ lsa = xhost_and_af2sockaddr(target, 0, AF_INET);
+ memcpy(&dst, &lsa->u.sin.sin_addr.s_addr, 4);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(lsa);
+ }
+
+ if (source && !inet_aton(source, &src)) {
+ bb_error_msg_and_die("invalid source address %s", source);
+ }
+
+ if ((option_mask32 & (DAD|UNSOLICITED)) == UNSOLICITED && src.s_addr == 0)
+ src = dst;
+
+ if (!(option_mask32 & DAD) || src.s_addr) {
+ struct sockaddr_in saddr;
+ int probe_fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+ setsockopt_bindtodevice(probe_fd, device);
+ memset(&saddr, 0, sizeof(saddr));
+ saddr.sin_family = AF_INET;
+ if (src.s_addr) {
+ /* Check that this is indeed our IP */
+ saddr.sin_addr = src;
+ xbind(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr));
+ } else { /* !(option_mask32 & DAD) case */
+ /* Find IP address on this iface */
+ socklen_t alen = sizeof(saddr);
+
+ saddr.sin_port = htons(1025);
+ saddr.sin_addr = dst;
+
+ if (setsockopt(probe_fd, SOL_SOCKET, SO_DONTROUTE, &const_int_1, sizeof(const_int_1)) == -1)
+ bb_perror_msg("setsockopt(SO_DONTROUTE)");
+ xconnect(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr));
+ if (getsockname(probe_fd, (struct sockaddr *) &saddr, &alen) == -1) {
+ bb_perror_msg_and_die("getsockname");
+ }
+ if (saddr.sin_family != AF_INET)
+ bb_error_msg_and_die("no IP address configured");
+ src = saddr.sin_addr;
+ }
+ close(probe_fd);
+ }
+
+ me.sll_family = AF_PACKET;
+ //me.sll_ifindex = ifindex; - done before
+ me.sll_protocol = htons(ETH_P_ARP);
+ xbind(sock_fd, (struct sockaddr *) &me, sizeof(me));
+
+ {
+ socklen_t alen = sizeof(me);
+
+ if (getsockname(sock_fd, (struct sockaddr *) &me, &alen) == -1) {
+ bb_perror_msg_and_die("getsockname");
+ }
+ }
+ if (me.sll_halen == 0) {
+ bb_error_msg(err_str, "is not ARPable (no ll address)");
+ return (option_mask32 & DAD ? 0 : 2);
+ }
+ he = me;
+ memset(he.sll_addr, -1, he.sll_halen);
+
+ if (!(option_mask32 & QUIET)) {
+ /* inet_ntoa uses static storage, can't use in same printf */
+ printf("ARPING to %s", inet_ntoa(dst));
+ printf(" from %s via %s\n", inet_ntoa(src), device);
+ }
+
+ signal_SA_RESTART_empty_mask(SIGINT, (void (*)(int))finish);
+ signal_SA_RESTART_empty_mask(SIGALRM, (void (*)(int))catcher);
+
+ catcher();
+
+ packet = xmalloc(4096);
+ while (1) {
+ sigset_t sset, osset;
+ struct sockaddr_ll from;
+ socklen_t alen = sizeof(from);
+ int cc;
+
+ cc = recvfrom(sock_fd, packet, 4096, 0, (struct sockaddr *) &from, &alen);
+ if (cc < 0) {
+ bb_perror_msg("recvfrom");
+ continue;
+ }
+ sigemptyset(&sset);
+ sigaddset(&sset, SIGALRM);
+ sigaddset(&sset, SIGINT);
+ sigprocmask(SIG_BLOCK, &sset, &osset);
+ recv_pack(packet, cc, &from);
+ sigprocmask(SIG_SETMASK, &osset, NULL);
+ }
+}
diff --git a/networking/brctl.c b/networking/brctl.c
new file mode 100644
index 0000000..8475179
--- /dev/null
+++ b/networking/brctl.c
@@ -0,0 +1,272 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Small implementation of brctl for busybox.
+ *
+ * Copyright (C) 2008 by Bernhard Reutner-Fischer
+ *
+ * Some helper functions from bridge-utils are
+ * Copyright (C) 2000 Lennert Buytenhek
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* This applet currently uses only the ioctl interface and no sysfs at all.
+ * At the time of this writing this was considered a feature.
+ */
+#include "libbb.h"
+#include <linux/sockios.h>
+#include <net/if.h>
+
+/* Maximum number of ports supported per bridge interface. */
+#ifndef MAX_PORTS
+#define MAX_PORTS 32
+#endif
+
+/* Use internal number parsing and not the "exact" conversion. */
+/* #define BRCTL_USE_INTERNAL 0 */ /* use exact conversion */
+#define BRCTL_USE_INTERNAL 1
+
+#if ENABLE_FEATURE_BRCTL_FANCY
+#include <linux/if_bridge.h>
+
+/* FIXME: These 4 funcs are not really clean and could be improved */
+static ALWAYS_INLINE void strtotimeval(struct timeval *tv,
+ const char *time_str)
+{
+ double secs;
+#if BRCTL_USE_INTERNAL
+ secs = /*bb_*/strtod(time_str, NULL);
+ if (!secs)
+#else
+ if (sscanf(time_str, "%lf", &secs) != 1)
+#endif
+ bb_error_msg_and_die (bb_msg_invalid_arg, time_str, "timespec");
+ tv->tv_sec = secs;
+ tv->tv_usec = 1000000 * (secs - tv->tv_sec);
+}
+
+static ALWAYS_INLINE unsigned long __tv_to_jiffies(const struct timeval *tv)
+{
+ unsigned long long jif;
+
+ jif = 1000000ULL * tv->tv_sec + tv->tv_usec;
+
+ return jif/10000;
+}
+# if 0
+static void __jiffies_to_tv(struct timeval *tv, unsigned long jiffies)
+{
+ unsigned long long tvusec;
+
+ tvusec = 10000ULL*jiffies;
+ tv->tv_sec = tvusec/1000000;
+ tv->tv_usec = tvusec - 1000000 * tv->tv_sec;
+}
+# endif
+static unsigned long str_to_jiffies(const char *time_str)
+{
+ struct timeval tv;
+ strtotimeval(&tv, time_str);
+ return __tv_to_jiffies(&tv);
+}
+
+static void arm_ioctl(unsigned long *args,
+ unsigned long arg0, unsigned long arg1, unsigned long arg2)
+{
+ args[0] = arg0;
+ args[1] = arg1;
+ args[2] = arg2;
+ args[3] = 0;
+}
+#endif
+
+
+int brctl_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int brctl_main(int argc UNUSED_PARAM, char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ "addbr\0" "delbr\0" "addif\0" "delif\0"
+ USE_FEATURE_BRCTL_FANCY(
+ "stp\0"
+ "setageing\0" "setfd\0" "sethello\0" "setmaxage\0"
+ "setpathcost\0" "setportprio\0" "setbridgeprio\0"
+ )
+ USE_FEATURE_BRCTL_SHOW("showmacs\0" "show\0");
+
+ enum { ARG_addbr = 0, ARG_delbr, ARG_addif, ARG_delif
+ USE_FEATURE_BRCTL_FANCY(,
+ ARG_stp,
+ ARG_setageing, ARG_setfd, ARG_sethello, ARG_setmaxage,
+ ARG_setpathcost, ARG_setportprio, ARG_setbridgeprio
+ )
+ USE_FEATURE_BRCTL_SHOW(, ARG_showmacs, ARG_show)
+ };
+
+ int fd;
+ smallint key;
+ struct ifreq ifr;
+ char *br, *brif;
+
+ argv++;
+ while (*argv) {
+#if ENABLE_FEATURE_BRCTL_FANCY
+ int ifidx[MAX_PORTS];
+ unsigned long args[4];
+ ifr.ifr_data = (char *) &args;
+#endif
+
+ key = index_in_strings(keywords, *argv);
+ if (key == -1) /* no match found in keywords array, bail out. */
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ argv++;
+ fd = xsocket(AF_INET, SOCK_STREAM, 0);
+
+#if ENABLE_FEATURE_BRCTL_SHOW
+ if (key == ARG_show) { /* show */
+ char brname[IFNAMSIZ];
+ int bridx[MAX_PORTS];
+ int i, num;
+ arm_ioctl(args, BRCTL_GET_BRIDGES,
+ (unsigned long) bridx, MAX_PORTS);
+ num = xioctl(fd, SIOCGIFBR, args);
+ printf("bridge name\tbridge id\t\tSTP enabled\tinterfaces\n");
+ for (i = 0; i < num; i++) {
+ char ifname[IFNAMSIZ];
+ int j, tabs;
+ struct __bridge_info bi;
+ unsigned char *x;
+
+ if (!if_indextoname(bridx[i], brname))
+ bb_perror_msg_and_die("can't get bridge name for index %d", i);
+ strncpy(ifr.ifr_name, brname, IFNAMSIZ);
+
+ arm_ioctl(args, BRCTL_GET_BRIDGE_INFO,
+ (unsigned long) &bi, 0);
+ xioctl(fd, SIOCDEVPRIVATE, &ifr);
+ printf("%s\t\t", brname);
+
+ /* print bridge id */
+ x = (unsigned char *) &bi.bridge_id;
+ for (j = 0; j < 8; j++) {
+ printf("%.2x", x[j]);
+ if (j == 1)
+ bb_putchar('.');
+ }
+ printf(bi.stp_enabled ? "\tyes" : "\tno");
+
+ /* print interface list */
+ arm_ioctl(args, BRCTL_GET_PORT_LIST,
+ (unsigned long) ifidx, MAX_PORTS);
+ xioctl(fd, SIOCDEVPRIVATE, &ifr);
+ tabs = 0;
+ for (j = 0; j < MAX_PORTS; j++) {
+ if (!ifidx[j])
+ continue;
+ if (!if_indextoname(ifidx[j], ifname))
+ bb_perror_msg_and_die("can't get interface name for index %d", j);
+ if (tabs)
+ printf("\t\t\t\t\t");
+ else
+ tabs = 1;
+ printf("\t\t%s\n", ifname);
+ }
+ if (!tabs) /* bridge has no interfaces */
+ bb_putchar('\n');
+ }
+ goto done;
+ }
+#endif
+
+ if (!*argv) /* all but 'show' need at least one argument */
+ bb_show_usage();
+
+ br = *argv++;
+
+ if (key == ARG_addbr || key == ARG_delbr) { /* addbr or delbr */
+ ioctl_or_perror_and_die(fd,
+ key == ARG_addbr ? SIOCBRADDBR : SIOCBRDELBR,
+ br, "bridge %s", br);
+ goto done;
+ }
+
+ if (!*argv) /* all but 'addif/delif' need at least two arguments */
+ bb_show_usage();
+
+ strncpy(ifr.ifr_name, br, IFNAMSIZ);
+ if (key == ARG_addif || key == ARG_delif) { /* addif or delif */
+ brif = *argv;
+ ifr.ifr_ifindex = if_nametoindex(brif);
+ if (!ifr.ifr_ifindex) {
+ bb_perror_msg_and_die("iface %s", brif);
+ }
+ ioctl_or_perror_and_die(fd,
+ key == ARG_addif ? SIOCBRADDIF : SIOCBRDELIF,
+ &ifr, "bridge %s", br);
+ goto done_next_argv;
+ }
+#if ENABLE_FEATURE_BRCTL_FANCY
+ if (key == ARG_stp) { /* stp */
+ /* FIXME: parsing yes/y/on/1 versus no/n/off/0 is too involved */
+ arm_ioctl(args, BRCTL_SET_BRIDGE_STP_STATE,
+ (unsigned)(**argv - '0'), 0);
+ goto fire;
+ }
+ if ((unsigned)(key - ARG_setageing) < 4) { /* time related ops */
+ static const uint8_t ops[] ALIGN1 = {
+ BRCTL_SET_AGEING_TIME, /* ARG_setageing */
+ BRCTL_SET_BRIDGE_FORWARD_DELAY, /* ARG_setfd */
+ BRCTL_SET_BRIDGE_HELLO_TIME, /* ARG_sethello */
+ BRCTL_SET_BRIDGE_MAX_AGE /* ARG_setmaxage */
+ };
+ arm_ioctl(args, ops[key - ARG_setageing], str_to_jiffies(*argv), 0);
+ goto fire;
+ }
+ if (key == ARG_setpathcost
+ || key == ARG_setportprio
+ || key == ARG_setbridgeprio
+ ) {
+ static const uint8_t ops[] ALIGN1 = {
+ BRCTL_SET_PATH_COST, /* ARG_setpathcost */
+ BRCTL_SET_PORT_PRIORITY, /* ARG_setportprio */
+ BRCTL_SET_BRIDGE_PRIORITY /* ARG_setbridgeprio */
+ };
+ int port = -1;
+ unsigned arg1, arg2;
+
+ if (key != ARG_setbridgeprio) {
+ /* get portnum */
+ unsigned i;
+
+ port = if_nametoindex(*argv++);
+ if (!port)
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, "port");
+ memset(ifidx, 0, sizeof ifidx);
+ arm_ioctl(args, BRCTL_GET_PORT_LIST, (unsigned long)ifidx,
+ MAX_PORTS);
+ xioctl(fd, SIOCDEVPRIVATE, &ifr);
+ for (i = 0; i < MAX_PORTS; i++) {
+ if (ifidx[i] == port) {
+ port = i;
+ break;
+ }
+ }
+ }
+ arg1 = port;
+ arg2 = xatoi_u(*argv);
+ if (key == ARG_setbridgeprio) {
+ arg1 = arg2;
+ arg2 = 0;
+ }
+ arm_ioctl(args, ops[key - ARG_setpathcost], arg1, arg2);
+ }
+ fire:
+ /* Execute the previously set command */
+ xioctl(fd, SIOCDEVPRIVATE, &ifr);
+#endif
+ done_next_argv:
+ argv++;
+ done:
+ close(fd);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/networking/dnsd.c b/networking/dnsd.c
new file mode 100644
index 0000000..e8dcb40
--- /dev/null
+++ b/networking/dnsd.c
@@ -0,0 +1,380 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini DNS server implementation for busybox
+ *
+ * Copyright (C) 2005 Roberto A. Foglietta (me@roberto.foglietta.name)
+ * Copyright (C) 2005 Odd Arild Olsen (oao at fibula dot no)
+ * Copyright (C) 2003 Paul Sheer
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Odd Arild Olsen started out with the sheerdns [1] of Paul Sheer and rewrote
+ * it into a shape which I believe is both easier to understand and maintain.
+ * I also reused the input buffer for output and removed services he did not
+ * need. [1] http://threading.2038bug.com/sheerdns/
+ *
+ * Some bugfix and minor changes was applied by Roberto A. Foglietta who made
+ * the first porting of oao' scdns to busybox also.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+
+//#define DEBUG 1
+#define DEBUG 0
+
+enum {
+ MAX_HOST_LEN = 16, // longest host name allowed is 15
+ IP_STRING_LEN = 18, // .xxx.xxx.xxx.xxx\0
+
+//must be strlen('.in-addr.arpa') larger than IP_STRING_LEN
+ MAX_NAME_LEN = (IP_STRING_LEN + 13),
+
+/* Cannot get bigger packets than 512 per RFC1035
+ In practice this can be set considerably smaller:
+ Length of response packet is header (12B) + 2*type(4B) + 2*class(4B) +
+ ttl(4B) + rlen(2B) + r (MAX_NAME_LEN =21B) +
+ 2*querystring (2 MAX_NAME_LEN= 42B), all together 90 Byte
+*/
+ MAX_PACK_LEN = 512,
+
+ DEFAULT_TTL = 30, // increase this when not testing?
+
+ REQ_A = 1,
+ REQ_PTR = 12
+};
+
+struct dns_head { // the message from client and first part of response mag
+ uint16_t id;
+ uint16_t flags;
+ uint16_t nquer; // accepts 0
+ uint16_t nansw; // 1 in response
+ uint16_t nauth; // 0
+ uint16_t nadd; // 0
+};
+struct dns_prop {
+ uint16_t type;
+ uint16_t class;
+};
+struct dns_entry { // element of known name, ip address and reversed ip address
+ struct dns_entry *next;
+ char ip[IP_STRING_LEN]; // dotted decimal IP
+ char rip[IP_STRING_LEN]; // length decimal reversed IP
+ char name[MAX_HOST_LEN];
+};
+
+static struct dns_entry *dnsentry;
+static uint32_t ttl = DEFAULT_TTL;
+
+static const char *fileconf = "/etc/dnsd.conf";
+
+// Must match getopt32 call
+#define OPT_daemon (option_mask32 & 0x10)
+#define OPT_verbose (option_mask32 & 0x20)
+
+
+/*
+ * Convert host name from C-string to dns length/string.
+ */
+static void convname(char *a, uint8_t *q)
+{
+ int i = (q[0] == '.') ? 0 : 1;
+ for (; i < MAX_HOST_LEN-1 && *q; i++, q++)
+ a[i] = tolower(*q);
+ a[0] = i - 1;
+ a[i] = 0;
+}
+
+/*
+ * Insert length of substrings instead of dots
+ */
+static void undot(uint8_t * rip)
+{
+ int i = 0, s = 0;
+ while (rip[i])
+ i++;
+ for (--i; i >= 0; i--) {
+ if (rip[i] == '.') {
+ rip[i] = s;
+ s = 0;
+ } else s++;
+ }
+}
+
+/*
+ * Read hostname/IP records from file
+ */
+static void dnsentryinit(void)
+{
+ char *token[2];
+ parser_t *parser;
+ struct dns_entry *m, *prev;
+
+ prev = dnsentry = NULL;
+ parser = config_open(fileconf);
+ while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+ unsigned a, b, c, d;
+ /*
+ * Assumes all host names are lower case only
+ * Hostnames with more than one label are not handled correctly.
+ * Presently the dot is copied into name without
+ * converting to a length/string substring for that label.
+ */
+// if (!token[1] || sscanf(token[1], ".%u.%u.%u.%u"+1, &a, &b, &c, &d) != 4)
+ if (sscanf(token[1], ".%u.%u.%u.%u"+1, &a, &b, &c, &d) != 4)
+ continue;
+
+ m = xzalloc(sizeof(*m));
+ /*m->next = NULL;*/
+ sprintf(m->ip, ".%u.%u.%u.%u"+1, a, b, c, d);
+ sprintf(m->rip, ".%u.%u.%u.%u", d, c, b, a);
+ undot((uint8_t*)m->rip);
+ convname(m->name, (uint8_t*)token[0]);
+
+ if (OPT_verbose)
+ fprintf(stderr, "\tname:%s, ip:%s\n", &(m->name[1]), m->ip);
+
+ if (prev == NULL)
+ dnsentry = m;
+ else
+ prev->next = m;
+ prev = m;
+ }
+ config_close(parser);
+}
+
+/*
+ * Look query up in dns records and return answer if found
+ * qs is the query string, first byte the string length
+ */
+static int table_lookup(uint16_t type, uint8_t * as, uint8_t * qs)
+{
+ int i;
+ struct dns_entry *d = dnsentry;
+
+ do {
+#if DEBUG
+ char *p,*q;
+ q = (char *)&(qs[1]);
+ p = &(d->name[1]);
+ fprintf(stderr, "\n%s: %d/%d p:%s q:%s %d",
+ __FUNCTION__, (int)strlen(p), (int)(d->name[0]),
+ p, q, (int)strlen(q));
+#endif
+ if (type == REQ_A) { /* search by host name */
+ for (i = 1; i <= (int)(d->name[0]); i++)
+ if (tolower(qs[i]) != d->name[i])
+ break;
+ if (i > (int)(d->name[0]) ||
+ (d->name[0] == 1 && d->name[1] == '*')) {
+ strcpy((char *)as, d->ip);
+#if DEBUG
+ fprintf(stderr, " OK as:%s\n", as);
+#endif
+ return 0;
+ }
+ } else if (type == REQ_PTR) { /* search by IP-address */
+ if ((d->name[0] != 1 || d->name[1] != '*') &&
+ !strncmp((char*)&d->rip[1], (char*)&qs[1], strlen(d->rip)-1)) {
+ strcpy((char *)as, d->name);
+ return 0;
+ }
+ }
+ d = d->next;
+ } while (d);
+ return -1;
+}
+
+/*
+ * Decode message and generate answer
+ */
+static int process_packet(uint8_t *buf)
+{
+ uint8_t answstr[MAX_NAME_LEN + 1];
+ struct dns_head *head;
+ struct dns_prop *qprop;
+ uint8_t *from, *answb;
+ uint16_t outr_rlen;
+ uint16_t outr_flags;
+ uint16_t flags;
+ int lookup_result, type, packet_len;
+ int querystr_len;
+
+ answstr[0] = '\0';
+
+ head = (struct dns_head *)buf;
+ if (head->nquer == 0) {
+ bb_error_msg("no queries");
+ return -1;
+ }
+
+ if (head->flags & 0x8000) {
+ bb_error_msg("ignoring response packet");
+ return -1;
+ }
+
+ from = (void *)&head[1]; // start of query string
+//FIXME: strlen of untrusted data??!
+ querystr_len = strlen((char *)from) + 1 + sizeof(struct dns_prop);
+ answb = from + querystr_len; // where to append answer block
+
+ outr_rlen = 0;
+ outr_flags = 0;
+
+ qprop = (struct dns_prop *)(answb - 4);
+ type = ntohs(qprop->type);
+
+ // only let REQ_A and REQ_PTR pass
+ if (!(type == REQ_A || type == REQ_PTR)) {
+ goto empty_packet; /* we can't handle the query type */
+ }
+
+ if (ntohs(qprop->class) != 1 /* class INET */ ) {
+ outr_flags = 4; /* not supported */
+ goto empty_packet;
+ }
+ /* we only support standard queries */
+
+ if ((ntohs(head->flags) & 0x7800) != 0)
+ goto empty_packet;
+
+ // We have a standard query
+ bb_info_msg("%s", (char *)from);
+ lookup_result = table_lookup(type, answstr, from);
+ if (lookup_result != 0) {
+ outr_flags = 3 | 0x0400; // name do not exist and auth
+ goto empty_packet;
+ }
+ if (type == REQ_A) { // return an address
+ struct in_addr a; // NB! its "struct { unsigned __long__ s_addr; }"
+ uint32_t v32;
+ if (!inet_aton((char*)answstr, &a)) { //dotted dec to long conv
+ outr_flags = 1; /* Frmt err */
+ goto empty_packet;
+ }
+ v32 = a.s_addr; /* in case long != int */
+ memcpy(answstr, &v32, 4);
+ outr_rlen = 4; // uint32_t IP
+ } else
+ outr_rlen = strlen((char *)answstr) + 1; // a host name
+ outr_flags |= 0x0400; /* authority-bit */
+ // we have an answer
+ head->nansw = htons(1);
+
+ // copy query block to answer block
+ memcpy(answb, from, querystr_len);
+ answb += querystr_len;
+
+ // and append answer rr
+// FIXME: unaligned accesses??
+ *(uint32_t *) answb = htonl(ttl);
+ answb += 4;
+ *(uint16_t *) answb = htons(outr_rlen);
+ answb += 2;
+ memcpy(answb, answstr, outr_rlen);
+ answb += outr_rlen;
+
+ empty_packet:
+
+ flags = ntohs(head->flags);
+ // clear rcode and RA, set responsebit and our new flags
+ flags |= (outr_flags & 0xff80) | 0x8000;
+ head->flags = htons(flags);
+ head->nauth = head->nadd = 0;
+ head->nquer = htons(1);
+
+ packet_len = answb - buf;
+ return packet_len;
+}
+
+/*
+ * Exit on signal
+ */
+static void interrupt(int sig)
+{
+ /* unlink("/var/run/dnsd.lock"); */
+ bb_error_msg("interrupt, exiting\n");
+ kill_myself_with_sig(sig);
+}
+
+int dnsd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dnsd_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *listen_interface = "0.0.0.0";
+ char *sttl, *sport;
+ len_and_sockaddr *lsa, *from, *to;
+ unsigned lsa_size;
+ int udps;
+ uint16_t port = 53;
+ /* Paranoid sizing: querystring x2 + ttl + outr_rlen + answstr */
+ /* I'd rather see process_packet() fixed instead... */
+ uint8_t buf[MAX_PACK_LEN * 2 + 4 + 2 + (MAX_NAME_LEN+1)];
+
+ getopt32(argv, "i:c:t:p:dv", &listen_interface, &fileconf, &sttl, &sport);
+ //if (option_mask32 & 0x1) // -i
+ //if (option_mask32 & 0x2) // -c
+ if (option_mask32 & 0x4) // -t
+ ttl = xatou_range(sttl, 1, 0xffffffff);
+ if (option_mask32 & 0x8) // -p
+ port = xatou_range(sport, 1, 0xffff);
+
+ if (OPT_verbose) {
+ bb_info_msg("listen_interface: %s", listen_interface);
+ bb_info_msg("ttl: %d, port: %d", ttl, port);
+ bb_info_msg("fileconf: %s", fileconf);
+ }
+
+ if (OPT_daemon) {
+ bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv);
+ openlog(applet_name, LOG_PID, LOG_DAEMON);
+ logmode = LOGMODE_SYSLOG;
+ }
+
+ dnsentryinit();
+
+ signal(SIGINT, interrupt);
+ bb_signals(0
+ /* why? + (1 << SIGPIPE) */
+ + (1 << SIGHUP)
+#ifdef SIGTSTP
+ + (1 << SIGTSTP)
+#endif
+#ifdef SIGURG
+ + (1 << SIGURG)
+#endif
+ , SIG_IGN);
+
+ lsa = xdotted2sockaddr(listen_interface, port);
+ udps = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+ xbind(udps, &lsa->u.sa, lsa->len);
+ socket_want_pktinfo(udps); /* needed for recv_from_to to work */
+ lsa_size = LSA_LEN_SIZE + lsa->len;
+ from = xzalloc(lsa_size);
+ to = xzalloc(lsa_size);
+
+ bb_info_msg("Accepting UDP packets on %s",
+ xmalloc_sockaddr2dotted(&lsa->u.sa));
+
+ while (1) {
+ int r;
+ /* Try to get *DEST* address (to which of our addresses
+ * this query was directed), and reply from the same address.
+ * Or else we can exhibit usual UDP ugliness:
+ * [ip1.multihomed.ip2] <= query to ip1 <= peer
+ * [ip1.multihomed.ip2] => reply from ip2 => peer (confused) */
+ memcpy(to, lsa, lsa_size);
+ r = recv_from_to(udps, buf, MAX_PACK_LEN + 1, 0, &from->u.sa, &to->u.sa, lsa->len);
+ if (r < 12 || r > MAX_PACK_LEN) {
+ bb_error_msg("invalid packet size");
+ continue;
+ }
+ if (OPT_verbose)
+ bb_info_msg("Got UDP packet");
+ buf[r] = '\0'; /* paranoia */
+ r = process_packet(buf);
+ if (r <= 0)
+ continue;
+ send_to_from(udps, buf, r, 0, &from->u.sa, &to->u.sa, lsa->len);
+ }
+ return 0;
+}
diff --git a/networking/ether-wake.c b/networking/ether-wake.c
new file mode 100644
index 0000000..a37b6eb
--- /dev/null
+++ b/networking/ether-wake.c
@@ -0,0 +1,276 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ether-wake.c - Send a magic packet to wake up sleeping machines.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Author: Donald Becker, http://www.scyld.com/"; http://www.scyld.com/wakeonlan.html
+ * Busybox port: Christian Volkmann <haveaniceday@online.de>
+ * Used version of ether-wake.c: v1.09 11/12/2003 Donald Becker, http://www.scyld.com/";
+ */
+
+/* full usage according Donald Becker
+ * usage: ether-wake [-i <ifname>] [-p aa:bb:cc:dd[:ee:ff]] 00:11:22:33:44:55\n"
+ *
+ * This program generates and transmits a Wake-On-LAN (WOL)\n"
+ * \"Magic Packet\", used for restarting machines that have been\n"
+ * soft-powered-down (ACPI D3-warm state).\n"
+ * It currently generates the standard AMD Magic Packet format, with\n"
+ * an optional password appended.\n"
+ *
+ * The single required parameter is the Ethernet MAC (station) address\n"
+ * of the machine to wake or a host ID with known NSS 'ethers' entry.\n"
+ * The MAC address may be found with the 'arp' program while the target\n"
+ * machine is awake.\n"
+ *
+ * Options:\n"
+ * -b Send wake-up packet to the broadcast address.\n"
+ * -D Increase the debug level.\n"
+ * -i ifname Use interface IFNAME instead of the default 'eth0'.\n"
+ * -p <pw> Append the four or six byte password PW to the packet.\n"
+ * A password is only required for a few adapter types.\n"
+ * The password may be specified in ethernet hex format\n"
+ * or dotted decimal (Internet address)\n"
+ * -p 00:22:44:66:88:aa\n"
+ * -p 192.168.1.1\n";
+ *
+ *
+ * This program generates and transmits a Wake-On-LAN (WOL) "Magic Packet",
+ * used for restarting machines that have been soft-powered-down
+ * (ACPI D3-warm state). It currently generates the standard AMD Magic Packet
+ * format, with an optional password appended.
+ *
+ * This software may be used and distributed according to the terms
+ * of the GNU Public License, incorporated herein by reference.
+ * Contact the author for use under other terms.
+ *
+ * This source file was originally part of the network tricks package, and
+ * is now distributed to support the Scyld Beowulf system.
+ * Copyright 1999-2003 Donald Becker and Scyld Computing Corporation.
+ *
+ * The author may be reached as becker@scyld, or C/O
+ * Scyld Computing Corporation
+ * 914 Bay Ridge Road, Suite 220
+ * Annapolis MD 21403
+ *
+ * Notes:
+ * On some systems dropping root capability allows the process to be
+ * dumped, traced or debugged.
+ * If someone traces this program, they get control of a raw socket.
+ * Linux handles this safely, but beware when porting this program.
+ *
+ * An alternative to needing 'root' is using a UDP broadcast socket, however
+ * doing so only works with adapters configured for unicast+broadcast Rx
+ * filter. That configuration consumes more power.
+*/
+
+
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#include <netinet/ether.h>
+#include <linux/if.h>
+
+#include "libbb.h"
+
+/* Note: PF_INET, SOCK_DGRAM, IPPROTO_UDP would allow SIOCGIFHWADDR to
+ * work as non-root, but we need SOCK_PACKET to specify the Ethernet
+ * destination address.
+ */
+#ifdef PF_PACKET
+# define whereto_t sockaddr_ll
+# define make_socket() xsocket(PF_PACKET, SOCK_RAW, 0)
+#else
+# define whereto_t sockaddr
+# define make_socket() xsocket(AF_INET, SOCK_PACKET, SOCK_PACKET)
+#endif
+
+#ifdef DEBUG
+# define bb_debug_msg(fmt, args...) fprintf(stderr, fmt, ## args)
+void bb_debug_dump_packet(unsigned char *outpack, int pktsize)
+{
+ int i;
+ printf("packet dump:\n");
+ for (i = 0; i < pktsize; ++i) {
+ printf("%2.2x ", outpack[i]);
+ if (i % 20 == 19) bb_putchar('\n');
+ }
+ printf("\n\n");
+}
+#else
+# define bb_debug_msg(fmt, args...) ((void)0)
+# define bb_debug_dump_packet(outpack, pktsize) ((void)0)
+#endif
+
+/* Convert the host ID string to a MAC address.
+ * The string may be a:
+ * Host name
+ * IP address string
+ * MAC address string
+*/
+static void get_dest_addr(const char *hostid, struct ether_addr *eaddr)
+{
+ struct ether_addr *eap;
+
+ eap = ether_aton(hostid);
+ if (eap) {
+ *eaddr = *eap;
+ bb_debug_msg("The target station address is %s\n\n", ether_ntoa(eaddr));
+#if !defined(__UCLIBC__)
+ } else if (ether_hostton(hostid, eaddr) == 0) {
+ bb_debug_msg("Station address for hostname %s is %s\n\n", hostid, ether_ntoa(eaddr));
+#endif
+ } else
+ bb_show_usage();
+}
+
+static int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast)
+{
+ int i;
+ unsigned char *station_addr = eaddr->ether_addr_octet;
+
+ memset(pkt, 0xff, 6);
+ if (!broadcast)
+ memcpy(pkt, station_addr, 6);
+ pkt += 6;
+
+ memcpy(pkt, station_addr, 6); /* 6 */
+ pkt += 6;
+
+ *pkt++ = 0x08; /* 12 */ /* Or 0x0806 for ARP, 0x8035 for RARP */
+ *pkt++ = 0x42; /* 13 */
+
+ memset(pkt, 0xff, 6); /* 14 */
+
+ for (i = 0; i < 16; ++i) {
+ pkt += 6;
+ memcpy(pkt, station_addr, 6); /* 20,26,32,... */
+ }
+
+ return 20 + 16*6; /* length of packet */
+}
+
+static int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd)
+{
+ unsigned passwd[6];
+ int byte_cnt, i;
+
+ /* handle MAC format */
+ byte_cnt = sscanf(ethoptarg, "%2x:%2x:%2x:%2x:%2x:%2x",
+ &passwd[0], &passwd[1], &passwd[2],
+ &passwd[3], &passwd[4], &passwd[5]);
+ /* handle IP format */
+// FIXME: why < 4?? should it be < 6?
+ if (byte_cnt < 4)
+ byte_cnt = sscanf(ethoptarg, "%u.%u.%u.%u",
+ &passwd[0], &passwd[1], &passwd[2], &passwd[3]);
+ if (byte_cnt < 4) {
+ bb_error_msg("cannot read Wake-On-LAN pass");
+ return 0;
+ }
+// TODO: check invalid numbers >255??
+ for (i = 0; i < byte_cnt; ++i)
+ wol_passwd[i] = passwd[i];
+
+ bb_debug_msg("password: %2.2x %2.2x %2.2x %2.2x (%d)\n\n",
+ wol_passwd[0], wol_passwd[1], wol_passwd[2], wol_passwd[3],
+ byte_cnt);
+
+ return byte_cnt;
+}
+
+int ether_wake_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ether_wake_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *ifname = "eth0";
+ char *pass;
+ unsigned flags;
+ unsigned char wol_passwd[6];
+ int wol_passwd_sz = 0;
+ int s; /* Raw socket */
+ int pktsize;
+ unsigned char outpack[1000];
+
+ struct ether_addr eaddr;
+ struct whereto_t whereto; /* who to wake up */
+
+ /* handle misc user options */
+ opt_complementary = "=1";
+ flags = getopt32(argv, "bi:p:", &ifname, &pass);
+ if (flags & 4) /* -p */
+ wol_passwd_sz = get_wol_pw(pass, wol_passwd);
+ flags &= 1; /* we further interested only in -b [bcast] flag */
+
+ /* create the raw socket */
+ s = make_socket();
+
+ /* now that we have a raw socket we can drop root */
+ /* xsetuid(getuid()); - but save on code size... */
+
+ /* look up the dest mac address */
+ get_dest_addr(argv[optind], &eaddr);
+
+ /* fill out the header of the packet */
+ pktsize = get_fill(outpack, &eaddr, flags /* & 1 OPT_BROADCAST */);
+
+ bb_debug_dump_packet(outpack, pktsize);
+
+ /* Fill in the source address, if possible. */
+#ifdef __linux__
+ {
+ struct ifreq if_hwaddr;
+
+ strncpy(if_hwaddr.ifr_name, ifname, sizeof(if_hwaddr.ifr_name));
+ ioctl_or_perror_and_die(s, SIOCGIFHWADDR, &if_hwaddr, "SIOCGIFHWADDR on %s failed", ifname);
+
+ memcpy(outpack+6, if_hwaddr.ifr_hwaddr.sa_data, 6);
+
+# ifdef DEBUG
+ {
+ unsigned char *hwaddr = if_hwaddr.ifr_hwaddr.sa_data;
+ printf("The hardware address (SIOCGIFHWADDR) of %s is type %d "
+ "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n\n", ifname,
+ if_hwaddr.ifr_hwaddr.sa_family, hwaddr[0], hwaddr[1],
+ hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
+ }
+# endif
+ }
+#endif /* __linux__ */
+
+ bb_debug_dump_packet(outpack, pktsize);
+
+ /* append the password if specified */
+ if (wol_passwd_sz > 0) {
+ memcpy(outpack+pktsize, wol_passwd, wol_passwd_sz);
+ pktsize += wol_passwd_sz;
+ }
+
+ bb_debug_dump_packet(outpack, pktsize);
+
+ /* This is necessary for broadcasts to work */
+ if (flags /* & 1 OPT_BROADCAST */) {
+ if (setsockopt_broadcast(s) != 0)
+ bb_perror_msg("SO_BROADCAST");
+ }
+
+#if defined(PF_PACKET)
+ {
+ struct ifreq ifr;
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ xioctl(s, SIOCGIFINDEX, &ifr);
+ memset(&whereto, 0, sizeof(whereto));
+ whereto.sll_family = AF_PACKET;
+ whereto.sll_ifindex = ifr.ifr_ifindex;
+ /* The manual page incorrectly claims the address must be filled.
+ We do so because the code may change to match the docs. */
+ whereto.sll_halen = ETH_ALEN;
+ memcpy(whereto.sll_addr, outpack, ETH_ALEN);
+ }
+#else
+ whereto.sa_family = 0;
+ strcpy(whereto.sa_data, ifname);
+#endif
+ xsendto(s, outpack, pktsize, (struct sockaddr *)&whereto, sizeof(whereto));
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(s);
+ return EXIT_SUCCESS;
+}
diff --git a/networking/ftpgetput.c b/networking/ftpgetput.c
new file mode 100644
index 0000000..d39b73e
--- /dev/null
+++ b/networking/ftpgetput.c
@@ -0,0 +1,325 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ftpget
+ *
+ * Mini implementation of FTP to retrieve a remote file.
+ *
+ * Copyright (C) 2002 Jeff Angielski, The PTR Group <jeff@theptrgroup.com>
+ * Copyright (C) 2002 Glenn McGrath
+ *
+ * Based on wget.c by Chip Rosenthal Covad Communications
+ * <chip@laserlink.net>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+struct globals {
+ const char *user;
+ const char *password;
+ struct len_and_sockaddr *lsa;
+ FILE *control_stream;
+ int verbose_flag;
+ int do_continue;
+ char buf[1]; /* actually [BUFSZ] */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+enum { BUFSZ = COMMON_BUFSIZE - offsetof(struct globals, buf) };
+struct BUG_G_too_big {
+ char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define user (G.user )
+#define password (G.password )
+#define lsa (G.lsa )
+#define control_stream (G.control_stream)
+#define verbose_flag (G.verbose_flag )
+#define do_continue (G.do_continue )
+#define buf (G.buf )
+#define INIT_G() do { } while (0)
+
+
+static void ftp_die(const char *msg) NORETURN;
+static void ftp_die(const char *msg)
+{
+ char *cp = buf; /* buf holds peer's response */
+
+ /* Guard against garbage from remote server */
+ while (*cp >= ' ' && *cp < '\x7f')
+ cp++;
+ *cp = '\0';
+ bb_error_msg_and_die("unexpected server response%s%s: %s",
+ (msg ? " to " : ""), (msg ? msg : ""), buf);
+}
+
+static int ftpcmd(const char *s1, const char *s2)
+{
+ unsigned n;
+
+ if (verbose_flag) {
+ bb_error_msg("cmd %s %s", s1, s2);
+ }
+
+ if (s1) {
+ fprintf(control_stream, (s2 ? "%s %s\r\n" : "%s %s\r\n"+3),
+ s1, s2);
+ fflush(control_stream);
+ }
+
+ do {
+ strcpy(buf, "EOF");
+ if (fgets(buf, BUFSZ - 2, control_stream) == NULL) {
+ ftp_die(NULL);
+ }
+ } while (!isdigit(buf[0]) || buf[3] != ' ');
+
+ buf[3] = '\0';
+ n = xatou(buf);
+ buf[3] = ' ';
+ return n;
+}
+
+static void ftp_login(void)
+{
+ /* Connect to the command socket */
+ control_stream = fdopen(xconnect_stream(lsa), "r+");
+ if (control_stream == NULL) {
+ /* fdopen failed - extremely unlikely */
+ bb_perror_nomsg_and_die();
+ }
+
+ if (ftpcmd(NULL, NULL) != 220) {
+ ftp_die(NULL);
+ }
+
+ /* Login to the server */
+ switch (ftpcmd("USER", user)) {
+ case 230:
+ break;
+ case 331:
+ if (ftpcmd("PASS", password) != 230) {
+ ftp_die("PASS");
+ }
+ break;
+ default:
+ ftp_die("USER");
+ }
+
+ ftpcmd("TYPE I", NULL);
+}
+
+static int xconnect_ftpdata(void)
+{
+ char *buf_ptr;
+ unsigned port_num;
+
+/*
+TODO: PASV command will not work for IPv6. RFC2428 describes
+IPv6-capable "extended PASV" - EPSV.
+
+"EPSV [protocol]" asks server to bind to and listen on a data port
+in specified protocol. Protocol is 1 for IPv4, 2 for IPv6.
+If not specified, defaults to "same as used for control connection".
+If server understood you, it should answer "229 <some text>(|||port|)"
+where "|" are literal pipe chars and "port" is ASCII decimal port#.
+
+There is also an IPv6-capable replacement for PORT (EPRT),
+but we don't need that.
+
+NB: PASV may still work for some servers even over IPv6.
+For example, vsftp happily answers
+"227 Entering Passive Mode (0,0,0,0,n,n)" and proceeds as usual.
+
+TODO2: need to stop ignoring IP address in PASV response.
+*/
+
+ if (ftpcmd("PASV", NULL) != 227) {
+ ftp_die("PASV");
+ }
+
+ /* Response is "NNN garbageN1,N2,N3,N4,P1,P2[)garbage]
+ * Server's IP is N1.N2.N3.N4 (we ignore it)
+ * Server's port for data connection is P1*256+P2 */
+ buf_ptr = strrchr(buf, ')');
+ if (buf_ptr) *buf_ptr = '\0';
+
+ buf_ptr = strrchr(buf, ',');
+ *buf_ptr = '\0';
+ port_num = xatoul_range(buf_ptr + 1, 0, 255);
+
+ buf_ptr = strrchr(buf, ',');
+ *buf_ptr = '\0';
+ port_num += xatoul_range(buf_ptr + 1, 0, 255) * 256;
+
+ set_nport(lsa, htons(port_num));
+ return xconnect_stream(lsa);
+}
+
+static int pump_data_and_QUIT(int from, int to)
+{
+ /* copy the file */
+ if (bb_copyfd_eof(from, to) == -1) {
+ /* error msg is already printed by bb_copyfd_eof */
+ return EXIT_FAILURE;
+ }
+
+ /* close data connection */
+ close(from); /* don't know which one is that, so we close both */
+ close(to);
+
+ /* does server confirm that transfer is finished? */
+ if (ftpcmd(NULL, NULL) != 226) {
+ ftp_die(NULL);
+ }
+ ftpcmd("QUIT", NULL);
+
+ return EXIT_SUCCESS;
+}
+
+#if !ENABLE_FTPGET
+int ftp_receive(const char *local_path, char *server_path);
+#else
+static
+int ftp_receive(const char *local_path, char *server_path)
+{
+ int fd_data;
+ int fd_local = -1;
+ off_t beg_range = 0;
+
+ /* connect to the data socket */
+ fd_data = xconnect_ftpdata();
+
+ if (ftpcmd("SIZE", server_path) != 213) {
+ do_continue = 0;
+ }
+
+ if (LONE_DASH(local_path)) {
+ fd_local = STDOUT_FILENO;
+ do_continue = 0;
+ }
+
+ if (do_continue) {
+ struct stat sbuf;
+ /* lstat would be wrong here! */
+ if (stat(local_path, &sbuf) < 0) {
+ bb_perror_msg_and_die("stat");
+ }
+ if (sbuf.st_size > 0) {
+ beg_range = sbuf.st_size;
+ } else {
+ do_continue = 0;
+ }
+ }
+
+ if (do_continue) {
+ sprintf(buf, "REST %"OFF_FMT"d", beg_range);
+ if (ftpcmd(buf, NULL) != 350) {
+ do_continue = 0;
+ }
+ }
+
+ if (ftpcmd("RETR", server_path) > 150) {
+ ftp_die("RETR");
+ }
+
+ /* create local file _after_ we know that remote file exists */
+ if (fd_local == -1) {
+ fd_local = xopen(local_path,
+ do_continue ? (O_APPEND | O_WRONLY)
+ : (O_CREAT | O_TRUNC | O_WRONLY)
+ );
+ }
+
+ return pump_data_and_QUIT(fd_data, fd_local);
+}
+#endif
+
+#if !ENABLE_FTPPUT
+int ftp_send(const char *server_path, char *local_path);
+#else
+static
+int ftp_send(const char *server_path, char *local_path)
+{
+ int fd_data;
+ int fd_local;
+ int response;
+
+ /* connect to the data socket */
+ fd_data = xconnect_ftpdata();
+
+ /* get the local file */
+ fd_local = STDIN_FILENO;
+ if (NOT_LONE_DASH(local_path))
+ fd_local = xopen(local_path, O_RDONLY);
+
+ response = ftpcmd("STOR", server_path);
+ switch (response) {
+ case 125:
+ case 150:
+ break;
+ default:
+ ftp_die("STOR");
+ }
+
+ return pump_data_and_QUIT(fd_local, fd_data);
+}
+#endif
+
+#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
+static const char ftpgetput_longopts[] ALIGN1 =
+ "continue\0" Required_argument "c"
+ "verbose\0" No_argument "v"
+ "username\0" Required_argument "u"
+ "password\0" Required_argument "p"
+ "port\0" Required_argument "P"
+ ;
+#endif
+
+int ftpgetput_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ftpgetput_main(int argc UNUSED_PARAM, char **argv)
+{
+ unsigned opt;
+ const char *port = "ftp";
+ /* socket to ftp server */
+
+#if ENABLE_FTPPUT && !ENABLE_FTPGET
+# define ftp_action ftp_send
+#elif ENABLE_FTPGET && !ENABLE_FTPPUT
+# define ftp_action ftp_receive
+#else
+ int (*ftp_action)(const char *, char *) = ftp_send;
+
+ /* Check to see if the command is ftpget or ftput */
+ if (applet_name[3] == 'g') {
+ ftp_action = ftp_receive;
+ }
+#endif
+
+ INIT_G();
+ /* Set default values */
+ user = "anonymous";
+ password = "busybox@";
+
+ /*
+ * Decipher the command line
+ */
+#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS
+ applet_long_options = ftpgetput_longopts;
+#endif
+ opt_complementary = "=3:vv:cc"; /* must have 3 params; -v and -c count */
+ opt = getopt32(argv, "cvu:p:P:", &user, &password, &port,
+ &verbose_flag, &do_continue);
+ argv += optind;
+
+ /* We want to do exactly _one_ DNS lookup, since some
+ * sites (i.e. ftp.us.debian.org) use round-robin DNS
+ * and we want to connect to only one IP... */
+ lsa = xhost2sockaddr(argv[0], bb_lookup_port(port, "tcp", 21));
+ if (verbose_flag) {
+ printf("Connecting to %s (%s)\n", argv[0],
+ xmalloc_sockaddr2dotted(&lsa->u.sa));
+ }
+
+ ftp_login();
+ return ftp_action(argv[1], argv[2]);
+}
diff --git a/networking/hostname.c b/networking/hostname.c
new file mode 100644
index 0000000..48e70db
--- /dev/null
+++ b/networking/hostname.c
@@ -0,0 +1,93 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini hostname implementation for busybox
+ *
+ * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
+ *
+ * adjusted by Erik Andersen <andersen@codepoet.org> to remove
+ * use of long options and GNU getopt. Improved the usage info.
+ *
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+static void do_sethostname(char *s, int isfile)
+{
+ if (!s)
+ return;
+ if (isfile) {
+ parser_t *parser = config_open2(s, xfopen_for_read);
+ while (config_read(parser, &s, 1, 1, "# \t", PARSE_NORMAL & ~PARSE_GREEDY)) {
+ do_sethostname(s, 0);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ config_close(parser);
+ } else if (sethostname(s, strlen(s)) < 0) {
+ if (errno == EPERM)
+ bb_error_msg_and_die(bb_msg_perm_denied_are_you_root);
+ bb_perror_msg_and_die("sethostname");
+ }
+}
+
+int hostname_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int hostname_main(int argc, char **argv)
+{
+ enum {
+ OPT_d = 0x1,
+ OPT_f = 0x2,
+ OPT_i = 0x4,
+ OPT_s = 0x8,
+ OPT_F = 0x10,
+ OPT_dfis = 0xf,
+ };
+
+ char *buf;
+ char *hostname_str;
+
+ if (argc < 1)
+ bb_show_usage();
+
+ getopt32(argv, "dfisF:", &hostname_str);
+ argv += optind;
+ buf = safe_gethostname();
+
+ /* Output in desired format */
+ if (option_mask32 & OPT_dfis) {
+ struct hostent *hp;
+ char *p;
+ hp = xgethostbyname(buf);
+ p = strchr(hp->h_name, '.');
+ if (option_mask32 & OPT_f) {
+ puts(hp->h_name);
+ } else if (option_mask32 & OPT_s) {
+ if (p)
+ *p = '\0';
+ puts(hp->h_name);
+ } else if (option_mask32 & OPT_d) {
+ if (p)
+ puts(p + 1);
+ } else if (option_mask32 & OPT_i) {
+ while (hp->h_addr_list[0]) {
+ printf("%s ", inet_ntoa(*(struct in_addr *) (*hp->h_addr_list++)));
+ }
+ bb_putchar('\n');
+ }
+ }
+ /* Set the hostname */
+ else if (option_mask32 & OPT_F) {
+ do_sethostname(hostname_str, 1);
+ } else if (argv[0]) {
+ do_sethostname(argv[0], 0);
+ }
+ /* Or if all else fails,
+ * just print the current hostname */
+ else {
+ puts(buf);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(buf);
+ return EXIT_SUCCESS;
+}
diff --git a/networking/httpd.c b/networking/httpd.c
new file mode 100644
index 0000000..db8eb1e
--- /dev/null
+++ b/networking/httpd.c
@@ -0,0 +1,2432 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * httpd implementation for busybox
+ *
+ * Copyright (C) 2002,2003 Glenn Engel <glenne@engel.org>
+ * Copyright (C) 2003-2006 Vladimir Oleynik <dzo@simtreas.ru>
+ *
+ * simplify patch stolen from libbb without using strdup
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ *****************************************************************************
+ *
+ * Typical usage:
+ * for non root user
+ * httpd -p 8080 -h $HOME/public_html
+ * or for daemon start from rc script with uid=0:
+ * httpd -u www
+ * This is equivalent if www user have uid=80 to
+ * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication"
+ *
+ *
+ * When a url starts by "/cgi-bin/" it is assumed to be a cgi script. The
+ * server changes directory to the location of the script and executes it
+ * after setting QUERY_STRING and other environment variables.
+ *
+ * Doc:
+ * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html
+ *
+ * The applet can also be invoked as a url arg decoder and html text encoder
+ * as follows:
+ * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World"
+ * bar=`httpd -e "<Hello World>"` # encode as "&#60Hello&#32World&#62"
+ * Note that url encoding for arguments is not the same as html encoding for
+ * presentation. -d decodes a url-encoded argument while -e encodes in html
+ * for page display.
+ *
+ * httpd.conf has the following format:
+ *
+ * H:/serverroot # define the server root. It will override -h
+ * A:172.20. # Allow address from 172.20.0.0/16
+ * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127
+ * A:10.0.0.0/255.255.255.128 # Allow any address that previous set
+ * A:127.0.0.1 # Allow local loopback connections
+ * D:* # Deny from other IP connections
+ * E404:/path/e404.html # /path/e404.html is the 404 (not found) error page
+ * I:index.html # Show index.html when a directory is requested
+ *
+ * P:/url:[http://]hostname[:port]/new/path
+ * # When /urlXXXXXX is requested, reverse proxy
+ * # it to http://hostname[:port]/new/pathXXXXXX
+ *
+ * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/
+ * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/
+ * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/
+ * .au:audio/basic # additional mime type for audio.au files
+ * *.php:/path/php # running cgi.php scripts through an interpreter
+ *
+ * A/D may be as a/d or allow/deny - only first char matters.
+ * Deny/Allow IP logic:
+ * - Default is to allow all (Allow all (A:*) is a no-op).
+ * - Deny rules take precedence over allow rules.
+ * - "Deny all" rule (D:*) is applied last.
+ *
+ * Example:
+ * 1. Allow only specified addresses
+ * A:172.20 # Allow any address that begins with 172.20.
+ * A:10.10. # Allow any address that begins with 10.10.
+ * A:127.0.0.1 # Allow local loopback connections
+ * D:* # Deny from other IP connections
+ *
+ * 2. Only deny specified addresses
+ * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255
+ * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255
+ * A:* # (optional line added for clarity)
+ *
+ * If a sub directory contains a config file it is parsed and merged with
+ * any existing settings as if it was appended to the original configuration.
+ *
+ * subdir paths are relative to the containing subdir and thus cannot
+ * affect the parent rules.
+ *
+ * Note that since the sub dir is parsed in the forked thread servicing the
+ * subdir http request, any merge is discarded when the process exits. As a
+ * result, the subdir settings only have a lifetime of a single request.
+ *
+ * Custom error pages can contain an absolute path or be relative to
+ * 'home_httpd'. Error pages are to be static files (no CGI or script). Error
+ * page can only be defined in the root configuration file and are not taken
+ * into account in local (directories) config files.
+ *
+ * If -c is not set, an attempt will be made to open the default
+ * root configuration file. If -c is set and the file is not found, the
+ * server exits with an error.
+ *
+ */
+
+#include "libbb.h"
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+#include <sys/sendfile.h>
+#endif
+
+//#define DEBUG 1
+#define DEBUG 0
+
+#define IOBUF_SIZE 8192 /* IO buffer */
+
+/* amount of buffering in a pipe */
+#ifndef PIPE_BUF
+# define PIPE_BUF 4096
+#endif
+#if PIPE_BUF >= IOBUF_SIZE
+# error "PIPE_BUF >= IOBUF_SIZE"
+#endif
+
+#define HEADER_READ_TIMEOUT 60
+
+static const char default_path_httpd_conf[] ALIGN1 = "/etc";
+static const char httpd_conf[] ALIGN1 = "httpd.conf";
+static const char HTTP_200[] ALIGN1 = "HTTP/1.0 200 OK\r\n";
+
+typedef struct has_next_ptr {
+ struct has_next_ptr *next;
+} has_next_ptr;
+
+/* Must have "next" as a first member */
+typedef struct Htaccess {
+ struct Htaccess *next;
+ char *after_colon;
+ char before_colon[1]; /* really bigger, must be last */
+} Htaccess;
+
+/* Must have "next" as a first member */
+typedef struct Htaccess_IP {
+ struct Htaccess_IP *next;
+ unsigned ip;
+ unsigned mask;
+ int allow_deny;
+} Htaccess_IP;
+
+/* Must have "next" as a first member */
+typedef struct Htaccess_Proxy {
+ struct Htaccess_Proxy *next;
+ char *url_from;
+ char *host_port;
+ char *url_to;
+} Htaccess_Proxy;
+
+enum {
+ HTTP_OK = 200,
+ HTTP_PARTIAL_CONTENT = 206,
+ HTTP_MOVED_TEMPORARILY = 302,
+ HTTP_BAD_REQUEST = 400, /* malformed syntax */
+ HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */
+ HTTP_NOT_FOUND = 404,
+ HTTP_FORBIDDEN = 403,
+ HTTP_REQUEST_TIMEOUT = 408,
+ HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */
+ HTTP_INTERNAL_SERVER_ERROR = 500,
+ HTTP_CONTINUE = 100,
+#if 0 /* future use */
+ HTTP_SWITCHING_PROTOCOLS = 101,
+ HTTP_CREATED = 201,
+ HTTP_ACCEPTED = 202,
+ HTTP_NON_AUTHORITATIVE_INFO = 203,
+ HTTP_NO_CONTENT = 204,
+ HTTP_MULTIPLE_CHOICES = 300,
+ HTTP_MOVED_PERMANENTLY = 301,
+ HTTP_NOT_MODIFIED = 304,
+ HTTP_PAYMENT_REQUIRED = 402,
+ HTTP_BAD_GATEWAY = 502,
+ HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */
+ HTTP_RESPONSE_SETSIZE = 0xffffffff
+#endif
+};
+
+static const uint16_t http_response_type[] ALIGN2 = {
+ HTTP_OK,
+#if ENABLE_FEATURE_HTTPD_RANGES
+ HTTP_PARTIAL_CONTENT,
+#endif
+ HTTP_MOVED_TEMPORARILY,
+ HTTP_REQUEST_TIMEOUT,
+ HTTP_NOT_IMPLEMENTED,
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ HTTP_UNAUTHORIZED,
+#endif
+ HTTP_NOT_FOUND,
+ HTTP_BAD_REQUEST,
+ HTTP_FORBIDDEN,
+ HTTP_INTERNAL_SERVER_ERROR,
+#if 0 /* not implemented */
+ HTTP_CREATED,
+ HTTP_ACCEPTED,
+ HTTP_NO_CONTENT,
+ HTTP_MULTIPLE_CHOICES,
+ HTTP_MOVED_PERMANENTLY,
+ HTTP_NOT_MODIFIED,
+ HTTP_BAD_GATEWAY,
+ HTTP_SERVICE_UNAVAILABLE,
+#endif
+};
+
+static const struct {
+ const char *name;
+ const char *info;
+} http_response[ARRAY_SIZE(http_response_type)] = {
+ { "OK", NULL },
+#if ENABLE_FEATURE_HTTPD_RANGES
+ { "Partial Content", NULL },
+#endif
+ { "Found", NULL },
+ { "Request Timeout", "No request appeared within 60 seconds" },
+ { "Not Implemented", "The requested method is not recognized" },
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ { "Unauthorized", "" },
+#endif
+ { "Not Found", "The requested URL was not found" },
+ { "Bad Request", "Unsupported method" },
+ { "Forbidden", "" },
+ { "Internal Server Error", "Internal Server Error" },
+#if 0 /* not implemented */
+ { "Created" },
+ { "Accepted" },
+ { "No Content" },
+ { "Multiple Choices" },
+ { "Moved Permanently" },
+ { "Not Modified" },
+ { "Bad Gateway", "" },
+ { "Service Unavailable", "" },
+#endif
+};
+
+
+struct globals {
+ int verbose; /* must be int (used by getopt32) */
+ smallint flg_deny_all;
+
+ unsigned rmt_ip; /* used for IP-based allow/deny rules */
+ time_t last_mod;
+ char *rmt_ip_str; /* for $REMOTE_ADDR and $REMOTE_PORT */
+ const char *bind_addr_or_port;
+
+ const char *g_query;
+ const char *configFile;
+ const char *home_httpd;
+ const char *index_page;
+
+ const char *found_mime_type;
+ const char *found_moved_temporarily;
+ Htaccess_IP *ip_a_d; /* config allow/deny lines */
+
+ USE_FEATURE_HTTPD_BASIC_AUTH(const char *g_realm;)
+ USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;)
+ USE_FEATURE_HTTPD_CGI(char *referer;)
+ USE_FEATURE_HTTPD_CGI(char *user_agent;)
+ USE_FEATURE_HTTPD_CGI(char *http_accept;)
+ USE_FEATURE_HTTPD_CGI(char *http_accept_language;)
+
+ off_t file_size; /* -1 - unknown */
+#if ENABLE_FEATURE_HTTPD_RANGES
+ off_t range_start;
+ off_t range_end;
+ off_t range_len;
+#endif
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ Htaccess *g_auth; /* config user:password lines */
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+ Htaccess *mime_a; /* config mime types */
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ Htaccess *script_i; /* config script interpreters */
+#endif
+ char *iobuf; /* [IOBUF_SIZE] */
+#define hdr_buf bb_common_bufsiz1
+ char *hdr_ptr;
+ int hdr_cnt;
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+ const char *http_error_page[ARRAY_SIZE(http_response_type)];
+#endif
+#if ENABLE_FEATURE_HTTPD_PROXY
+ Htaccess_Proxy *proxy;
+#endif
+};
+#define G (*ptr_to_globals)
+#define verbose (G.verbose )
+#define flg_deny_all (G.flg_deny_all )
+#define rmt_ip (G.rmt_ip )
+#define bind_addr_or_port (G.bind_addr_or_port)
+#define g_query (G.g_query )
+#define configFile (G.configFile )
+#define home_httpd (G.home_httpd )
+#define index_page (G.index_page )
+#define found_mime_type (G.found_mime_type )
+#define found_moved_temporarily (G.found_moved_temporarily)
+#define last_mod (G.last_mod )
+#define ip_a_d (G.ip_a_d )
+#define g_realm (G.g_realm )
+#define remoteuser (G.remoteuser )
+#define referer (G.referer )
+#define user_agent (G.user_agent )
+#define http_accept (G.http_accept )
+#define http_accept_language (G.http_accept_language)
+#define file_size (G.file_size )
+#if ENABLE_FEATURE_HTTPD_RANGES
+#define range_start (G.range_start )
+#define range_end (G.range_end )
+#define range_len (G.range_len )
+#endif
+#define rmt_ip_str (G.rmt_ip_str )
+#define g_auth (G.g_auth )
+#define mime_a (G.mime_a )
+#define script_i (G.script_i )
+#define iobuf (G.iobuf )
+#define hdr_ptr (G.hdr_ptr )
+#define hdr_cnt (G.hdr_cnt )
+#define http_error_page (G.http_error_page )
+#define proxy (G.proxy )
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ USE_FEATURE_HTTPD_BASIC_AUTH(g_realm = "Web Server Authentication";) \
+ bind_addr_or_port = "80"; \
+ index_page = "index.html"; \
+ file_size = -1; \
+} while (0)
+
+#if !ENABLE_FEATURE_HTTPD_RANGES
+enum {
+ range_start = 0,
+ range_end = MAXINT(off_t) - 1,
+ range_len = MAXINT(off_t),
+};
+#endif
+
+
+#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1)
+
+/* Prototypes */
+enum {
+ SEND_HEADERS = (1 << 0),
+ SEND_BODY = (1 << 1),
+ SEND_HEADERS_AND_BODY = SEND_HEADERS + SEND_BODY,
+};
+static void send_file_and_exit(const char *url, int what) NORETURN;
+
+static void free_llist(has_next_ptr **pptr)
+{
+ has_next_ptr *cur = *pptr;
+ while (cur) {
+ has_next_ptr *t = cur;
+ cur = cur->next;
+ free(t);
+ }
+ *pptr = NULL;
+}
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+static ALWAYS_INLINE void free_Htaccess_list(Htaccess **pptr)
+{
+ free_llist((has_next_ptr**)pptr);
+}
+#endif
+
+static ALWAYS_INLINE void free_Htaccess_IP_list(Htaccess_IP **pptr)
+{
+ free_llist((has_next_ptr**)pptr);
+}
+
+/* Returns presumed mask width in bits or < 0 on error.
+ * Updates strp, stores IP at provided pointer */
+static int scan_ip(const char **strp, unsigned *ipp, unsigned char endc)
+{
+ const char *p = *strp;
+ int auto_mask = 8;
+ unsigned ip = 0;
+ int j;
+
+ if (*p == '/')
+ return -auto_mask;
+
+ for (j = 0; j < 4; j++) {
+ unsigned octet;
+
+ if ((*p < '0' || *p > '9') && *p != '/' && *p)
+ return -auto_mask;
+ octet = 0;
+ while (*p >= '0' && *p <= '9') {
+ octet *= 10;
+ octet += *p - '0';
+ if (octet > 255)
+ return -auto_mask;
+ p++;
+ }
+ if (*p == '.')
+ p++;
+ if (*p != '/' && *p)
+ auto_mask += 8;
+ ip = (ip << 8) | octet;
+ }
+ if (*p) {
+ if (*p != endc)
+ return -auto_mask;
+ p++;
+ if (*p == '\0')
+ return -auto_mask;
+ }
+ *ipp = ip;
+ *strp = p;
+ return auto_mask;
+}
+
+/* Returns 0 on success. Stores IP and mask at provided pointers */
+static int scan_ip_mask(const char *str, unsigned *ipp, unsigned *maskp)
+{
+ int i;
+ unsigned mask;
+ char *p;
+
+ i = scan_ip(&str, ipp, '/');
+ if (i < 0)
+ return i;
+
+ if (*str) {
+ /* there is /xxx after dotted-IP address */
+ i = bb_strtou(str, &p, 10);
+ if (*p == '.') {
+ /* 'xxx' itself is dotted-IP mask, parse it */
+ /* (return 0 (success) only if it has N.N.N.N form) */
+ return scan_ip(&str, maskp, '\0') - 32;
+ }
+ if (*p)
+ return -1;
+ }
+
+ if (i > 32)
+ return -1;
+
+ if (sizeof(unsigned) == 4 && i == 32) {
+ /* mask >>= 32 below may not work */
+ mask = 0;
+ } else {
+ mask = 0xffffffff;
+ mask >>= i;
+ }
+ /* i == 0 -> *maskp = 0x00000000
+ * i == 1 -> *maskp = 0x80000000
+ * i == 4 -> *maskp = 0xf0000000
+ * i == 31 -> *maskp = 0xfffffffe
+ * i == 32 -> *maskp = 0xffffffff */
+ *maskp = (uint32_t)(~mask);
+ return 0;
+}
+
+/*
+ * Parse configuration file into in-memory linked list.
+ *
+ * The first non-white character is examined to determine if the config line
+ * is one of the following:
+ * .ext:mime/type # new mime type not compiled into httpd
+ * [adAD]:from # ip address allow/deny, * for wildcard
+ * /path:user:pass # username/password
+ * Ennn:error.html # error page for status nnn
+ * P:/url:[http://]hostname[:port]/new/path # reverse proxy
+ *
+ * Any previous IP rules are discarded.
+ * If the flag argument is not SUBDIR_PARSE then all /path and mime rules
+ * are also discarded. That is, previous settings are retained if flag is
+ * SUBDIR_PARSE.
+ * Error pages are only parsed on the main config file.
+ *
+ * path Path where to look for httpd.conf (without filename).
+ * flag Type of the parse request.
+ */
+/* flag */
+#define FIRST_PARSE 0
+#define SUBDIR_PARSE 1
+#define SIGNALED_PARSE 2
+#define FIND_FROM_HTTPD_ROOT 3
+static void parse_conf(const char *path, int flag)
+{
+ FILE *f;
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ Htaccess *prev;
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ Htaccess *cur;
+#endif
+ const char *filename = configFile;
+ char buf[160];
+ char *p, *p0;
+ char *after_colon;
+ Htaccess_IP *pip;
+
+ /* discard old rules */
+ free_Htaccess_IP_list(&ip_a_d);
+ flg_deny_all = 0;
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ /* retain previous auth and mime config only for subdir parse */
+ if (flag != SUBDIR_PARSE) {
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ free_Htaccess_list(&g_auth);
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+ free_Htaccess_list(&mime_a);
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ free_Htaccess_list(&script_i);
+#endif
+ }
+#endif
+
+ if (flag == SUBDIR_PARSE || filename == NULL) {
+ filename = alloca(strlen(path) + sizeof(httpd_conf) + 2);
+ sprintf((char *)filename, "%s/%s", path, httpd_conf);
+ }
+
+ while ((f = fopen_for_read(filename)) == NULL) {
+ if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) {
+ /* config file not found, no changes to config */
+ return;
+ }
+ if (configFile && flag == FIRST_PARSE) /* if -c option given */
+ bb_simple_perror_msg_and_die(filename);
+ flag = FIND_FROM_HTTPD_ROOT;
+ filename = httpd_conf;
+ }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ prev = g_auth;
+#endif
+ /* This could stand some work */
+ while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) {
+ after_colon = NULL;
+ for (p = p0; *p0 != '\0' && *p0 != '#'; p0++) {
+ if (!isspace(*p0)) {
+ *p++ = *p0;
+ if (*p0 == ':' && after_colon == NULL)
+ after_colon = p;
+ }
+ }
+ *p = '\0';
+
+ /* test for empty or strange line */
+ if (after_colon == NULL || *after_colon == '\0')
+ continue;
+ p0 = buf;
+ if (*p0 == 'd' || *p0 == 'a')
+ *p0 -= 0x20; /* a/d -> A/D */
+ if (*after_colon == '*') {
+ if (*p0 == 'D') {
+ /* memorize "deny all" */
+ flg_deny_all = 1;
+ }
+ /* skip assumed "A:*", it is a default anyway */
+ continue;
+ }
+
+ if (*p0 == 'A' || *p0 == 'D') {
+ /* storing current config IP line */
+ pip = xzalloc(sizeof(Htaccess_IP));
+ if (scan_ip_mask(after_colon, &(pip->ip), &(pip->mask))) {
+ /* IP{/mask} syntax error detected, protect all */
+ *p0 = 'D';
+ pip->mask = 0;
+ }
+ pip->allow_deny = *p0;
+ if (*p0 == 'D') {
+ /* Deny:from_IP - prepend */
+ pip->next = ip_a_d;
+ ip_a_d = pip;
+ } else {
+ /* A:from_IP - append (thus D precedes A) */
+ Htaccess_IP *prev_IP = ip_a_d;
+ if (prev_IP == NULL) {
+ ip_a_d = pip;
+ } else {
+ while (prev_IP->next)
+ prev_IP = prev_IP->next;
+ prev_IP->next = pip;
+ }
+ }
+ continue;
+ }
+
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+ if (flag == FIRST_PARSE && *p0 == 'E') {
+ unsigned i;
+ int status = atoi(++p0); /* error status code */
+ if (status < HTTP_CONTINUE) {
+ bb_error_msg("config error '%s' in '%s'", buf, filename);
+ continue;
+ }
+ /* then error page; find matching status */
+ for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
+ if (http_response_type[i] == status) {
+ /* We chdir to home_httpd, thus no need to
+ * concat_path_file(home_httpd, after_colon)
+ * here */
+ http_error_page[i] = xstrdup(after_colon);
+ break;
+ }
+ }
+ continue;
+ }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+ if (flag == FIRST_PARSE && *p0 == 'P') {
+ /* P:/url:[http://]hostname[:port]/new/path */
+ char *url_from, *host_port, *url_to;
+ Htaccess_Proxy *proxy_entry;
+
+ url_from = after_colon;
+ host_port = strchr(after_colon, ':');
+ if (host_port == NULL) {
+ bb_error_msg("config error '%s' in '%s'", buf, filename);
+ continue;
+ }
+ *host_port++ = '\0';
+ if (strncmp(host_port, "http://", 7) == 0)
+ host_port += 7;
+ if (*host_port == '\0') {
+ bb_error_msg("config error '%s' in '%s'", buf, filename);
+ continue;
+ }
+ url_to = strchr(host_port, '/');
+ if (url_to == NULL) {
+ bb_error_msg("config error '%s' in '%s'", buf, filename);
+ continue;
+ }
+ *url_to = '\0';
+ proxy_entry = xzalloc(sizeof(Htaccess_Proxy));
+ proxy_entry->url_from = xstrdup(url_from);
+ proxy_entry->host_port = xstrdup(host_port);
+ *url_to = '/';
+ proxy_entry->url_to = xstrdup(url_to);
+ proxy_entry->next = proxy;
+ proxy = proxy_entry;
+ continue;
+ }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ if (*p0 == '/') {
+ /* make full path from httpd root / current_path / config_line_path */
+ const char *tp = (flag == SUBDIR_PARSE ? path : "");
+ p0 = xmalloc(strlen(tp) + (after_colon - buf) + 2 + strlen(after_colon));
+ after_colon[-1] = '\0';
+ sprintf(p0, "/%s%s", tp, buf);
+
+ /* looks like bb_simplify_path... */
+ tp = p = p0;
+ do {
+ if (*p == '/') {
+ if (*tp == '/') { /* skip duplicate (or initial) slash */
+ continue;
+ }
+ if (*tp == '.') {
+ if (tp[1] == '/' || tp[1] == '\0') { /* remove extra '.' */
+ continue;
+ }
+ if ((tp[1] == '.') && (tp[2] == '/' || tp[2] == '\0')) {
+ ++tp;
+ if (p > p0) {
+ while (*--p != '/') /* omit previous dir */
+ continue;
+ }
+ continue;
+ }
+ }
+ }
+ *++p = *tp;
+ } while (*++tp);
+
+ if ((p == p0) || (*p != '/')) { /* not a trailing slash */
+ ++p; /* so keep last character */
+ }
+ *p = ':';
+ strcpy(p + 1, after_colon);
+ }
+#endif
+ if (*p0 == 'I') {
+ index_page = xstrdup(after_colon);
+ continue;
+ }
+
+ /* Do not allow jumping around using H in subdir's configs */
+ if (flag == FIRST_PARSE && *p0 == 'H') {
+ home_httpd = xstrdup(after_colon);
+ xchdir(home_httpd);
+ continue;
+ }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES \
+ || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ /* storing current config line */
+ cur = xzalloc(sizeof(Htaccess) + strlen(p0));
+ strcpy(cur->before_colon, p0);
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ if (*p0 == '/') /* was malloced - see above */
+ free(p0);
+#endif
+ cur->after_colon = strchr(cur->before_colon, ':');
+ *cur->after_colon++ = '\0';
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+ if (cur->before_colon[0] == '.') {
+ /* .mime line: prepend to mime_a list */
+ cur->next = mime_a;
+ mime_a = cur;
+ continue;
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ if (cur->before_colon[0] == '*' && cur->before_colon[1] == '.') {
+ /* script interpreter line: prepend to script_i list */
+ cur->next = script_i;
+ script_i = cur;
+ continue;
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+//TODO: we do not test for leading "/"??
+//also, do we leak cur if BASIC_AUTH is off?
+ if (prev == NULL) {
+ /* first line */
+ g_auth = prev = cur;
+ } else {
+ /* sort path, if current length eq or bigger then move up */
+ Htaccess *prev_hti = g_auth;
+ size_t l = strlen(cur->before_colon);
+ Htaccess *hti;
+
+ for (hti = prev_hti; hti; hti = hti->next) {
+ if (l >= strlen(hti->before_colon)) {
+ /* insert before hti */
+ cur->next = hti;
+ if (prev_hti != hti) {
+ prev_hti->next = cur;
+ } else {
+ /* insert as top */
+ g_auth = cur;
+ }
+ break;
+ }
+ if (prev_hti != hti)
+ prev_hti = prev_hti->next;
+ }
+ if (!hti) { /* not inserted, add to bottom */
+ prev->next = cur;
+ prev = cur;
+ }
+ }
+#endif /* BASIC_AUTH */
+#endif /* BASIC_AUTH || MIME_TYPES || SCRIPT_INTERPR */
+ } /* while (fgets) */
+ fclose(f);
+}
+
+#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
+/*
+ * Given a string, html-encode special characters.
+ * This is used for the -e command line option to provide an easy way
+ * for scripts to encode result data without confusing browsers. The
+ * returned string pointer is memory allocated by malloc().
+ *
+ * Returns a pointer to the encoded string (malloced).
+ */
+static char *encodeString(const char *string)
+{
+ /* take the simple route and encode everything */
+ /* could possibly scan once to get length. */
+ int len = strlen(string);
+ char *out = xmalloc(len * 6 + 1);
+ char *p = out;
+ char ch;
+
+ while ((ch = *string++)) {
+ /* very simple check for what to encode */
+ if (isalnum(ch))
+ *p++ = ch;
+ else
+ p += sprintf(p, "&#%d;", (unsigned char) ch);
+ }
+ *p = '\0';
+ return out;
+}
+#endif /* FEATURE_HTTPD_ENCODE_URL_STR */
+
+/*
+ * Given a URL encoded string, convert it to plain ascii.
+ * Since decoding always makes strings smaller, the decode is done in-place.
+ * Thus, callers should xstrdup() the argument if they do not want the
+ * argument modified. The return is the original pointer, allowing this
+ * function to be easily used as arguments to other functions.
+ *
+ * string The first string to decode.
+ * option_d 1 if called for httpd -d
+ *
+ * Returns a pointer to the decoded string (same as input).
+ */
+static unsigned hex_to_bin(unsigned char c)
+{
+ unsigned v;
+
+ v = c - '0';
+ if (v <= 9)
+ return v;
+ /* c | 0x20: letters to lower case, non-letters
+ * to (potentially different) non-letters */
+ v = (unsigned)(c | 0x20) - 'a';
+ if (v <= 5)
+ return v + 10;
+ return ~0;
+}
+/* For testing:
+void t(char c) { printf("'%c'(%u) %u\n", c, c, hex_to_bin(c)); }
+int main() { t(0x10); t(0x20); t('0'); t('9'); t('A'); t('F'); t('a'); t('f');
+t('0'-1); t('9'+1); t('A'-1); t('F'+1); t('a'-1); t('f'+1); return 0; }
+*/
+static char *decodeString(char *orig, int option_d)
+{
+ /* note that decoded string is always shorter than original */
+ char *string = orig;
+ char *ptr = string;
+ char c;
+
+ while ((c = *ptr++) != '\0') {
+ unsigned v;
+
+ if (option_d && c == '+') {
+ *string++ = ' ';
+ continue;
+ }
+ if (c != '%') {
+ *string++ = c;
+ continue;
+ }
+ v = hex_to_bin(ptr[0]);
+ if (v > 15) {
+ bad_hex:
+ if (!option_d)
+ return NULL;
+ *string++ = '%';
+ continue;
+ }
+ v = (v * 16) | hex_to_bin(ptr[1]);
+ if (v > 255)
+ goto bad_hex;
+ if (!option_d && (v == '/' || v == '\0')) {
+ /* caller takes it as indication of invalid
+ * (dangerous wrt exploits) chars */
+ return orig + 1;
+ }
+ *string++ = v;
+ ptr += 2;
+ }
+ *string = '\0';
+ return orig;
+}
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+/*
+ * Decode a base64 data stream as per rfc1521.
+ * Note that the rfc states that non base64 chars are to be ignored.
+ * Since the decode always results in a shorter size than the input,
+ * it is OK to pass the input arg as an output arg.
+ * Parameter: a pointer to a base64 encoded string.
+ * Decoded data is stored in-place.
+ */
+static void decodeBase64(char *Data)
+{
+ const unsigned char *in = (const unsigned char *)Data;
+ /* The decoded size will be at most 3/4 the size of the encoded */
+ unsigned ch = 0;
+ int i = 0;
+
+ while (*in) {
+ int t = *in++;
+
+ if (t >= '0' && t <= '9')
+ t = t - '0' + 52;
+ else if (t >= 'A' && t <= 'Z')
+ t = t - 'A';
+ else if (t >= 'a' && t <= 'z')
+ t = t - 'a' + 26;
+ else if (t == '+')
+ t = 62;
+ else if (t == '/')
+ t = 63;
+ else if (t == '=')
+ t = 0;
+ else
+ continue;
+
+ ch = (ch << 6) | t;
+ i++;
+ if (i == 4) {
+ *Data++ = (char) (ch >> 16);
+ *Data++ = (char) (ch >> 8);
+ *Data++ = (char) ch;
+ i = 0;
+ }
+ }
+ *Data = '\0';
+}
+#endif
+
+/*
+ * Create a listen server socket on the designated port.
+ */
+static int openServer(void)
+{
+ unsigned n = bb_strtou(bind_addr_or_port, NULL, 10);
+ if (!errno && n && n <= 0xffff)
+ n = create_and_bind_stream_or_die(NULL, n);
+ else
+ n = create_and_bind_stream_or_die(bind_addr_or_port, 80);
+ xlisten(n, 9);
+ return n;
+}
+
+/*
+ * Log the connection closure and exit.
+ */
+static void log_and_exit(void) NORETURN;
+static void log_and_exit(void)
+{
+ /* Paranoia. IE said to be buggy. It may send some extra data
+ * or be confused by us just exiting without SHUT_WR. Oh well. */
+ shutdown(1, SHUT_WR);
+ /* Why??
+ (this also messes up stdin when user runs httpd -i from terminal)
+ ndelay_on(0);
+ while (read(STDIN_FILENO, iobuf, IOBUF_SIZE) > 0)
+ continue;
+ */
+
+ if (verbose > 2)
+ bb_error_msg("closed");
+ _exit(xfunc_error_retval);
+}
+
+/*
+ * Create and send HTTP response headers.
+ * The arguments are combined and sent as one write operation. Note that
+ * IE will puke big-time if the headers are not sent in one packet and the
+ * second packet is delayed for any reason.
+ * responseNum - the result code to send.
+ */
+static void send_headers(int responseNum)
+{
+ static const char RFC1123FMT[] ALIGN1 = "%a, %d %b %Y %H:%M:%S GMT";
+
+ const char *responseString = "";
+ const char *infoString = NULL;
+ const char *mime_type;
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+ const char *error_page = NULL;
+#endif
+ unsigned i;
+ time_t timer = time(0);
+ char tmp_str[80];
+ int len;
+
+ for (i = 0; i < ARRAY_SIZE(http_response_type); i++) {
+ if (http_response_type[i] == responseNum) {
+ responseString = http_response[i].name;
+ infoString = http_response[i].info;
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+ error_page = http_error_page[i];
+#endif
+ break;
+ }
+ }
+ /* error message is HTML */
+ mime_type = responseNum == HTTP_OK ?
+ found_mime_type : "text/html";
+
+ if (verbose)
+ bb_error_msg("response:%u", responseNum);
+
+ /* emit the current date */
+ strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&timer));
+ len = sprintf(iobuf,
+ "HTTP/1.0 %d %s\r\nContent-type: %s\r\n"
+ "Date: %s\r\nConnection: close\r\n",
+ responseNum, responseString, mime_type, tmp_str);
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ if (responseNum == HTTP_UNAUTHORIZED) {
+ len += sprintf(iobuf + len,
+ "WWW-Authenticate: Basic realm=\"%s\"\r\n",
+ g_realm);
+ }
+#endif
+ if (responseNum == HTTP_MOVED_TEMPORARILY) {
+ len += sprintf(iobuf + len, "Location: %s/%s%s\r\n",
+ found_moved_temporarily,
+ (g_query ? "?" : ""),
+ (g_query ? g_query : ""));
+ }
+
+#if ENABLE_FEATURE_HTTPD_ERROR_PAGES
+ if (error_page && access(error_page, R_OK) == 0) {
+ strcat(iobuf, "\r\n");
+ len += 2;
+
+ if (DEBUG)
+ fprintf(stderr, "headers: '%s'\n", iobuf);
+ full_write(STDOUT_FILENO, iobuf, len);
+ if (DEBUG)
+ fprintf(stderr, "writing error page: '%s'\n", error_page);
+ return send_file_and_exit(error_page, SEND_BODY);
+ }
+#endif
+
+ if (file_size != -1) { /* file */
+ strftime(tmp_str, sizeof(tmp_str), RFC1123FMT, gmtime(&last_mod));
+#if ENABLE_FEATURE_HTTPD_RANGES
+ if (responseNum == HTTP_PARTIAL_CONTENT) {
+ len += sprintf(iobuf + len, "Content-Range: bytes %"OFF_FMT"d-%"OFF_FMT"d/%"OFF_FMT"d\r\n",
+ range_start,
+ range_end,
+ file_size);
+ file_size = range_end - range_start + 1;
+ }
+#endif
+ len += sprintf(iobuf + len,
+#if ENABLE_FEATURE_HTTPD_RANGES
+ "Accept-Ranges: bytes\r\n"
+#endif
+ "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n",
+ tmp_str,
+ "Content-length:",
+ file_size
+ );
+ }
+ iobuf[len++] = '\r';
+ iobuf[len++] = '\n';
+ if (infoString) {
+ len += sprintf(iobuf + len,
+ "<HTML><HEAD><TITLE>%d %s</TITLE></HEAD>\n"
+ "<BODY><H1>%d %s</H1>\n%s\n</BODY></HTML>\n",
+ responseNum, responseString,
+ responseNum, responseString, infoString);
+ }
+ if (DEBUG)
+ fprintf(stderr, "headers: '%s'\n", iobuf);
+ if (full_write(STDOUT_FILENO, iobuf, len) != len) {
+ if (verbose > 1)
+ bb_perror_msg("error");
+ log_and_exit();
+ }
+}
+
+static void send_headers_and_exit(int responseNum) NORETURN;
+static void send_headers_and_exit(int responseNum)
+{
+ send_headers(responseNum);
+ log_and_exit();
+}
+
+/*
+ * Read from the socket until '\n' or EOF. '\r' chars are removed.
+ * '\n' is replaced with NUL.
+ * Return number of characters read or 0 if nothing is read
+ * ('\r' and '\n' are not counted).
+ * Data is returned in iobuf.
+ */
+static int get_line(void)
+{
+ int count = 0;
+ char c;
+
+ while (1) {
+ if (hdr_cnt <= 0) {
+ hdr_cnt = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
+ if (hdr_cnt <= 0)
+ break;
+ hdr_ptr = hdr_buf;
+ }
+ iobuf[count] = c = *hdr_ptr++;
+ hdr_cnt--;
+
+ if (c == '\r')
+ continue;
+ if (c == '\n') {
+ iobuf[count] = '\0';
+ return count;
+ }
+ if (count < (IOBUF_SIZE - 1)) /* check overflow */
+ count++;
+ }
+ return count;
+}
+
+#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
+
+/* gcc 4.2.1 fares better with NOINLINE */
+static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len) NORETURN;
+static NOINLINE void cgi_io_loop_and_exit(int fromCgi_rd, int toCgi_wr, int post_len)
+{
+ enum { FROM_CGI = 1, TO_CGI = 2 }; /* indexes in pfd[] */
+ struct pollfd pfd[3];
+ int out_cnt; /* we buffer a bit of initial CGI output */
+ int count;
+
+ /* iobuf is used for CGI -> network data,
+ * hdr_buf is for network -> CGI data (POSTDATA) */
+
+ /* If CGI dies, we still want to correctly finish reading its output
+ * and send it to the peer. So please no SIGPIPEs! */
+ signal(SIGPIPE, SIG_IGN);
+
+ // We inconsistently handle a case when more POSTDATA from network
+ // is coming than we expected. We may give *some part* of that
+ // extra data to CGI.
+
+ //if (hdr_cnt > post_len) {
+ // /* We got more POSTDATA from network than we expected */
+ // hdr_cnt = post_len;
+ //}
+ post_len -= hdr_cnt;
+ /* post_len - number of POST bytes not yet read from network */
+
+ /* NB: breaking out of this loop jumps to log_and_exit() */
+ out_cnt = 0;
+ while (1) {
+ memset(pfd, 0, sizeof(pfd));
+
+ pfd[FROM_CGI].fd = fromCgi_rd;
+ pfd[FROM_CGI].events = POLLIN;
+
+ if (toCgi_wr) {
+ pfd[TO_CGI].fd = toCgi_wr;
+ if (hdr_cnt > 0) {
+ pfd[TO_CGI].events = POLLOUT;
+ } else if (post_len > 0) {
+ pfd[0].events = POLLIN;
+ } else {
+ /* post_len <= 0 && hdr_cnt <= 0:
+ * no more POST data to CGI,
+ * let CGI see EOF on CGI's stdin */
+ close(toCgi_wr);
+ toCgi_wr = 0;
+ }
+ }
+
+ /* Now wait on the set of sockets */
+ count = safe_poll(pfd, 3, -1);
+ if (count <= 0) {
+#if 0
+ if (safe_waitpid(pid, &status, WNOHANG) <= 0) {
+ /* Weird. CGI didn't exit and no fd's
+ * are ready, yet poll returned?! */
+ continue;
+ }
+ if (DEBUG && WIFEXITED(status))
+ bb_error_msg("CGI exited, status=%d", WEXITSTATUS(status));
+ if (DEBUG && WIFSIGNALED(status))
+ bb_error_msg("CGI killed, signal=%d", WTERMSIG(status));
+#endif
+ break;
+ }
+
+ if (pfd[TO_CGI].revents) {
+ /* hdr_cnt > 0 here due to the way pfd[TO_CGI].events set */
+ /* Have data from peer and can write to CGI */
+ count = safe_write(toCgi_wr, hdr_ptr, hdr_cnt);
+ /* Doesn't happen, we dont use nonblocking IO here
+ *if (count < 0 && errno == EAGAIN) {
+ * ...
+ *} else */
+ if (count > 0) {
+ hdr_ptr += count;
+ hdr_cnt -= count;
+ } else {
+ /* EOF/broken pipe to CGI, stop piping POST data */
+ hdr_cnt = post_len = 0;
+ }
+ }
+
+ if (pfd[0].revents) {
+ /* post_len > 0 && hdr_cnt == 0 here */
+ /* We expect data, prev data portion is eaten by CGI
+ * and there *is* data to read from the peer
+ * (POSTDATA) */
+ //count = post_len > (int)sizeof(hdr_buf) ? (int)sizeof(hdr_buf) : post_len;
+ //count = safe_read(STDIN_FILENO, hdr_buf, count);
+ count = safe_read(STDIN_FILENO, hdr_buf, sizeof(hdr_buf));
+ if (count > 0) {
+ hdr_cnt = count;
+ hdr_ptr = hdr_buf;
+ post_len -= count;
+ } else {
+ /* no more POST data can be read */
+ post_len = 0;
+ }
+ }
+
+ if (pfd[FROM_CGI].revents) {
+ /* There is something to read from CGI */
+ char *rbuf = iobuf;
+
+ /* Are we still buffering CGI output? */
+ if (out_cnt >= 0) {
+ /* HTTP_200[] has single "\r\n" at the end.
+ * According to http://hoohoo.ncsa.uiuc.edu/cgi/out.html,
+ * CGI scripts MUST send their own header terminated by
+ * empty line, then data. That's why we have only one
+ * <cr><lf> pair here. We will output "200 OK" line
+ * if needed, but CGI still has to provide blank line
+ * between header and body */
+
+ /* Must use safe_read, not full_read, because
+ * CGI may output a few first bytes and then wait
+ * for POSTDATA without closing stdout.
+ * With full_read we may wait here forever. */
+ count = safe_read(fromCgi_rd, rbuf + out_cnt, PIPE_BUF - 8);
+ if (count <= 0) {
+ /* eof (or error) and there was no "HTTP",
+ * so write it, then write received data */
+ if (out_cnt) {
+ full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1);
+ full_write(STDOUT_FILENO, rbuf, out_cnt);
+ }
+ break; /* CGI stdout is closed, exiting */
+ }
+ out_cnt += count;
+ count = 0;
+ /* "Status" header format is: "Status: 302 Redirected\r\n" */
+ if (out_cnt >= 8 && memcmp(rbuf, "Status: ", 8) == 0) {
+ /* send "HTTP/1.0 " */
+ if (full_write(STDOUT_FILENO, HTTP_200, 9) != 9)
+ break;
+ rbuf += 8; /* skip "Status: " */
+ count = out_cnt - 8;
+ out_cnt = -1; /* buffering off */
+ } else if (out_cnt >= 4) {
+ /* Did CGI add "HTTP"? */
+ if (memcmp(rbuf, HTTP_200, 4) != 0) {
+ /* there is no "HTTP", do it ourself */
+ if (full_write(STDOUT_FILENO, HTTP_200, sizeof(HTTP_200)-1) != sizeof(HTTP_200)-1)
+ break;
+ }
+ /* Commented out:
+ if (!strstr(rbuf, "ontent-")) {
+ full_write(s, "Content-type: text/plain\r\n\r\n", 28);
+ }
+ * Counter-example of valid CGI without Content-type:
+ * echo -en "HTTP/1.0 302 Found\r\n"
+ * echo -en "Location: http://www.busybox.net\r\n"
+ * echo -en "\r\n"
+ */
+ count = out_cnt;
+ out_cnt = -1; /* buffering off */
+ }
+ } else {
+ count = safe_read(fromCgi_rd, rbuf, PIPE_BUF);
+ if (count <= 0)
+ break; /* eof (or error) */
+ }
+ if (full_write(STDOUT_FILENO, rbuf, count) != count)
+ break;
+ if (DEBUG)
+ fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf);
+ } /* if (pfd[FROM_CGI].revents) */
+ } /* while (1) */
+ log_and_exit();
+}
+#endif
+
+#if ENABLE_FEATURE_HTTPD_CGI
+
+static void setenv1(const char *name, const char *value)
+{
+ setenv(name, value ? value : "", 1);
+}
+
+/*
+ * Spawn CGI script, forward CGI's stdin/out <=> network
+ *
+ * Environment variables are set up and the script is invoked with pipes
+ * for stdin/stdout. If a POST is being done the script is fed the POST
+ * data in addition to setting the QUERY_STRING variable (for GETs or POSTs).
+ *
+ * Parameters:
+ * const char *url The requested URL (with leading /).
+ * int post_len Length of the POST body.
+ * const char *cookie For set HTTP_COOKIE.
+ * const char *content_type For set CONTENT_TYPE.
+ */
+static void send_cgi_and_exit(
+ const char *url,
+ const char *request,
+ int post_len,
+ const char *cookie,
+ const char *content_type) NORETURN;
+static void send_cgi_and_exit(
+ const char *url,
+ const char *request,
+ int post_len,
+ const char *cookie,
+ const char *content_type)
+{
+ struct fd_pair fromCgi; /* CGI -> httpd pipe */
+ struct fd_pair toCgi; /* httpd -> CGI pipe */
+ char *script;
+ int pid;
+
+ /* Make a copy. NB: caller guarantees:
+ * url[0] == '/', url[1] != '/' */
+ url = xstrdup(url);
+
+ /*
+ * We are mucking with environment _first_ and then vfork/exec,
+ * this allows us to use vfork safely. Parent doesn't care about
+ * these environment changes anyway.
+ */
+
+ /* Check for [dirs/]script.cgi/PATH_INFO */
+ script = (char*)url;
+ while ((script = strchr(script + 1, '/')) != NULL) {
+ struct stat sb;
+
+ *script = '\0';
+ if (!is_directory(url + 1, 1, &sb)) {
+ /* not directory, found script.cgi/PATH_INFO */
+ *script = '/';
+ break;
+ }
+ *script = '/'; /* is directory, find next '/' */
+ }
+ setenv1("PATH_INFO", script); /* set to /PATH_INFO or "" */
+ setenv1("REQUEST_METHOD", request);
+ if (g_query) {
+ putenv(xasprintf("%s=%s?%s", "REQUEST_URI", url, g_query));
+ } else {
+ setenv1("REQUEST_URI", url);
+ }
+ if (script != NULL)
+ *script = '\0'; /* cut off /PATH_INFO */
+
+ /* SCRIPT_FILENAME is required by PHP in CGI mode */
+ if (home_httpd[0] == '/') {
+ char *fullpath = concat_path_file(home_httpd, url);
+ setenv1("SCRIPT_FILENAME", fullpath);
+ }
+ /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */
+ setenv1("SCRIPT_NAME", url);
+ /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html:
+ * QUERY_STRING: The information which follows the ? in the URL
+ * which referenced this script. This is the query information.
+ * It should not be decoded in any fashion. This variable
+ * should always be set when there is query information,
+ * regardless of command line decoding. */
+ /* (Older versions of bbox seem to do some decoding) */
+ setenv1("QUERY_STRING", g_query);
+ putenv((char*)"SERVER_SOFTWARE=busybox httpd/"BB_VER);
+ putenv((char*)"SERVER_PROTOCOL=HTTP/1.0");
+ putenv((char*)"GATEWAY_INTERFACE=CGI/1.1");
+ /* Having _separate_ variables for IP and port defeats
+ * the purpose of having socket abstraction. Which "port"
+ * are you using on Unix domain socket?
+ * IOW - REMOTE_PEER="1.2.3.4:56" makes much more sense.
+ * Oh well... */
+ {
+ char *p = rmt_ip_str ? rmt_ip_str : (char*)"";
+ char *cp = strrchr(p, ':');
+ if (ENABLE_FEATURE_IPV6 && cp && strchr(cp, ']'))
+ cp = NULL;
+ if (cp) *cp = '\0'; /* delete :PORT */
+ setenv1("REMOTE_ADDR", p);
+ if (cp) {
+ *cp = ':';
+#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV
+ setenv1("REMOTE_PORT", cp + 1);
+#endif
+ }
+ }
+ setenv1("HTTP_USER_AGENT", user_agent);
+ if (http_accept)
+ setenv1("HTTP_ACCEPT", http_accept);
+ if (http_accept_language)
+ setenv1("HTTP_ACCEPT_LANGUAGE", http_accept_language);
+ if (post_len)
+ putenv(xasprintf("CONTENT_LENGTH=%d", post_len));
+ if (cookie)
+ setenv1("HTTP_COOKIE", cookie);
+ if (content_type)
+ setenv1("CONTENT_TYPE", content_type);
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ if (remoteuser) {
+ setenv1("REMOTE_USER", remoteuser);
+ putenv((char*)"AUTH_TYPE=Basic");
+ }
+#endif
+ if (referer)
+ setenv1("HTTP_REFERER", referer);
+
+ xpiped_pair(fromCgi);
+ xpiped_pair(toCgi);
+
+ pid = vfork();
+ if (pid < 0) {
+ /* TODO: log perror? */
+ log_and_exit();
+ }
+
+ if (!pid) {
+ /* Child process */
+ char *argv[3];
+
+ xfunc_error_retval = 242;
+
+ /* NB: close _first_, then move fds! */
+ close(toCgi.wr);
+ close(fromCgi.rd);
+ xmove_fd(toCgi.rd, 0); /* replace stdin with the pipe */
+ xmove_fd(fromCgi.wr, 1); /* replace stdout with the pipe */
+ /* User seeing stderr output can be a security problem.
+ * If CGI really wants that, it can always do dup itself. */
+ /* dup2(1, 2); */
+
+ /* Chdiring to script's dir */
+ script = strrchr(url, '/');
+ if (script != url) { /* paranoia */
+ *script = '\0';
+ if (chdir(url + 1) != 0) {
+ bb_perror_msg("chdir %s", url + 1);
+ goto error_execing_cgi;
+ }
+ // not needed: *script = '/';
+ }
+ script++;
+
+ /* set argv[0] to name without path */
+ argv[0] = script;
+ argv[1] = NULL;
+
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ {
+ char *suffix = strrchr(script, '.');
+
+ if (suffix) {
+ Htaccess *cur;
+ for (cur = script_i; cur; cur = cur->next) {
+ if (strcmp(cur->before_colon + 1, suffix) == 0) {
+ /* found interpreter name */
+ argv[0] = cur->after_colon;
+ argv[1] = script;
+ argv[2] = NULL;
+ break;
+ }
+ }
+ }
+ }
+#endif
+ /* restore default signal dispositions for CGI process */
+ bb_signals(0
+ | (1 << SIGCHLD)
+ | (1 << SIGPIPE)
+ | (1 << SIGHUP)
+ , SIG_DFL);
+
+ /* _NOT_ execvp. We do not search PATH. argv[0] is a filename
+ * without any dir components and will only match a file
+ * in the current directory */
+ execv(argv[0], argv);
+ if (verbose)
+ bb_perror_msg("exec %s", argv[0]);
+ error_execing_cgi:
+ /* send to stdout
+ * (we are CGI here, our stdout is pumped to the net) */
+ send_headers_and_exit(HTTP_NOT_FOUND);
+ } /* end child */
+
+ /* Parent process */
+
+ /* Restore variables possibly changed by child */
+ xfunc_error_retval = 0;
+
+ /* Pump data */
+ close(fromCgi.wr);
+ close(toCgi.rd);
+ cgi_io_loop_and_exit(fromCgi.rd, toCgi.wr, post_len);
+}
+
+#endif /* FEATURE_HTTPD_CGI */
+
+/*
+ * Send a file response to a HTTP request, and exit
+ *
+ * Parameters:
+ * const char *url The requested URL (with leading /).
+ * what What to send (headers/body/both).
+ */
+static void send_file_and_exit(const char *url, int what)
+{
+ static const char *const suffixTable[] = {
+ /* Warning: shorter equivalent suffix in one line must be first */
+ ".htm.html", "text/html",
+ ".jpg.jpeg", "image/jpeg",
+ ".gif", "image/gif",
+ ".png", "image/png",
+ ".txt.h.c.cc.cpp", "text/plain",
+ ".css", "text/css",
+ ".wav", "audio/wav",
+ ".avi", "video/x-msvideo",
+ ".qt.mov", "video/quicktime",
+ ".mpe.mpeg", "video/mpeg",
+ ".mid.midi", "audio/midi",
+ ".mp3", "audio/mpeg",
+#if 0 /* unpopular */
+ ".au", "audio/basic",
+ ".pac", "application/x-ns-proxy-autoconfig",
+ ".vrml.wrl", "model/vrml",
+#endif
+ NULL
+ };
+
+ char *suffix;
+ int f;
+ const char *const *table;
+ const char *try_suffix;
+ ssize_t count;
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+ off_t offset;
+#endif
+
+ /* If you want to know about EPIPE below
+ * (happens if you abort downloads from local httpd): */
+ signal(SIGPIPE, SIG_IGN);
+
+ suffix = strrchr(url, '.');
+
+ /* If not found, set default as "application/octet-stream"; */
+ found_mime_type = "application/octet-stream";
+ if (suffix) {
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+ Htaccess *cur;
+#endif
+ for (table = suffixTable; *table; table += 2) {
+ try_suffix = strstr(table[0], suffix);
+ if (try_suffix) {
+ try_suffix += strlen(suffix);
+ if (*try_suffix == '\0' || *try_suffix == '.') {
+ found_mime_type = table[1];
+ break;
+ }
+ }
+ }
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES
+ for (cur = mime_a; cur; cur = cur->next) {
+ if (strcmp(cur->before_colon, suffix) == 0) {
+ found_mime_type = cur->after_colon;
+ break;
+ }
+ }
+#endif
+ }
+
+ if (DEBUG)
+ bb_error_msg("sending file '%s' content-type: %s",
+ url, found_mime_type);
+
+ f = open(url, O_RDONLY);
+ if (f < 0) {
+ if (DEBUG)
+ bb_perror_msg("can't open '%s'", url);
+ /* Error pages are sent by using send_file_and_exit(SEND_BODY).
+ * IOW: it is unsafe to call send_headers_and_exit
+ * if what is SEND_BODY! Can recurse! */
+ if (what != SEND_BODY)
+ send_headers_and_exit(HTTP_NOT_FOUND);
+ log_and_exit();
+ }
+#if ENABLE_FEATURE_HTTPD_RANGES
+ if (what == SEND_BODY)
+ range_start = 0; /* err pages and ranges don't mix */
+ range_len = MAXINT(off_t);
+ if (range_start) {
+ if (!range_end) {
+ range_end = file_size - 1;
+ }
+ if (range_end < range_start
+ || lseek(f, range_start, SEEK_SET) != range_start
+ ) {
+ lseek(f, 0, SEEK_SET);
+ range_start = 0;
+ } else {
+ range_len = range_end - range_start + 1;
+ send_headers(HTTP_PARTIAL_CONTENT);
+ what = SEND_BODY;
+ }
+ }
+#endif
+
+ if (what & SEND_HEADERS)
+ send_headers(HTTP_OK);
+
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+ offset = range_start;
+ do {
+ /* sz is rounded down to 64k */
+ ssize_t sz = MAXINT(ssize_t) - 0xffff;
+ USE_FEATURE_HTTPD_RANGES(if (sz > range_len) sz = range_len;)
+ count = sendfile(1, f, &offset, sz);
+ if (count < 0) {
+ if (offset == range_start)
+ goto fallback;
+ goto fin;
+ }
+ USE_FEATURE_HTTPD_RANGES(range_len -= sz;)
+ } while (count > 0 && range_len);
+ log_and_exit();
+
+ fallback:
+#endif
+ while ((count = safe_read(f, iobuf, IOBUF_SIZE)) > 0) {
+ ssize_t n;
+ USE_FEATURE_HTTPD_RANGES(if (count > range_len) count = range_len;)
+ n = full_write(STDOUT_FILENO, iobuf, count);
+ if (count != n)
+ break;
+ USE_FEATURE_HTTPD_RANGES(range_len -= count;)
+ if (!range_len)
+ break;
+ }
+#if ENABLE_FEATURE_HTTPD_USE_SENDFILE
+ fin:
+#endif
+ if (count < 0 && verbose > 1)
+ bb_perror_msg("error");
+ log_and_exit();
+}
+
+static int checkPermIP(void)
+{
+ Htaccess_IP *cur;
+
+ for (cur = ip_a_d; cur; cur = cur->next) {
+#if DEBUG
+ fprintf(stderr,
+ "checkPermIP: '%s' ? '%u.%u.%u.%u/%u.%u.%u.%u'\n",
+ rmt_ip_str,
+ (unsigned char)(cur->ip >> 24),
+ (unsigned char)(cur->ip >> 16),
+ (unsigned char)(cur->ip >> 8),
+ (unsigned char)(cur->ip),
+ (unsigned char)(cur->mask >> 24),
+ (unsigned char)(cur->mask >> 16),
+ (unsigned char)(cur->mask >> 8),
+ (unsigned char)(cur->mask)
+ );
+#endif
+ if ((rmt_ip & cur->mask) == cur->ip)
+ return (cur->allow_deny == 'A'); /* A -> 1 */
+ }
+
+ return !flg_deny_all; /* depends on whether we saw "D:*" */
+}
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+/*
+ * Config file entries are of the form "/<path>:<user>:<passwd>".
+ * If config file has no prefix match for path, access is allowed.
+ *
+ * path The file path
+ * user_and_passwd "user:passwd" to validate
+ *
+ * Returns 1 if user_and_passwd is OK.
+ */
+static int check_user_passwd(const char *path, const char *user_and_passwd)
+{
+ Htaccess *cur;
+ const char *prev = NULL;
+
+ for (cur = g_auth; cur; cur = cur->next) {
+ const char *dir_prefix;
+ size_t len;
+
+ dir_prefix = cur->before_colon;
+
+ /* WHY? */
+ /* If already saw a match, don't accept other different matches */
+ if (prev && strcmp(prev, dir_prefix) != 0)
+ continue;
+
+ if (DEBUG)
+ fprintf(stderr, "checkPerm: '%s' ? '%s'\n", dir_prefix, user_and_passwd);
+
+ /* If it's not a prefix match, continue searching */
+ len = strlen(dir_prefix);
+ if (len != 1 /* dir_prefix "/" matches all, don't need to check */
+ && (strncmp(dir_prefix, path, len) != 0
+ || (path[len] != '/' && path[len] != '\0'))
+ ) {
+ continue;
+ }
+
+ /* Path match found */
+ prev = dir_prefix;
+
+ if (ENABLE_FEATURE_HTTPD_AUTH_MD5) {
+ char *md5_passwd;
+
+ md5_passwd = strchr(cur->after_colon, ':');
+ if (md5_passwd && md5_passwd[1] == '$' && md5_passwd[2] == '1'
+ && md5_passwd[3] == '$' && md5_passwd[4]
+ ) {
+ char *encrypted;
+ int r, user_len_p1;
+
+ md5_passwd++;
+ user_len_p1 = md5_passwd - cur->after_colon;
+ /* comparing "user:" */
+ if (strncmp(cur->after_colon, user_and_passwd, user_len_p1) != 0) {
+ continue;
+ }
+
+ encrypted = pw_encrypt(
+ user_and_passwd + user_len_p1 /* cleartext pwd from user */,
+ md5_passwd /*salt */, 1 /* cleanup */);
+ r = strcmp(encrypted, md5_passwd);
+ free(encrypted);
+ if (r == 0)
+ goto set_remoteuser_var; /* Ok */
+ continue;
+ }
+ }
+
+ /* Comparing plaintext "user:pass" in one go */
+ if (strcmp(cur->after_colon, user_and_passwd) == 0) {
+ set_remoteuser_var:
+ remoteuser = xstrndup(user_and_passwd,
+ strchrnul(user_and_passwd, ':') - user_and_passwd);
+ return 1; /* Ok */
+ }
+ } /* for */
+
+ /* 0(bad) if prev is set: matches were found but passwd was wrong */
+ return (prev == NULL);
+}
+#endif /* FEATURE_HTTPD_BASIC_AUTH */
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+static Htaccess_Proxy *find_proxy_entry(const char *url)
+{
+ Htaccess_Proxy *p;
+ for (p = proxy; p; p = p->next) {
+ if (strncmp(url, p->url_from, strlen(p->url_from)) == 0)
+ return p;
+ }
+ return NULL;
+}
+#endif
+
+/*
+ * Handle timeouts
+ */
+static void exit_on_signal(int sig) NORETURN;
+static void exit_on_signal(int sig UNUSED_PARAM)
+{
+ send_headers_and_exit(HTTP_REQUEST_TIMEOUT);
+}
+
+/*
+ * Handle an incoming http request and exit.
+ */
+static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr) NORETURN;
+static void handle_incoming_and_exit(const len_and_sockaddr *fromAddr)
+{
+ static const char request_GET[] ALIGN1 = "GET";
+ struct stat sb;
+ char *urlcopy;
+ char *urlp;
+ char *tptr;
+#if ENABLE_FEATURE_HTTPD_CGI
+ static const char request_HEAD[] ALIGN1 = "HEAD";
+ const char *prequest;
+ char *cookie = NULL;
+ char *content_type = NULL;
+ unsigned long length = 0;
+#elif ENABLE_FEATURE_HTTPD_PROXY
+#define prequest request_GET
+ unsigned long length = 0;
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ smallint authorized = -1;
+#endif
+ smallint ip_allowed;
+ char http_major_version;
+#if ENABLE_FEATURE_HTTPD_PROXY
+ char http_minor_version;
+ char *header_buf = header_buf; /* for gcc */
+ char *header_ptr = header_ptr;
+ Htaccess_Proxy *proxy_entry;
+#endif
+
+ /* Allocation of iobuf is postponed until now
+ * (IOW, server process doesn't need to waste 8k) */
+ iobuf = xmalloc(IOBUF_SIZE);
+
+ rmt_ip = 0;
+ if (fromAddr->u.sa.sa_family == AF_INET) {
+ rmt_ip = ntohl(fromAddr->u.sin.sin_addr.s_addr);
+ }
+#if ENABLE_FEATURE_IPV6
+ if (fromAddr->u.sa.sa_family == AF_INET6
+ && fromAddr->u.sin6.sin6_addr.s6_addr32[0] == 0
+ && fromAddr->u.sin6.sin6_addr.s6_addr32[1] == 0
+ && ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[2]) == 0xffff)
+ rmt_ip = ntohl(fromAddr->u.sin6.sin6_addr.s6_addr32[3]);
+#endif
+ if (ENABLE_FEATURE_HTTPD_CGI || DEBUG || verbose) {
+ /* NB: can be NULL (user runs httpd -i by hand?) */
+ rmt_ip_str = xmalloc_sockaddr2dotted(&fromAddr->u.sa);
+ }
+ if (verbose) {
+ /* this trick makes -v logging much simpler */
+ if (rmt_ip_str)
+ applet_name = rmt_ip_str;
+ if (verbose > 2)
+ bb_error_msg("connected");
+ }
+
+ /* Install timeout handler */
+ signal_no_SA_RESTART_empty_mask(SIGALRM, exit_on_signal);
+ alarm(HEADER_READ_TIMEOUT);
+
+ if (!get_line()) /* EOF or error or empty line */
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+
+ /* Determine type of request (GET/POST) */
+ urlp = strpbrk(iobuf, " \t");
+ if (urlp == NULL)
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+ *urlp++ = '\0';
+#if ENABLE_FEATURE_HTTPD_CGI
+ prequest = request_GET;
+ if (strcasecmp(iobuf, prequest) != 0) {
+ prequest = request_HEAD;
+ if (strcasecmp(iobuf, prequest) != 0) {
+ prequest = "POST";
+ if (strcasecmp(iobuf, prequest) != 0)
+ send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+ }
+ }
+#else
+ if (strcasecmp(iobuf, request_GET) != 0)
+ send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+#endif
+ urlp = skip_whitespace(urlp);
+ if (urlp[0] != '/')
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+
+ /* Find end of URL and parse HTTP version, if any */
+ http_major_version = '0';
+ USE_FEATURE_HTTPD_PROXY(http_minor_version = '0';)
+ tptr = strchrnul(urlp, ' ');
+ /* Is it " HTTP/"? */
+ if (tptr[0] && strncmp(tptr + 1, HTTP_200, 5) == 0) {
+ http_major_version = tptr[6];
+ USE_FEATURE_HTTPD_PROXY(http_minor_version = tptr[8];)
+ }
+ *tptr = '\0';
+
+ /* Copy URL from after "GET "/"POST " to stack-allocated char[] */
+ urlcopy = alloca((tptr - urlp) + 2 + strlen(index_page));
+ /*if (urlcopy == NULL)
+ * send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);*/
+ strcpy(urlcopy, urlp);
+ /* NB: urlcopy ptr is never changed after this */
+
+ /* Extract url args if present */
+ g_query = NULL;
+ tptr = strchr(urlcopy, '?');
+ if (tptr) {
+ *tptr++ = '\0';
+ g_query = tptr;
+ }
+
+ /* Decode URL escape sequences */
+ tptr = decodeString(urlcopy, 0);
+ if (tptr == NULL)
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+ if (tptr == urlcopy + 1) {
+ /* '/' or NUL is encoded */
+ send_headers_and_exit(HTTP_NOT_FOUND);
+ }
+
+ /* Canonicalize path */
+ /* Algorithm stolen from libbb bb_simplify_path(),
+ * but don't strdup, retain trailing slash, protect root */
+ urlp = tptr = urlcopy;
+ do {
+ if (*urlp == '/') {
+ /* skip duplicate (or initial) slash */
+ if (*tptr == '/') {
+ continue;
+ }
+ if (*tptr == '.') {
+ /* skip extra "/./" */
+ if (tptr[1] == '/' || !tptr[1]) {
+ continue;
+ }
+ /* "..": be careful */
+ if (tptr[1] == '.' && (tptr[2] == '/' || !tptr[2])) {
+ ++tptr;
+ if (urlp == urlcopy) /* protect root */
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+ while (*--urlp != '/') /* omit previous dir */;
+ continue;
+ }
+ }
+ }
+ *++urlp = *tptr;
+ } while (*++tptr);
+ *++urlp = '\0'; /* terminate after last character */
+
+ /* If URL is a directory, add '/' */
+ if (urlp[-1] != '/') {
+ if (is_directory(urlcopy + 1, 1, &sb)) {
+ found_moved_temporarily = urlcopy;
+ }
+ }
+
+ /* Log it */
+ if (verbose > 1)
+ bb_error_msg("url:%s", urlcopy);
+
+ tptr = urlcopy;
+ ip_allowed = checkPermIP();
+ while (ip_allowed && (tptr = strchr(tptr + 1, '/')) != NULL) {
+ /* have path1/path2 */
+ *tptr = '\0';
+ if (is_directory(urlcopy + 1, 1, &sb)) {
+ /* may have subdir config */
+ parse_conf(urlcopy + 1, SUBDIR_PARSE);
+ ip_allowed = checkPermIP();
+ }
+ *tptr = '/';
+ }
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+ proxy_entry = find_proxy_entry(urlcopy);
+ if (proxy_entry)
+ header_buf = header_ptr = xmalloc(IOBUF_SIZE);
+#endif
+
+ if (http_major_version >= '0') {
+ /* Request was with "... HTTP/nXXX", and n >= 0 */
+
+ /* Read until blank line for HTTP version specified, else parse immediate */
+ while (1) {
+ alarm(HEADER_READ_TIMEOUT);
+ if (!get_line())
+ break; /* EOF or error or empty line */
+ if (DEBUG)
+ bb_error_msg("header: '%s'", iobuf);
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+ /* We need 2 more bytes for yet another "\r\n" -
+ * see near fdprintf(proxy_fd...) further below */
+ if (proxy_entry && (header_ptr - header_buf) < IOBUF_SIZE - 2) {
+ int len = strlen(iobuf);
+ if (len > IOBUF_SIZE - (header_ptr - header_buf) - 4)
+ len = IOBUF_SIZE - (header_ptr - header_buf) - 4;
+ memcpy(header_ptr, iobuf, len);
+ header_ptr += len;
+ header_ptr[0] = '\r';
+ header_ptr[1] = '\n';
+ header_ptr += 2;
+ }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_CGI || ENABLE_FEATURE_HTTPD_PROXY
+ /* Try and do our best to parse more lines */
+ if ((STRNCASECMP(iobuf, "Content-length:") == 0)) {
+ /* extra read only for POST */
+ if (prequest != request_GET
+#if ENABLE_FEATURE_HTTPD_CGI
+ && prequest != request_HEAD
+#endif
+ ) {
+ tptr = skip_whitespace(iobuf + sizeof("Content-length:") - 1);
+ if (!tptr[0])
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+ /* not using strtoul: it ignores leading minus! */
+ length = bb_strtou(tptr, NULL, 10);
+ /* length is "ulong", but we need to pass it to int later */
+ if (errno || length > INT_MAX)
+ send_headers_and_exit(HTTP_BAD_REQUEST);
+ }
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_CGI
+ else if (STRNCASECMP(iobuf, "Cookie:") == 0) {
+ cookie = xstrdup(skip_whitespace(iobuf + sizeof("Cookie:")-1));
+ } else if (STRNCASECMP(iobuf, "Content-Type:") == 0) {
+ content_type = xstrdup(skip_whitespace(iobuf + sizeof("Content-Type:")-1));
+ } else if (STRNCASECMP(iobuf, "Referer:") == 0) {
+ referer = xstrdup(skip_whitespace(iobuf + sizeof("Referer:")-1));
+ } else if (STRNCASECMP(iobuf, "User-Agent:") == 0) {
+ user_agent = xstrdup(skip_whitespace(iobuf + sizeof("User-Agent:")-1));
+ } else if (STRNCASECMP(iobuf, "Accept:") == 0) {
+ http_accept = xstrdup(skip_whitespace(iobuf + sizeof("Accept:")-1));
+ } else if (STRNCASECMP(iobuf, "Accept-Language:") == 0) {
+ http_accept_language = xstrdup(skip_whitespace(iobuf + sizeof("Accept-Language:")-1));
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ if (STRNCASECMP(iobuf, "Authorization:") == 0) {
+ /* We only allow Basic credentials.
+ * It shows up as "Authorization: Basic <user>:<passwd>" where
+ * "<user>:<passwd>" is base64 encoded.
+ */
+ tptr = skip_whitespace(iobuf + sizeof("Authorization:")-1);
+ if (STRNCASECMP(tptr, "Basic") != 0)
+ continue;
+ tptr += sizeof("Basic")-1;
+ /* decodeBase64() skips whitespace itself */
+ decodeBase64(tptr);
+ authorized = check_user_passwd(urlcopy, tptr);
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_RANGES
+ if (STRNCASECMP(iobuf, "Range:") == 0) {
+ /* We know only bytes=NNN-[MMM] */
+ char *s = skip_whitespace(iobuf + sizeof("Range:")-1);
+ if (strncmp(s, "bytes=", 6) == 0) {
+ s += sizeof("bytes=")-1;
+ range_start = BB_STRTOOFF(s, &s, 10);
+ if (s[0] != '-' || range_start < 0) {
+ range_start = 0;
+ } else if (s[1]) {
+ range_end = BB_STRTOOFF(s+1, NULL, 10);
+ if (errno || range_end < range_start)
+ range_start = 0;
+ }
+ }
+ }
+#endif
+ } /* while extra header reading */
+ }
+
+ /* We are done reading headers, disable peer timeout */
+ alarm(0);
+
+ if (strcmp(bb_basename(urlcopy), httpd_conf) == 0 || !ip_allowed) {
+ /* protect listing [/path]/httpd_conf or IP deny */
+ send_headers_and_exit(HTTP_FORBIDDEN);
+ }
+
+#if ENABLE_FEATURE_HTTPD_BASIC_AUTH
+ /* Case: no "Authorization:" was seen, but page does require passwd.
+ * Check that with dummy user:pass */
+ if (authorized < 0)
+ authorized = check_user_passwd(urlcopy, ":");
+ if (!authorized)
+ send_headers_and_exit(HTTP_UNAUTHORIZED);
+#endif
+
+ if (found_moved_temporarily) {
+ send_headers_and_exit(HTTP_MOVED_TEMPORARILY);
+ }
+
+#if ENABLE_FEATURE_HTTPD_PROXY
+ if (proxy_entry != NULL) {
+ int proxy_fd;
+ len_and_sockaddr *lsa;
+
+ proxy_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (proxy_fd < 0)
+ send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
+ lsa = host2sockaddr(proxy_entry->host_port, 80);
+ if (lsa == NULL)
+ send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
+ if (connect(proxy_fd, &lsa->u.sa, lsa->len) < 0)
+ send_headers_and_exit(HTTP_INTERNAL_SERVER_ERROR);
+ fdprintf(proxy_fd, "%s %s%s%s%s HTTP/%c.%c\r\n",
+ prequest, /* GET or POST */
+ proxy_entry->url_to, /* url part 1 */
+ urlcopy + strlen(proxy_entry->url_from), /* url part 2 */
+ (g_query ? "?" : ""), /* "?" (maybe) */
+ (g_query ? g_query : ""), /* query string (maybe) */
+ http_major_version, http_minor_version);
+ header_ptr[0] = '\r';
+ header_ptr[1] = '\n';
+ header_ptr += 2;
+ write(proxy_fd, header_buf, header_ptr - header_buf);
+ free(header_buf); /* on the order of 8k, free it */
+ /* cgi_io_loop_and_exit needs to have two disctinct fds */
+ cgi_io_loop_and_exit(proxy_fd, dup(proxy_fd), length);
+ }
+#endif
+
+ tptr = urlcopy + 1; /* skip first '/' */
+
+#if ENABLE_FEATURE_HTTPD_CGI
+ if (strncmp(tptr, "cgi-bin/", 8) == 0) {
+ if (tptr[8] == '\0') {
+ /* protect listing "cgi-bin/" */
+ send_headers_and_exit(HTTP_FORBIDDEN);
+ }
+ send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
+ }
+#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR
+ {
+ char *suffix = strrchr(tptr, '.');
+ if (suffix) {
+ Htaccess *cur;
+ for (cur = script_i; cur; cur = cur->next) {
+ if (strcmp(cur->before_colon + 1, suffix) == 0) {
+ send_cgi_and_exit(urlcopy, prequest, length, cookie, content_type);
+ }
+ }
+ }
+ }
+#endif
+ if (prequest != request_GET && prequest != request_HEAD) {
+ send_headers_and_exit(HTTP_NOT_IMPLEMENTED);
+ }
+#endif /* FEATURE_HTTPD_CGI */
+
+ if (urlp[-1] == '/')
+ strcpy(urlp, index_page);
+ if (stat(tptr, &sb) == 0) {
+ file_size = sb.st_size;
+ last_mod = sb.st_mtime;
+ }
+#if ENABLE_FEATURE_HTTPD_CGI
+ else if (urlp[-1] == '/') {
+ /* It's a dir URL and there is no index.html
+ * Try cgi-bin/index.cgi */
+ if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) {
+ urlp[0] = '\0';
+ g_query = urlcopy;
+ send_cgi_and_exit("/cgi-bin/index.cgi", prequest, length, cookie, content_type);
+ }
+ }
+#endif
+ /* else {
+ * fall through to send_file, it errors out if open fails
+ * }
+ */
+
+ send_file_and_exit(tptr,
+#if ENABLE_FEATURE_HTTPD_CGI
+ (prequest != request_HEAD ? SEND_HEADERS_AND_BODY : SEND_HEADERS)
+#else
+ SEND_HEADERS_AND_BODY
+#endif
+ );
+}
+
+/*
+ * The main http server function.
+ * Given a socket, listen for new connections and farm out
+ * the processing as a [v]forked process.
+ * Never returns.
+ */
+#if BB_MMU
+static void mini_httpd(int server_socket) NORETURN;
+static void mini_httpd(int server_socket)
+{
+ /* NB: it's best to not use xfuncs in this loop before fork().
+ * Otherwise server may die on transient errors (temporary
+ * out-of-memory condition, etc), which is Bad(tm).
+ * Try to do any dangerous calls after fork.
+ */
+ while (1) {
+ int n;
+ len_and_sockaddr fromAddr;
+
+ /* Wait for connections... */
+ fromAddr.len = LSA_SIZEOF_SA;
+ n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
+
+ if (n < 0)
+ continue;
+ /* set the KEEPALIVE option to cull dead connections */
+ setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+ if (fork() == 0) {
+ /* child */
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+ /* Do not reload config on HUP */
+ signal(SIGHUP, SIG_IGN);
+#endif
+ close(server_socket);
+ xmove_fd(n, 0);
+ xdup2(0, 1);
+
+ handle_incoming_and_exit(&fromAddr);
+ }
+ /* parent, or fork failed */
+ close(n);
+ } /* while (1) */
+ /* never reached */
+}
+#else
+static void mini_httpd_nommu(int server_socket, int argc, char **argv) NORETURN;
+static void mini_httpd_nommu(int server_socket, int argc, char **argv)
+{
+ char *argv_copy[argc + 2];
+
+ argv_copy[0] = argv[0];
+ argv_copy[1] = (char*)"-i";
+ memcpy(&argv_copy[2], &argv[1], argc * sizeof(argv[0]));
+
+ /* NB: it's best to not use xfuncs in this loop before vfork().
+ * Otherwise server may die on transient errors (temporary
+ * out-of-memory condition, etc), which is Bad(tm).
+ * Try to do any dangerous calls after fork.
+ */
+ while (1) {
+ int n;
+ len_and_sockaddr fromAddr;
+
+ /* Wait for connections... */
+ fromAddr.len = LSA_SIZEOF_SA;
+ n = accept(server_socket, &fromAddr.u.sa, &fromAddr.len);
+
+ if (n < 0)
+ continue;
+ /* set the KEEPALIVE option to cull dead connections */
+ setsockopt(n, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+ if (vfork() == 0) {
+ /* child */
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+ /* Do not reload config on HUP */
+ signal(SIGHUP, SIG_IGN);
+#endif
+ close(server_socket);
+ xmove_fd(n, 0);
+ xdup2(0, 1);
+
+ /* Run a copy of ourself in inetd mode */
+ re_exec(argv_copy);
+ }
+ /* parent, or vfork failed */
+ close(n);
+ } /* while (1) */
+ /* never reached */
+}
+#endif
+
+/*
+ * Process a HTTP connection on stdin/out.
+ * Never returns.
+ */
+static void mini_httpd_inetd(void) NORETURN;
+static void mini_httpd_inetd(void)
+{
+ len_and_sockaddr fromAddr;
+
+ memset(&fromAddr, 0, sizeof(fromAddr));
+ fromAddr.len = LSA_SIZEOF_SA;
+ /* NB: can fail if user runs it by hand and types in http cmds */
+ getpeername(0, &fromAddr.u.sa, &fromAddr.len);
+ handle_incoming_and_exit(&fromAddr);
+}
+
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+static void sighup_handler(int sig)
+{
+ parse_conf(default_path_httpd_conf, sig ? SIGNALED_PARSE : FIRST_PARSE);
+ signal_SA_RESTART_empty_mask(SIGHUP, sighup_handler);
+}
+#endif
+
+enum {
+ c_opt_config_file = 0,
+ d_opt_decode_url,
+ h_opt_home_httpd,
+ USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,)
+ USE_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,)
+ USE_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,)
+ USE_FEATURE_HTTPD_SETUID( u_opt_setuid ,)
+ p_opt_port ,
+ p_opt_inetd ,
+ p_opt_foreground,
+ p_opt_verbose ,
+ OPT_CONFIG_FILE = 1 << c_opt_config_file,
+ OPT_DECODE_URL = 1 << d_opt_decode_url,
+ OPT_HOME_HTTPD = 1 << h_opt_home_httpd,
+ OPT_ENCODE_URL = USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0,
+ OPT_REALM = USE_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0,
+ OPT_MD5 = USE_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0,
+ OPT_SETUID = USE_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0,
+ OPT_PORT = 1 << p_opt_port,
+ OPT_INETD = 1 << p_opt_inetd,
+ OPT_FOREGROUND = 1 << p_opt_foreground,
+ OPT_VERBOSE = 1 << p_opt_verbose,
+};
+
+
+int httpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int httpd_main(int argc UNUSED_PARAM, char **argv)
+{
+ int server_socket = server_socket; /* for gcc */
+ unsigned opt;
+ char *url_for_decode;
+ USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;)
+ USE_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;)
+ USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;)
+ USE_FEATURE_HTTPD_AUTH_MD5(const char *pass;)
+
+ INIT_G();
+
+#if ENABLE_LOCALE_SUPPORT
+ /* Undo busybox.c: we want to speak English in http (dates etc) */
+ setlocale(LC_TIME, "C");
+#endif
+
+ home_httpd = xrealloc_getcwd_or_warn(NULL);
+ /* -v counts, -i implies -f */
+ opt_complementary = "vv:if";
+ /* We do not "absolutize" path given by -h (home) opt.
+ * If user gives relative path in -h,
+ * $SCRIPT_FILENAME will not be set. */
+ opt = getopt32(argv, "c:d:h:"
+ USE_FEATURE_HTTPD_ENCODE_URL_STR("e:")
+ USE_FEATURE_HTTPD_BASIC_AUTH("r:")
+ USE_FEATURE_HTTPD_AUTH_MD5("m:")
+ USE_FEATURE_HTTPD_SETUID("u:")
+ "p:ifv",
+ &configFile, &url_for_decode, &home_httpd
+ USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode)
+ USE_FEATURE_HTTPD_BASIC_AUTH(, &g_realm)
+ USE_FEATURE_HTTPD_AUTH_MD5(, &pass)
+ USE_FEATURE_HTTPD_SETUID(, &s_ugid)
+ , &bind_addr_or_port
+ , &verbose
+ );
+ if (opt & OPT_DECODE_URL) {
+ fputs(decodeString(url_for_decode, 1), stdout);
+ return 0;
+ }
+#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR
+ if (opt & OPT_ENCODE_URL) {
+ fputs(encodeString(url_for_encode), stdout);
+ return 0;
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_AUTH_MD5
+ if (opt & OPT_MD5) {
+ puts(pw_encrypt(pass, "$1$", 1));
+ return 0;
+ }
+#endif
+#if ENABLE_FEATURE_HTTPD_SETUID
+ if (opt & OPT_SETUID) {
+ xget_uidgid(&ugid, s_ugid);
+ }
+#endif
+
+#if !BB_MMU
+ if (!(opt & OPT_FOREGROUND)) {
+ bb_daemonize_or_rexec(0, argv); /* don't change current directory */
+ }
+#endif
+
+ xchdir(home_httpd);
+ if (!(opt & OPT_INETD)) {
+ signal(SIGCHLD, SIG_IGN);
+ server_socket = openServer();
+#if ENABLE_FEATURE_HTTPD_SETUID
+ /* drop privileges */
+ if (opt & OPT_SETUID) {
+ if (ugid.gid != (gid_t)-1) {
+ if (setgroups(1, &ugid.gid) == -1)
+ bb_perror_msg_and_die("setgroups");
+ xsetgid(ugid.gid);
+ }
+ xsetuid(ugid.uid);
+ }
+#endif
+ }
+
+#if 0 /*was #if ENABLE_FEATURE_HTTPD_CGI*/
+ /* User can do it himself: 'env - PATH="$PATH" httpd'
+ * We don't do it because we don't want to screw users
+ * which want to do
+ * 'env - VAR1=val1 VAR2=val2 httpd'
+ * and have VAR1 and VAR2 values visible in their CGIs.
+ * Besides, it is also smaller. */
+ {
+ char *p = getenv("PATH");
+ /* env strings themself are not freed, no need to xstrdup(p): */
+ clearenv();
+ if (p)
+ putenv(p - 5);
+// if (!(opt & OPT_INETD))
+// setenv_long("SERVER_PORT", ???);
+ }
+#endif
+
+#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP
+ if (!(opt & OPT_INETD)) {
+ /* runs parse_conf() inside */
+ sighup_handler(0);
+ } else
+#endif
+ {
+ parse_conf(default_path_httpd_conf, FIRST_PARSE);
+ }
+
+ xfunc_error_retval = 0;
+ if (opt & OPT_INETD)
+ mini_httpd_inetd();
+#if BB_MMU
+ if (!(opt & OPT_FOREGROUND))
+ bb_daemonize(0); /* don't change current directory */
+ mini_httpd(server_socket); /* never returns */
+#else
+ mini_httpd_nommu(server_socket, argc, argv); /* never returns */
+#endif
+ /* return 0; */
+}
diff --git a/networking/httpd_indexcgi.c b/networking/httpd_indexcgi.c
new file mode 100644
index 0000000..94c6a69
--- /dev/null
+++ b/networking/httpd_indexcgi.c
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2007 Denys Vlasenko <vda.linux@googlemail.com>
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * This program is a CGI application. It outputs directory index page.
+ * Put it into cgi-bin/index.cgi and chmod 0755.
+ */
+
+/* Build a-la
+i486-linux-uclibc-gcc \
+-static -static-libgcc \
+-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \
+-Wall -Wshadow -Wwrite-strings -Wundef -Wstrict-prototypes -Werror \
+-Wold-style-definition -Wdeclaration-after-statement -Wno-pointer-sign \
+-Wmissing-prototypes -Wmissing-declarations \
+-Os -fno-builtin-strlen -finline-limit=0 -fomit-frame-pointer \
+-ffunction-sections -fdata-sections -fno-guess-branch-probability \
+-funsigned-char \
+-falign-functions=1 -falign-jumps=1 -falign-labels=1 -falign-loops=1 \
+-march=i386 -mpreferred-stack-boundary=2 \
+-Wl,-Map -Wl,link.map -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \
+httpd_indexcgi.c -o index.cgi
+*/
+
+/* We don't use printf, as it pulls in >12 kb of code from uclibc (i386). */
+/* Currently malloc machinery is the biggest part of libc we pull in. */
+/* We have only one realloc and one strdup, any idea how to do without? */
+/* Size (i386, approximate):
+ * text data bss dec hex filename
+ * 13036 44 3052 16132 3f04 index.cgi
+ * 2576 4 2048 4628 1214 index.cgi.o
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <time.h>
+
+/* Appearance of the table is controlled by style sheet *ONLY*,
+ * formatting code uses <TAG class=CLASS> to apply style
+ * to elements. Edit stylesheet to your liking and recompile. */
+
+#define STYLE_STR \
+"<style>" "\n"\
+"table {" "\n"\
+ "width:100%;" "\n"\
+ "background-color:#fff5ee;" "\n"\
+ "border-width:1px;" /* 1px 1px 1px 1px; */ "\n"\
+ "border-spacing:2px;" "\n"\
+ "border-style:solid;" /* solid solid solid solid; */ "\n"\
+ "border-color:black;" /* black black black black; */ "\n"\
+ "border-collapse:collapse;" "\n"\
+"}" "\n"\
+"th {" "\n"\
+ "border-width:1px;" /* 1px 1px 1px 1px; */ "\n"\
+ "padding:1px;" /* 1px 1px 1px 1px; */ "\n"\
+ "border-style:solid;" /* solid solid solid solid; */ "\n"\
+ "border-color:black;" /* black black black black; */ "\n"\
+"}" "\n"\
+"td {" "\n"\
+ /* top right bottom left */ \
+ "border-width:0px 1px 0px 1px;" "\n"\
+ "padding:1px;" /* 1px 1px 1px 1px; */ "\n"\
+ "border-style:solid;" /* solid solid solid solid; */ "\n"\
+ "border-color:black;" /* black black black black; */ "\n"\
+ "white-space:nowrap;" "\n"\
+"}" "\n"\
+"tr.hdr { background-color:#eee5de; }" "\n"\
+"tr.o { background-color:#ffffff; }" "\n"\
+/* tr.e { ... } - for even rows (currently none) */ \
+"tr.foot { background-color:#eee5de; }" "\n"\
+"th.cnt { text-align:left; }" "\n"\
+"th.sz { text-align:right; }" "\n"\
+"th.dt { text-align:right; }" "\n"\
+"td.sz { text-align:right; }" "\n"\
+"td.dt { text-align:right; }" "\n"\
+"col.nm { width:98%; }" "\n"\
+"col.sz { width:1%; }" "\n"\
+"col.dt { width:1%; }" "\n"\
+"</style>" "\n"\
+
+typedef struct dir_list_t {
+ char *dl_name;
+ mode_t dl_mode;
+ off_t dl_size;
+ time_t dl_mtime;
+} dir_list_t;
+
+static int compare_dl(dir_list_t *a, dir_list_t *b)
+{
+ /* ".." is 'less than' any other dir entry */
+ if (strcmp(a->dl_name, "..") == 0) {
+ return -1;
+ }
+ if (strcmp(b->dl_name, "..") == 0) {
+ return 1;
+ }
+ if (S_ISDIR(a->dl_mode) != S_ISDIR(b->dl_mode)) {
+ /* 1 if b is a dir (and thus a is 'after' b, a > b),
+ * else -1 (a < b) */
+ return (S_ISDIR(b->dl_mode) != 0) ? 1 : -1;
+ }
+ return strcmp(a->dl_name, b->dl_name);
+}
+
+static char buffer[2*1024 > sizeof(STYLE_STR) ? 2*1024 : sizeof(STYLE_STR)];
+static char *dst = buffer;
+enum {
+ BUFFER_SIZE = sizeof(buffer),
+ HEADROOM = 64,
+};
+
+/* After this call, you have at least size + HEADROOM bytes available
+ * ahead of dst */
+static void guarantee(int size)
+{
+ if (buffer + (BUFFER_SIZE-HEADROOM) - dst >= size)
+ return;
+ write(STDOUT_FILENO, buffer, dst - buffer);
+ dst = buffer;
+}
+
+/* NB: formatters do not store terminating NUL! */
+
+/* HEADROOM bytes are available after dst after this call */
+static void fmt_str(/*char *dst,*/ const char *src)
+{
+ unsigned len = strlen(src);
+ guarantee(len);
+ memcpy(dst, src, len);
+ dst += len;
+}
+
+/* HEADROOM bytes after dst are available after this call */
+static void fmt_url(/*char *dst,*/ const char *name)
+{
+ while (*name) {
+ unsigned c = *name++;
+ guarantee(3);
+ *dst = c;
+ if ((c - '0') > 9 /* not a digit */
+ && ((c|0x20) - 'a') > 26 /* not A-Z or a-z */
+ && !strchr("._-+@", c)
+ ) {
+ *dst++ = '%';
+ *dst++ = "0123456789ABCDEF"[c >> 4];
+ *dst = "0123456789ABCDEF"[c & 0xf];
+ }
+ dst++;
+ }
+}
+
+/* HEADROOM bytes are available after dst after this call */
+static void fmt_html(/*char *dst,*/ const char *name)
+{
+ while (*name) {
+ char c = *name++;
+ if (c == '<')
+ fmt_str("&lt;");
+ else if (c == '>')
+ fmt_str("&gt;");
+ else if (c == '&') {
+ fmt_str("&amp;");
+ } else {
+ guarantee(1);
+ *dst++ = c;
+ continue;
+ }
+ }
+}
+
+/* HEADROOM bytes are available after dst after this call */
+static void fmt_ull(/*char *dst,*/ unsigned long long n)
+{
+ char buf[sizeof(n)*3 + 2];
+ char *p;
+
+ p = buf + sizeof(buf) - 1;
+ *p = '\0';
+ do {
+ *--p = (n % 10) + '0';
+ n /= 10;
+ } while (n);
+ fmt_str(/*dst,*/ p);
+}
+
+/* Does not call guarantee - eats into headroom instead */
+static void fmt_02u(/*char *dst,*/ unsigned n)
+{
+ /* n %= 100; - not needed, callers don't pass big n */
+ dst[0] = (n / 10) + '0';
+ dst[1] = (n % 10) + '0';
+ dst += 2;
+}
+
+/* Does not call guarantee - eats into headroom instead */
+static void fmt_04u(/*char *dst,*/ unsigned n)
+{
+ /* n %= 10000; - not needed, callers don't pass big n */
+ fmt_02u(n / 100);
+ fmt_02u(n % 100);
+}
+
+int main(void)
+{
+ dir_list_t *dir_list;
+ dir_list_t *cdir;
+ unsigned dir_list_count;
+ unsigned count_dirs;
+ unsigned count_files;
+ unsigned long long size_total;
+ int odd;
+ DIR *dirp;
+ char *QUERY_STRING;
+
+ QUERY_STRING = getenv("QUERY_STRING");
+ if (!QUERY_STRING
+ || QUERY_STRING[0] != '/'
+ || strstr(QUERY_STRING, "/../")
+ || strcmp(strrchr(QUERY_STRING, '/'), "/..") == 0
+ ) {
+ return 1;
+ }
+
+ if (chdir("..")
+ || (QUERY_STRING[1] && chdir(QUERY_STRING + 1))
+ ) {
+ return 1;
+ }
+
+ dirp = opendir(".");
+ if (!dirp)
+ return 1;
+ dir_list = NULL;
+ dir_list_count = 0;
+ while (1) {
+ struct dirent *dp;
+ struct stat sb;
+
+ dp = readdir(dirp);
+ if (!dp)
+ break;
+ if (dp->d_name[0] == '.' && !dp->d_name[1])
+ continue;
+ if (stat(dp->d_name, &sb) != 0)
+ continue;
+ dir_list = realloc(dir_list, (dir_list_count + 1) * sizeof(dir_list[0]));
+ dir_list[dir_list_count].dl_name = strdup(dp->d_name);
+ dir_list[dir_list_count].dl_mode = sb.st_mode;
+ dir_list[dir_list_count].dl_size = sb.st_size;
+ dir_list[dir_list_count].dl_mtime = sb.st_mtime;
+ dir_list_count++;
+ }
+ closedir(dirp);
+
+ qsort(dir_list, dir_list_count, sizeof(dir_list[0]), (void*)compare_dl);
+
+ fmt_str(
+ "" /* Additional headers (currently none) */
+ "\r\n" /* Mandatory empty line after headers */
+ "<html><head><title>Index of ");
+ /* Guard against directories with &, > etc */
+ fmt_html(QUERY_STRING);
+ fmt_str(
+ "</title>\n"
+ STYLE_STR
+ "</head>" "\n"
+ "<body>" "\n"
+ "<h1>Index of ");
+ fmt_html(QUERY_STRING);
+ fmt_str(
+ "</h1>" "\n"
+ "<table>" "\n"
+ "<col class=nm><col class=sz><col class=dt>" "\n"
+ "<tr class=hdr><th class=cnt>Name<th class=sz>Size<th class=dt>Last modified" "\n");
+
+ odd = 0;
+ count_dirs = 0;
+ count_files = 0;
+ size_total = 0;
+ cdir = dir_list;
+ while (dir_list_count--) {
+ struct tm *tm;
+
+ if (S_ISDIR(cdir->dl_mode)) {
+ count_dirs++;
+ } else if (S_ISREG(cdir->dl_mode)) {
+ count_files++;
+ size_total += cdir->dl_size;
+ } else
+ goto next;
+
+ fmt_str("<tr class=");
+ *dst++ = (odd ? 'o' : 'e');
+ fmt_str("><td class=nm><a href='");
+ fmt_url(cdir->dl_name); /* %20 etc */
+ if (S_ISDIR(cdir->dl_mode))
+ *dst++ = '/';
+ fmt_str("'>");
+ fmt_html(cdir->dl_name); /* &lt; etc */
+ if (S_ISDIR(cdir->dl_mode))
+ *dst++ = '/';
+ fmt_str("</a><td class=sz>");
+ if (S_ISREG(cdir->dl_mode))
+ fmt_ull(cdir->dl_size);
+ fmt_str("<td class=dt>");
+ tm = gmtime(&cdir->dl_mtime);
+ fmt_04u(1900 + tm->tm_year); *dst++ = '-';
+ fmt_02u(tm->tm_mon + 1); *dst++ = '-';
+ fmt_02u(tm->tm_mday); *dst++ = ' ';
+ fmt_02u(tm->tm_hour); *dst++ = ':';
+ fmt_02u(tm->tm_min); *dst++ = ':';
+ fmt_02u(tm->tm_sec);
+ *dst++ = '\n';
+
+ odd = 1 - odd;
+ next:
+ cdir++;
+ }
+
+ fmt_str("<tr class=foot><th class=cnt>Files: ");
+ fmt_ull(count_files);
+ /* count_dirs - 1: we don't want to count ".." */
+ fmt_str(", directories: ");
+ fmt_ull(count_dirs - 1);
+ fmt_str("<th class=sz>");
+ fmt_ull(size_total);
+ fmt_str("<th class=dt>\n");
+ /* "</table></body></html>" - why bother? */
+ guarantee(BUFFER_SIZE * 2); /* flush */
+
+ return 0;
+}
diff --git a/networking/httpd_post_upload.txt b/networking/httpd_post_upload.txt
new file mode 100644
index 0000000..a53b114
--- /dev/null
+++ b/networking/httpd_post_upload.txt
@@ -0,0 +1,76 @@
+POST upload example:
+
+post_upload.htm
+===============
+<html>
+<body>
+<form action=/cgi-bin/post_upload.cgi method=post enctype=multipart/form-data>
+File to upload: <input type=file name=file1> <input type=submit>
+</form>
+
+
+post_upload.cgi
+===============
+#!/bin/sh
+
+# POST upload format:
+# -----------------------------29995809218093749221856446032^M
+# Content-Disposition: form-data; name="file1"; filename="..."^M
+# Content-Type: application/octet-stream^M
+# ^M <--------- headers end with empty line
+# file contents
+# file contents
+# file contents
+# ^M <--------- extra empty line
+# -----------------------------29995809218093749221856446032--^M
+
+# Beware: bashism $'\r' is used to handle ^M
+
+file=/tmp/$$-$RANDOM
+
+# CGI output must start with at least empty line (or headers)
+printf '\r\n'
+
+IFS=$'\r'
+read -r delim_line
+
+IFS=''
+delim_line="${delim_line}--"$'\r'
+
+while read -r line; do
+ test "$line" = '' && break
+ test "$line" = $'\r' && break
+done
+
+# This will not work well for binary files: bash 3.2 is upset
+# by reading NUL bytes and loses chunks of data.
+# If you are not bothered by having junk appended to the uploaded file,
+# consider using simple "cat >file" instead of the entire
+# fragment below.
+
+while read -r line; do
+
+ while test "$line" = $'\r'; do
+ read -r line
+ test "$line" = "$delim_line" && {
+ # Aha! Empty line + delimiter! All done
+ cat <<EOF
+<html>
+<body>
+File upload has been accepted
+EOF
+ exit 0
+ }
+ # Empty line + NOT delimiter. Save empty line,
+ # and go check next line
+ printf "%s\n" $'\r' -vC >&3
+ done
+ # Not empty line - just save
+ printf "%s\n" "$line" -vC >&3
+done 3>"$file"
+
+cat <<EOF
+<html>
+<body>
+File upload was not terminated with '$delim_line' - ??!
+EOF
diff --git a/networking/ifconfig.c b/networking/ifconfig.c
new file mode 100644
index 0000000..e999741
--- /dev/null
+++ b/networking/ifconfig.c
@@ -0,0 +1,540 @@
+/* vi: set sw=4 ts=4: */
+/* ifconfig
+ *
+ * Similar to the standard Unix ifconfig, but with only the necessary
+ * parts for AF_INET, and without any printing of if info (for now).
+ *
+ * Bjorn Wesen, Axis Communications AB
+ *
+ *
+ * Authors of the original ifconfig was:
+ * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/*
+ * Heavily modified by Manuel Novoa III Mar 6, 2001
+ *
+ * From initial port to busybox, removed most of the redundancy by
+ * converting to a table-driven approach. Added several (optional)
+ * args missing from initial port.
+ *
+ * Still missing: media, tunnel.
+ *
+ * 2002-04-20
+ * IPV6 support added by Bart Visscher <magick@linux-fan.com>
+ */
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <netinet/in.h>
+#if defined(__GLIBC__) && __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <sys/types.h>
+#include <netinet/if_ether.h>
+#endif
+#include "inet_common.h"
+#include "libbb.h"
+
+#if ENABLE_FEATURE_IFCONFIG_SLIP
+# include <net/if_slip.h>
+#endif
+
+/* I don't know if this is needed for busybox or not. Anyone? */
+#define QUESTIONABLE_ALIAS_CASE
+
+
+/* Defines for glibc2.0 users. */
+#ifndef SIOCSIFTXQLEN
+# define SIOCSIFTXQLEN 0x8943
+# define SIOCGIFTXQLEN 0x8942
+#endif
+
+/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */
+#ifndef ifr_qlen
+# define ifr_qlen ifr_ifru.ifru_mtu
+#endif
+
+#ifndef IFF_DYNAMIC
+# define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */
+#endif
+
+#if ENABLE_FEATURE_IPV6
+struct in6_ifreq {
+ struct in6_addr ifr6_addr;
+ uint32_t ifr6_prefixlen;
+ int ifr6_ifindex;
+};
+#endif
+
+/*
+ * Here are the bit masks for the "flags" member of struct options below.
+ * N_ signifies no arg prefix; M_ signifies arg prefixed by '-'.
+ * CLR clears the flag; SET sets the flag; ARG signifies (optional) arg.
+ */
+#define N_CLR 0x01
+#define M_CLR 0x02
+#define N_SET 0x04
+#define M_SET 0x08
+#define N_ARG 0x10
+#define M_ARG 0x20
+
+#define M_MASK (M_CLR | M_SET | M_ARG)
+#define N_MASK (N_CLR | N_SET | N_ARG)
+#define SET_MASK (N_SET | M_SET)
+#define CLR_MASK (N_CLR | M_CLR)
+#define SET_CLR_MASK (SET_MASK | CLR_MASK)
+#define ARG_MASK (M_ARG | N_ARG)
+
+/*
+ * Here are the bit masks for the "arg_flags" member of struct options below.
+ */
+
+/*
+ * cast type:
+ * 00 int
+ * 01 char *
+ * 02 HOST_COPY in_ether
+ * 03 HOST_COPY INET_resolve
+ */
+#define A_CAST_TYPE 0x03
+/*
+ * map type:
+ * 00 not a map type (mem_start, io_addr, irq)
+ * 04 memstart (unsigned long)
+ * 08 io_addr (unsigned short)
+ * 0C irq (unsigned char)
+ */
+#define A_MAP_TYPE 0x0C
+#define A_ARG_REQ 0x10 /* Set if an arg is required. */
+#define A_NETMASK 0x20 /* Set if netmask (check for multiple sets). */
+#define A_SET_AFTER 0x40 /* Set a flag at the end. */
+#define A_COLON_CHK 0x80 /* Is this needed? See below. */
+#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS
+#define A_HOSTNAME 0x100 /* Set if it is ip addr. */
+#define A_BROADCAST 0x200 /* Set if it is broadcast addr. */
+#else
+#define A_HOSTNAME 0
+#define A_BROADCAST 0
+#endif
+
+/*
+ * These defines are for dealing with the A_CAST_TYPE field.
+ */
+#define A_CAST_CHAR_PTR 0x01
+#define A_CAST_RESOLVE 0x01
+#define A_CAST_HOST_COPY 0x02
+#define A_CAST_HOST_COPY_IN_ETHER A_CAST_HOST_COPY
+#define A_CAST_HOST_COPY_RESOLVE (A_CAST_HOST_COPY | A_CAST_RESOLVE)
+
+/*
+ * These defines are for dealing with the A_MAP_TYPE field.
+ */
+#define A_MAP_ULONG 0x04 /* memstart */
+#define A_MAP_USHORT 0x08 /* io_addr */
+#define A_MAP_UCHAR 0x0C /* irq */
+
+/*
+ * Define the bit masks signifying which operations to perform for each arg.
+ */
+
+#define ARG_METRIC (A_ARG_REQ /*| A_CAST_INT*/)
+#define ARG_MTU (A_ARG_REQ /*| A_CAST_INT*/)
+#define ARG_TXQUEUELEN (A_ARG_REQ /*| A_CAST_INT*/)
+#define ARG_MEM_START (A_ARG_REQ | A_MAP_ULONG)
+#define ARG_IO_ADDR (A_ARG_REQ | A_MAP_ULONG)
+#define ARG_IRQ (A_ARG_REQ | A_MAP_UCHAR)
+#define ARG_DSTADDR (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE)
+#define ARG_NETMASK (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_NETMASK)
+#define ARG_BROADCAST (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_BROADCAST)
+#define ARG_HW (A_ARG_REQ | A_CAST_HOST_COPY_IN_ETHER)
+#define ARG_POINTOPOINT (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER)
+#define ARG_KEEPALIVE (A_ARG_REQ | A_CAST_CHAR_PTR)
+#define ARG_OUTFILL (A_ARG_REQ | A_CAST_CHAR_PTR)
+#define ARG_HOSTNAME (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_COLON_CHK | A_HOSTNAME)
+#define ARG_ADD_DEL (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER)
+
+
+/*
+ * Set up the tables. Warning! They must have corresponding order!
+ */
+
+struct arg1opt {
+ const char *name;
+ unsigned short selector;
+ unsigned short ifr_offset;
+};
+
+struct options {
+ const char *name;
+#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS
+ const unsigned int flags:6;
+ const unsigned int arg_flags:10;
+#else
+ const unsigned char flags;
+ const unsigned char arg_flags;
+#endif
+ const unsigned short selector;
+};
+
+#define ifreq_offsetof(x) offsetof(struct ifreq, x)
+
+static const struct arg1opt Arg1Opt[] = {
+ { "SIFMETRIC", SIOCSIFMETRIC, ifreq_offsetof(ifr_metric) },
+ { "SIFMTU", SIOCSIFMTU, ifreq_offsetof(ifr_mtu) },
+ { "SIFTXQLEN", SIOCSIFTXQLEN, ifreq_offsetof(ifr_qlen) },
+ { "SIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr) },
+ { "SIFNETMASK", SIOCSIFNETMASK, ifreq_offsetof(ifr_netmask) },
+ { "SIFBRDADDR", SIOCSIFBRDADDR, ifreq_offsetof(ifr_broadaddr) },
+#if ENABLE_FEATURE_IFCONFIG_HW
+ { "SIFHWADDR", SIOCSIFHWADDR, ifreq_offsetof(ifr_hwaddr) },
+#endif
+ { "SIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr) },
+#ifdef SIOCSKEEPALIVE
+ { "SKEEPALIVE", SIOCSKEEPALIVE, ifreq_offsetof(ifr_data) },
+#endif
+#ifdef SIOCSOUTFILL
+ { "SOUTFILL", SIOCSOUTFILL, ifreq_offsetof(ifr_data) },
+#endif
+#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
+ { "SIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.mem_start) },
+ { "SIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.base_addr) },
+ { "SIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.irq) },
+#endif
+ /* Last entry if for unmatched (possibly hostname) arg. */
+#if ENABLE_FEATURE_IPV6
+ { "SIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr) }, /* IPv6 version ignores the offset */
+ { "DIFADDR", SIOCDIFADDR, ifreq_offsetof(ifr_addr) }, /* IPv6 version ignores the offset */
+#endif
+ { "SIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr) },
+};
+
+static const struct options OptArray[] = {
+ { "metric", N_ARG, ARG_METRIC, 0 },
+ { "mtu", N_ARG, ARG_MTU, 0 },
+ { "txqueuelen", N_ARG, ARG_TXQUEUELEN, 0 },
+ { "dstaddr", N_ARG, ARG_DSTADDR, 0 },
+ { "netmask", N_ARG, ARG_NETMASK, 0 },
+ { "broadcast", N_ARG | M_CLR, ARG_BROADCAST, IFF_BROADCAST },
+#if ENABLE_FEATURE_IFCONFIG_HW
+ { "hw", N_ARG, ARG_HW, 0 },
+#endif
+ { "pointopoint", N_ARG | M_CLR, ARG_POINTOPOINT, IFF_POINTOPOINT },
+#ifdef SIOCSKEEPALIVE
+ { "keepalive", N_ARG, ARG_KEEPALIVE, 0 },
+#endif
+#ifdef SIOCSOUTFILL
+ { "outfill", N_ARG, ARG_OUTFILL, 0 },
+#endif
+#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
+ { "mem_start", N_ARG, ARG_MEM_START, 0 },
+ { "io_addr", N_ARG, ARG_IO_ADDR, 0 },
+ { "irq", N_ARG, ARG_IRQ, 0 },
+#endif
+#if ENABLE_FEATURE_IPV6
+ { "add", N_ARG, ARG_ADD_DEL, 0 },
+ { "del", N_ARG, ARG_ADD_DEL, 0 },
+#endif
+ { "arp", N_CLR | M_SET, 0, IFF_NOARP },
+ { "trailers", N_CLR | M_SET, 0, IFF_NOTRAILERS },
+ { "promisc", N_SET | M_CLR, 0, IFF_PROMISC },
+ { "multicast", N_SET | M_CLR, 0, IFF_MULTICAST },
+ { "allmulti", N_SET | M_CLR, 0, IFF_ALLMULTI },
+ { "dynamic", N_SET | M_CLR, 0, IFF_DYNAMIC },
+ { "up", N_SET, 0, (IFF_UP | IFF_RUNNING) },
+ { "down", N_CLR, 0, IFF_UP },
+ { NULL, 0, ARG_HOSTNAME, (IFF_UP | IFF_RUNNING) }
+};
+
+/*
+ * A couple of prototypes.
+ */
+#if ENABLE_FEATURE_IFCONFIG_HW
+static int in_ether(const char *bufp, struct sockaddr *sap);
+#endif
+
+/*
+ * Our main function.
+ */
+int ifconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifconfig_main(int argc, char **argv)
+{
+ struct ifreq ifr;
+ struct sockaddr_in sai;
+#if ENABLE_FEATURE_IFCONFIG_HW
+ struct sockaddr sa;
+#endif
+ const struct arg1opt *a1op;
+ const struct options *op;
+ int sockfd; /* socket fd we use to manipulate stuff with */
+ int selector;
+#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS
+ unsigned int mask;
+ unsigned int did_flags;
+ unsigned int sai_hostname, sai_netmask;
+#else
+ unsigned char mask;
+ unsigned char did_flags;
+#endif
+ char *p;
+ /*char host[128];*/
+ const char *host = NULL; /* make gcc happy */
+
+ did_flags = 0;
+#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS
+ sai_hostname = 0;
+ sai_netmask = 0;
+#endif
+
+ /* skip argv[0] */
+ ++argv;
+ --argc;
+
+#if ENABLE_FEATURE_IFCONFIG_STATUS
+ if (argc > 0 && (argv[0][0] == '-' && argv[0][1] == 'a' && !argv[0][2])) {
+ interface_opt_a = 1;
+ --argc;
+ ++argv;
+ }
+#endif
+
+ if (argc <= 1) {
+#if ENABLE_FEATURE_IFCONFIG_STATUS
+ return display_interfaces(argc ? *argv : NULL);
+#else
+ bb_error_msg_and_die("no support for status display");
+#endif
+ }
+
+ /* Create a channel to the NET kernel. */
+ sockfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+ /* get interface name */
+ strncpy(ifr.ifr_name, *argv, IFNAMSIZ);
+
+ /* Process the remaining arguments. */
+ while (*++argv != (char *) NULL) {
+ p = *argv;
+ mask = N_MASK;
+ if (*p == '-') { /* If the arg starts with '-'... */
+ ++p; /* advance past it and */
+ mask = M_MASK; /* set the appropriate mask. */
+ }
+ for (op = OptArray; op->name; op++) { /* Find table entry. */
+ if (strcmp(p, op->name) == 0) { /* If name matches... */
+ mask &= op->flags;
+ if (mask) /* set the mask and go. */
+ goto FOUND_ARG;
+ /* If we get here, there was a valid arg with an */
+ /* invalid '-' prefix. */
+ bb_error_msg_and_die("bad: '%s'", p-1);
+ }
+ }
+
+ /* We fell through, so treat as possible hostname. */
+ a1op = Arg1Opt + ARRAY_SIZE(Arg1Opt) - 1;
+ mask = op->arg_flags;
+ goto HOSTNAME;
+
+ FOUND_ARG:
+ if (mask & ARG_MASK) {
+ mask = op->arg_flags;
+ a1op = Arg1Opt + (op - OptArray);
+ if (mask & A_NETMASK & did_flags)
+ bb_show_usage();
+ if (*++argv == NULL) {
+ if (mask & A_ARG_REQ)
+ bb_show_usage();
+ --argv;
+ mask &= A_SET_AFTER; /* just for broadcast */
+ } else { /* got an arg so process it */
+ HOSTNAME:
+ did_flags |= (mask & (A_NETMASK|A_HOSTNAME));
+ if (mask & A_CAST_HOST_COPY) {
+#if ENABLE_FEATURE_IFCONFIG_HW
+ if (mask & A_CAST_RESOLVE) {
+#endif
+#if ENABLE_FEATURE_IPV6
+ char *prefix;
+ int prefix_len = 0;
+#endif
+ /*safe_strncpy(host, *argv, (sizeof host));*/
+ host = *argv;
+#if ENABLE_FEATURE_IPV6
+ prefix = strchr(host, '/');
+ if (prefix) {
+ prefix_len = xatou_range(prefix + 1, 0, 128);
+ *prefix = '\0';
+ }
+#endif
+ sai.sin_family = AF_INET;
+ sai.sin_port = 0;
+ if (!strcmp(host, bb_str_default)) {
+ /* Default is special, meaning 0.0.0.0. */
+ sai.sin_addr.s_addr = INADDR_ANY;
+ }
+#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS
+ else if ((host[0] == '+' && !host[1]) && (mask & A_BROADCAST)
+ && (did_flags & (A_NETMASK|A_HOSTNAME)) == (A_NETMASK|A_HOSTNAME)
+ ) {
+ /* + is special, meaning broadcast is derived. */
+ sai.sin_addr.s_addr = (~sai_netmask) | (sai_hostname & sai_netmask);
+ }
+#endif
+ else {
+ len_and_sockaddr *lsa;
+ if (strcmp(host, "inet") == 0)
+ continue; /* compat stuff */
+ lsa = xhost2sockaddr(host, 0);
+#if ENABLE_FEATURE_IPV6
+ if (lsa->u.sa.sa_family == AF_INET6) {
+ int sockfd6;
+ struct in6_ifreq ifr6;
+
+ memcpy((char *) &ifr6.ifr6_addr,
+ (char *) &(lsa->u.sin6.sin6_addr),
+ sizeof(struct in6_addr));
+
+ /* Create a channel to the NET kernel. */
+ sockfd6 = xsocket(AF_INET6, SOCK_DGRAM, 0);
+ xioctl(sockfd6, SIOGIFINDEX, &ifr);
+ ifr6.ifr6_ifindex = ifr.ifr_ifindex;
+ ifr6.ifr6_prefixlen = prefix_len;
+ ioctl_or_perror_and_die(sockfd6, a1op->selector, &ifr6, "SIOC%s", a1op->name);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(lsa);
+ continue;
+ }
+#endif
+ sai.sin_addr = lsa->u.sin.sin_addr;
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(lsa);
+ }
+#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS
+ if (mask & A_HOSTNAME)
+ sai_hostname = sai.sin_addr.s_addr;
+ if (mask & A_NETMASK)
+ sai_netmask = sai.sin_addr.s_addr;
+#endif
+ p = (char *) &sai;
+#if ENABLE_FEATURE_IFCONFIG_HW
+ } else { /* A_CAST_HOST_COPY_IN_ETHER */
+ /* This is the "hw" arg case. */
+ smalluint hw_class= index_in_substrings("ether\0"
+ USE_FEATURE_HWIB("infiniband\0"), *argv) + 1;
+ if (!hw_class || !*++argv)
+ bb_show_usage();
+ /*safe_strncpy(host, *argv, sizeof(host));*/
+ host = *argv;
+ if (hw_class == 1 ? in_ether(host, &sa) : in_ib(host, &sa))
+ bb_error_msg_and_die("invalid hw-addr %s", host);
+ p = (char *) &sa;
+ }
+#endif
+ memcpy( (((char *)&ifr) + a1op->ifr_offset),
+ p, sizeof(struct sockaddr));
+ } else {
+ /* FIXME: error check?? */
+ unsigned long i = strtoul(*argv, NULL, 0);
+ p = ((char *)&ifr) + a1op->ifr_offset;
+#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ
+ if (mask & A_MAP_TYPE) {
+ xioctl(sockfd, SIOCGIFMAP, &ifr);
+ if ((mask & A_MAP_UCHAR) == A_MAP_UCHAR)
+ *((unsigned char *) p) = i;
+ else if (mask & A_MAP_USHORT)
+ *((unsigned short *) p) = i;
+ else
+ *((unsigned long *) p) = i;
+ } else
+#endif
+ if (mask & A_CAST_CHAR_PTR)
+ *((caddr_t *) p) = (caddr_t) i;
+ else /* A_CAST_INT */
+ *((int *) p) = i;
+ }
+
+ ioctl_or_perror_and_die(sockfd, a1op->selector, &ifr, "SIOC%s", a1op->name);
+#ifdef QUESTIONABLE_ALIAS_CASE
+ if (mask & A_COLON_CHK) {
+ /*
+ * Don't do the set_flag() if the address is an alias with
+ * a '-' at the end, since it's deleted already! - Roman
+ *
+ * Should really use regex.h here, not sure though how well
+ * it'll go with the cross-platform support etc.
+ */
+ char *ptr;
+ short int found_colon = 0;
+ for (ptr = ifr.ifr_name; *ptr; ptr++)
+ if (*ptr == ':')
+ found_colon++;
+ if (found_colon && ptr[-1] == '-')
+ continue;
+ }
+#endif
+ }
+ if (!(mask & A_SET_AFTER))
+ continue;
+ mask = N_SET;
+ }
+
+ xioctl(sockfd, SIOCGIFFLAGS, &ifr);
+ selector = op->selector;
+ if (mask & SET_MASK)
+ ifr.ifr_flags |= selector;
+ else
+ ifr.ifr_flags &= ~selector;
+ xioctl(sockfd, SIOCSIFFLAGS, &ifr);
+ } /* while () */
+
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(sockfd);
+ return 0;
+}
+
+#if ENABLE_FEATURE_IFCONFIG_HW
+/* Input an Ethernet address and convert to binary. */
+static int in_ether(const char *bufp, struct sockaddr *sap)
+{
+ char *ptr;
+ int i, j;
+ unsigned char val;
+ unsigned char c;
+
+ sap->sa_family = ARPHRD_ETHER;
+ ptr = (char *) sap->sa_data;
+
+ i = 0;
+ do {
+ j = val = 0;
+
+ /* We might get a semicolon here - not required. */
+ if (i && (*bufp == ':')) {
+ bufp++;
+ }
+
+ do {
+ c = *bufp;
+ if (((unsigned char)(c - '0')) <= 9) {
+ c -= '0';
+ } else if (((unsigned char)((c|0x20) - 'a')) <= 5) {
+ c = (c|0x20) - ('a'-10);
+ } else if (j && (c == ':' || c == 0)) {
+ break;
+ } else {
+ return -1;
+ }
+ ++bufp;
+ val <<= 4;
+ val += c;
+ } while (++j < 2);
+ *ptr++ = val;
+ } while (++i < ETH_ALEN);
+
+ return *bufp; /* Error if we don't end at end of string. */
+}
+#endif
diff --git a/networking/ifenslave.c b/networking/ifenslave.c
new file mode 100644
index 0000000..ae97457
--- /dev/null
+++ b/networking/ifenslave.c
@@ -0,0 +1,597 @@
+/* Mode: C;
+ *
+ * Mini ifenslave implementation for busybox
+ * Copyright (C) 2005 by Marc Leeman <marc.leeman@barco.com>
+ *
+ * ifenslave.c: Configure network interfaces for parallel routing.
+ *
+ * This program controls the Linux implementation of running multiple
+ * network interfaces in parallel.
+ *
+ * Author: Donald Becker <becker@cesdis.gsfc.nasa.gov>
+ * Copyright 1994-1996 Donald Becker
+ *
+ * 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.
+ *
+ * The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O
+ * Center of Excellence in Space Data and Information Sciences
+ * Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771
+ *
+ * Changes :
+ * - 2000/10/02 Willy Tarreau <willy at meta-x.org> :
+ * - few fixes. Master's MAC address is now correctly taken from
+ * the first device when not previously set ;
+ * - detach support : call BOND_RELEASE to detach an enslaved interface.
+ * - give a mini-howto from command-line help : # ifenslave -h
+ *
+ * - 2001/02/16 Chad N. Tindel <ctindel at ieee dot org> :
+ * - Master is now brought down before setting the MAC address. In
+ * the 2.4 kernel you can't change the MAC address while the device is
+ * up because you get EBUSY.
+ *
+ * - 2001/09/13 Takao Indoh <indou dot takao at jp dot fujitsu dot com>
+ * - Added the ability to change the active interface on a mode 1 bond
+ * at runtime.
+ *
+ * - 2001/10/23 Chad N. Tindel <ctindel at ieee dot org> :
+ * - No longer set the MAC address of the master. The bond device will
+ * take care of this itself
+ * - Try the SIOC*** versions of the bonding ioctls before using the
+ * old versions
+ * - 2002/02/18 Erik Habbinga <erik_habbinga @ hp dot com> :
+ * - ifr2.ifr_flags was not initialized in the hwaddr_notset case,
+ * SIOCGIFFLAGS now called before hwaddr_notset test
+ *
+ * - 2002/10/31 Tony Cureington <tony.cureington * hp_com> :
+ * - If the master does not have a hardware address when the first slave
+ * is enslaved, the master is assigned the hardware address of that
+ * slave - there is a comment in bonding.c stating "ifenslave takes
+ * care of this now." This corrects the problem of slaves having
+ * different hardware addresses in active-backup mode when
+ * multiple interfaces are specified on a single ifenslave command
+ * (ifenslave bond0 eth0 eth1).
+ *
+ * - 2003/03/18 - Tsippy Mendelson <tsippy.mendelson at intel dot com> and
+ * Shmulik Hen <shmulik.hen at intel dot com>
+ * - Moved setting the slave's mac address and openning it, from
+ * the application to the driver. This enables support of modes
+ * that need to use the unique mac address of each slave.
+ * The driver also takes care of closing the slave and restoring its
+ * original mac address upon release.
+ * In addition, block possibility of enslaving before the master is up.
+ * This prevents putting the system in an undefined state.
+ *
+ * - 2003/05/01 - Amir Noam <amir.noam at intel dot com>
+ * - Added ABI version control to restore compatibility between
+ * new/old ifenslave and new/old bonding.
+ * - Prevent adding an adapter that is already a slave.
+ * Fixes the problem of stalling the transmission and leaving
+ * the slave in a down state.
+ *
+ * - 2003/05/01 - Shmulik Hen <shmulik.hen at intel dot com>
+ * - Prevent enslaving if the bond device is down.
+ * Fixes the problem of leaving the system in unstable state and
+ * halting when trying to remove the module.
+ * - Close socket on all abnormal exists.
+ * - Add versioning scheme that follows that of the bonding driver.
+ * current version is 1.0.0 as a base line.
+ *
+ * - 2003/05/22 - Jay Vosburgh <fubar at us dot ibm dot com>
+ * - ifenslave -c was broken; it's now fixed
+ * - Fixed problem with routes vanishing from master during enslave
+ * processing.
+ *
+ * - 2003/05/27 - Amir Noam <amir.noam at intel dot com>
+ * - Fix backward compatibility issues:
+ * For drivers not using ABI versions, slave was set down while
+ * it should be left up before enslaving.
+ * Also, master was not set down and the default set_mac_address()
+ * would fail and generate an error message in the system log.
+ * - For opt_c: slave should not be set to the master's setting
+ * while it is running. It was already set during enslave. To
+ * simplify things, it is now handeled separately.
+ *
+ * - 2003/12/01 - Shmulik Hen <shmulik.hen at intel dot com>
+ * - Code cleanup and style changes
+ * set version to 1.1.0
+ */
+
+#include "libbb.h"
+
+/* #include <net/if.h> - no. linux/if_bonding.h pulls in linux/if.h */
+#include <net/if_arp.h>
+#include <linux/if_bonding.h>
+#include <linux/sockios.h>
+
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+typedef uint64_t u64; /* hack, so we may include kernel's ethtool.h */
+typedef uint32_t u32; /* ditto */
+typedef uint16_t u16; /* ditto */
+typedef uint8_t u8; /* ditto */
+#include <linux/ethtool.h>
+
+
+struct dev_data {
+ struct ifreq mtu, flags, hwaddr;
+};
+
+
+enum { skfd = 3 }; /* AF_INET socket for ioctl() calls. */
+struct globals {
+ unsigned abi_ver; /* userland - kernel ABI version */
+ smallint hwaddr_set; /* Master's hwaddr is set */
+ struct dev_data master;
+ struct dev_data slave;
+};
+#define G (*ptr_to_globals)
+#define abi_ver (G.abi_ver )
+#define hwaddr_set (G.hwaddr_set)
+#define master (G.master )
+#define slave (G.slave )
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+/* NOINLINEs are placed where it results in smaller code (gcc 4.3.1) */
+
+static void strncpy_IFNAMSIZ(char *dst, const char *src)
+{
+ strncpy(dst, src, IFNAMSIZ);
+}
+
+static int ioctl_on_skfd(unsigned request, struct ifreq *ifr)
+{
+ return ioctl(skfd, request, ifr);
+}
+
+static int set_ifrname_and_do_ioctl(unsigned request, struct ifreq *ifr, const char *ifname)
+{
+ strncpy_IFNAMSIZ(ifr->ifr_name, ifname);
+ return ioctl_on_skfd(request, ifr);
+}
+
+static int get_if_settings(char *ifname, struct dev_data *dd)
+{
+ int res;
+
+ res = set_ifrname_and_do_ioctl(SIOCGIFMTU, &dd->mtu, ifname);
+ res |= set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &dd->flags, ifname);
+ res |= set_ifrname_and_do_ioctl(SIOCGIFHWADDR, &dd->hwaddr, ifname);
+
+ return res;
+}
+
+static int get_slave_flags(char *slave_ifname)
+{
+ return set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &slave.flags, slave_ifname);
+}
+
+static int set_hwaddr(char *ifname, struct sockaddr *hwaddr)
+{
+ struct ifreq ifr;
+
+ memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(*hwaddr));
+ return set_ifrname_and_do_ioctl(SIOCSIFHWADDR, &ifr, ifname);
+}
+
+static int set_mtu(char *ifname, int mtu)
+{
+ struct ifreq ifr;
+
+ ifr.ifr_mtu = mtu;
+ return set_ifrname_and_do_ioctl(SIOCSIFMTU, &ifr, ifname);
+}
+
+static int set_if_flags(char *ifname, int flags)
+{
+ struct ifreq ifr;
+
+ ifr.ifr_flags = flags;
+ return set_ifrname_and_do_ioctl(SIOCSIFFLAGS, &ifr, ifname);
+}
+
+static int set_if_up(char *ifname, int flags)
+{
+ int res = set_if_flags(ifname, flags | IFF_UP);
+ if (res)
+ bb_perror_msg("%s: can't up", ifname);
+ return res;
+}
+
+static int set_if_down(char *ifname, int flags)
+{
+ int res = set_if_flags(ifname, flags & ~IFF_UP);
+ if (res)
+ bb_perror_msg("%s: can't down", ifname);
+ return res;
+}
+
+static int clear_if_addr(char *ifname)
+{
+ struct ifreq ifr;
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ memset(ifr.ifr_addr.sa_data, 0, sizeof(ifr.ifr_addr.sa_data));
+ return set_ifrname_and_do_ioctl(SIOCSIFADDR, &ifr, ifname);
+}
+
+static int set_if_addr(char *master_ifname, char *slave_ifname)
+{
+#if (SIOCGIFADDR | SIOCSIFADDR \
+ | SIOCGIFDSTADDR | SIOCSIFDSTADDR \
+ | SIOCGIFBRDADDR | SIOCSIFBRDADDR \
+ | SIOCGIFNETMASK | SIOCSIFNETMASK) <= 0xffff
+#define INT uint16_t
+#else
+#define INT int
+#endif
+ static const struct {
+ INT g_ioctl;
+ INT s_ioctl;
+ } ifra[] = {
+ { SIOCGIFADDR, SIOCSIFADDR },
+ { SIOCGIFDSTADDR, SIOCSIFDSTADDR },
+ { SIOCGIFBRDADDR, SIOCSIFBRDADDR },
+ { SIOCGIFNETMASK, SIOCSIFNETMASK },
+ };
+
+ struct ifreq ifr;
+ int res;
+ unsigned i;
+
+ for (i = 0; i < ARRAY_SIZE(ifra); i++) {
+ res = set_ifrname_and_do_ioctl(ifra[i].g_ioctl, &ifr, master_ifname);
+ if (res < 0) {
+ ifr.ifr_addr.sa_family = AF_INET;
+ memset(ifr.ifr_addr.sa_data, 0,
+ sizeof(ifr.ifr_addr.sa_data));
+ }
+
+ res = set_ifrname_and_do_ioctl(ifra[i].s_ioctl, &ifr, slave_ifname);
+ if (res < 0)
+ return res;
+ }
+
+ return 0;
+}
+
+static void change_active(char *master_ifname, char *slave_ifname)
+{
+ struct ifreq ifr;
+
+ if (!(slave.flags.ifr_flags & IFF_SLAVE)) {
+ bb_error_msg_and_die("%s is not a slave", slave_ifname);
+ }
+
+ strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
+ if (set_ifrname_and_do_ioctl(SIOCBONDCHANGEACTIVE, &ifr, master_ifname)
+ && ioctl_on_skfd(BOND_CHANGE_ACTIVE_OLD, &ifr)
+ ) {
+ bb_perror_msg_and_die(
+ "master %s, slave %s: can't "
+ "change active",
+ master_ifname, slave_ifname);
+ }
+}
+
+static NOINLINE int enslave(char *master_ifname, char *slave_ifname)
+{
+ struct ifreq ifr;
+ int res;
+
+ if (slave.flags.ifr_flags & IFF_SLAVE) {
+ bb_error_msg(
+ "%s is already a slave",
+ slave_ifname);
+ return 1;
+ }
+
+ res = set_if_down(slave_ifname, slave.flags.ifr_flags);
+ if (res)
+ return res;
+
+ if (abi_ver < 2) {
+ /* Older bonding versions would panic if the slave has no IP
+ * address, so get the IP setting from the master.
+ */
+ res = set_if_addr(master_ifname, slave_ifname);
+ if (res) {
+ bb_perror_msg("%s: can't set address", slave_ifname);
+ return res;
+ }
+ } else {
+ res = clear_if_addr(slave_ifname);
+ if (res) {
+ bb_perror_msg("%s: can't clear address", slave_ifname);
+ return res;
+ }
+ }
+
+ if (master.mtu.ifr_mtu != slave.mtu.ifr_mtu) {
+ res = set_mtu(slave_ifname, master.mtu.ifr_mtu);
+ if (res) {
+ bb_perror_msg("%s: can't set MTU", slave_ifname);
+ return res;
+ }
+ }
+
+ if (hwaddr_set) {
+ /* Master already has an hwaddr
+ * so set it's hwaddr to the slave
+ */
+ if (abi_ver < 1) {
+ /* The driver is using an old ABI, so
+ * the application sets the slave's
+ * hwaddr
+ */
+ if (set_hwaddr(slave_ifname, &(master.hwaddr.ifr_hwaddr))) {
+ bb_perror_msg("%s: can't set hw address",
+ slave_ifname);
+ goto undo_mtu;
+ }
+
+ /* For old ABI the application needs to bring the
+ * slave back up
+ */
+ if (set_if_up(slave_ifname, slave.flags.ifr_flags))
+ goto undo_slave_mac;
+ }
+ /* The driver is using a new ABI,
+ * so the driver takes care of setting
+ * the slave's hwaddr and bringing
+ * it up again
+ */
+ } else {
+ /* No hwaddr for master yet, so
+ * set the slave's hwaddr to it
+ */
+ if (abi_ver < 1) {
+ /* For old ABI, the master needs to be
+ * down before setting it's hwaddr
+ */
+ if (set_if_down(master_ifname, master.flags.ifr_flags))
+ goto undo_mtu;
+ }
+
+ if (set_hwaddr(master_ifname, &(slave.hwaddr.ifr_hwaddr))) {
+ bb_error_msg("%s: can't set hw address",
+ master_ifname);
+ goto undo_mtu;
+ }
+
+ if (abi_ver < 1) {
+ /* For old ABI, bring the master
+ * back up
+ */
+ if (set_if_up(master_ifname, master.flags.ifr_flags))
+ goto undo_master_mac;
+ }
+
+ hwaddr_set = 1;
+ }
+
+ /* Do the real thing */
+ strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
+ if (set_ifrname_and_do_ioctl(SIOCBONDENSLAVE, &ifr, master_ifname)
+ && ioctl_on_skfd(BOND_ENSLAVE_OLD, &ifr)
+ ) {
+ goto undo_master_mac;
+ }
+
+ return 0;
+
+/* rollback (best effort) */
+ undo_master_mac:
+ set_hwaddr(master_ifname, &(master.hwaddr.ifr_hwaddr));
+ hwaddr_set = 0;
+ goto undo_mtu;
+
+ undo_slave_mac:
+ set_hwaddr(slave_ifname, &(slave.hwaddr.ifr_hwaddr));
+ undo_mtu:
+ set_mtu(slave_ifname, slave.mtu.ifr_mtu);
+ return 1;
+}
+
+static int release(char *master_ifname, char *slave_ifname)
+{
+ struct ifreq ifr;
+ int res = 0;
+
+ if (!(slave.flags.ifr_flags & IFF_SLAVE)) {
+ bb_error_msg("%s is not a slave", slave_ifname);
+ return 1;
+ }
+
+ strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
+ if (set_ifrname_and_do_ioctl(SIOCBONDRELEASE, &ifr, master_ifname) < 0
+ && ioctl_on_skfd(BOND_RELEASE_OLD, &ifr) < 0
+ ) {
+ return 1;
+ }
+ if (abi_ver < 1) {
+ /* The driver is using an old ABI, so we'll set the interface
+ * down to avoid any conflicts due to same MAC/IP
+ */
+ res = set_if_down(slave_ifname, slave.flags.ifr_flags);
+ }
+
+ /* set to default mtu */
+ set_mtu(slave_ifname, 1500);
+
+ return res;
+}
+
+static NOINLINE void get_drv_info(char *master_ifname)
+{
+ struct ifreq ifr;
+ struct ethtool_drvinfo info;
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_data = (caddr_t)&info;
+ info.cmd = ETHTOOL_GDRVINFO;
+ /* both fields are 32 bytes long (long enough) */
+ strcpy(info.driver, "ifenslave");
+ strcpy(info.fw_version, utoa(BOND_ABI_VERSION));
+ if (set_ifrname_and_do_ioctl(SIOCETHTOOL, &ifr, master_ifname) < 0) {
+ if (errno == EOPNOTSUPP)
+ return;
+ bb_perror_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
+ }
+
+ abi_ver = bb_strtou(info.fw_version, NULL, 0);
+ if (errno)
+ bb_error_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
+}
+
+int ifenslave_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifenslave_main(int argc UNUSED_PARAM, char **argv)
+{
+ char *master_ifname, *slave_ifname;
+ int rv;
+ int res;
+ unsigned opt;
+ enum {
+ OPT_c = (1 << 0),
+ OPT_d = (1 << 1),
+ OPT_f = (1 << 2),
+ };
+#if ENABLE_GETOPT_LONG
+ static const char ifenslave_longopts[] ALIGN1 =
+ "change-active\0" No_argument "c"
+ "detach\0" No_argument "d"
+ "force\0" No_argument "f"
+ /* "all-interfaces\0" No_argument "a" */
+ ;
+
+ applet_long_options = ifenslave_longopts;
+#endif
+ INIT_G();
+
+ opt = getopt32(argv, "cdfa");
+ argv += optind;
+ if (opt & (opt-1)) /* Only one option can be given */
+ bb_show_usage();
+
+ master_ifname = *argv++;
+
+ /* No interface names - show all interfaces. */
+ if (!master_ifname) {
+ display_interfaces(NULL);
+ return EXIT_SUCCESS;
+ }
+
+ /* Open a basic socket */
+ xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), skfd);
+
+ /* Exchange abi version with bonding module */
+ get_drv_info(master_ifname);
+
+ slave_ifname = *argv++;
+ if (!slave_ifname) {
+ if (opt & (OPT_d|OPT_c)) {
+ /* --change or --detach, and no slaves given -
+ * show all interfaces. */
+ display_interfaces(slave_ifname /* == NULL */);
+ return 2; /* why 2? */
+ }
+ /* A single arg means show the
+ * configuration for this interface
+ */
+ display_interfaces(master_ifname);
+ return EXIT_SUCCESS;
+ }
+
+ if (get_if_settings(master_ifname, &master)) {
+ /* Probably a good reason not to go on */
+ bb_perror_msg_and_die("%s: can't get settings", master_ifname);
+ }
+
+ /* Check if master is indeed a master;
+ * if not then fail any operation
+ */
+ if (!(master.flags.ifr_flags & IFF_MASTER))
+ bb_error_msg_and_die("%s is not a master", master_ifname);
+
+ /* Check if master is up; if not then fail any operation */
+ if (!(master.flags.ifr_flags & IFF_UP))
+ bb_error_msg_and_die("%s is not up", master_ifname);
+
+#ifdef WHY_BOTHER
+ /* Neither -c[hange] nor -d[etach] -> it's "enslave" then;
+ * and -f[orce] is not there too. Check that it's ethernet. */
+ if (!(opt & (OPT_d|OPT_c|OPT_f)) {
+ /* The family '1' is ARPHRD_ETHER for ethernet. */
+ if (master.hwaddr.ifr_hwaddr.sa_family != 1) {
+ bb_error_msg_and_die(
+ "%s is not ethernet-like (-f overrides)",
+ master_ifname);
+ }
+ }
+#endif
+
+ /* Accepts only one slave */
+ if (opt & OPT_c) {
+ /* Change active slave */
+ if (get_slave_flags(slave_ifname)) {
+ bb_perror_msg_and_die(
+ "%s: can't get flags", slave_ifname);
+ }
+ change_active(master_ifname, slave_ifname);
+ return EXIT_SUCCESS;
+ }
+
+ /* Accepts multiple slaves */
+ res = 0;
+ do {
+ if (opt & OPT_d) {
+ /* Detach a slave interface from the master */
+ rv = get_slave_flags(slave_ifname);
+ if (rv) {
+ /* Can't work with this slave, */
+ /* remember the error and skip it */
+ bb_perror_msg(
+ "skipping %s: can't get flags",
+ slave_ifname);
+ res = rv;
+ continue;
+ }
+ rv = release(master_ifname, slave_ifname);
+ if (rv) {
+ bb_perror_msg("can't release %s from %s",
+ slave_ifname, master_ifname);
+ res = rv;
+ }
+ } else {
+ /* Attach a slave interface to the master */
+ rv = get_if_settings(slave_ifname, &slave);
+ if (rv) {
+ /* Can't work with this slave, */
+ /* remember the error and skip it */
+ bb_perror_msg(
+ "skipping %s: can't get settings",
+ slave_ifname);
+ res = rv;
+ continue;
+ }
+ rv = enslave(master_ifname, slave_ifname);
+ if (rv) {
+ bb_perror_msg("can't enslave %s to %s",
+ slave_ifname, master_ifname);
+ res = rv;
+ }
+ }
+ } while ((slave_ifname = *argv++) != NULL);
+
+ if (ENABLE_FEATURE_CLEAN_UP) {
+ close(skfd);
+ }
+
+ return res;
+}
diff --git a/networking/ifupdown.c b/networking/ifupdown.c
new file mode 100644
index 0000000..d7cb40f
--- /dev/null
+++ b/networking/ifupdown.c
@@ -0,0 +1,1313 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ifupdown for busybox
+ * Copyright (c) 2002 Glenn McGrath
+ * Copyright (c) 2003-2004 Erik Andersen <andersen@codepoet.org>
+ *
+ * Based on ifupdown v 0.6.4 by Anthony Towns
+ * Copyright (c) 1999 Anthony Towns <aj@azure.humbug.org.au>
+ *
+ * Changes to upstream version
+ * Remove checks for kernel version, assume kernel version 2.2.0 or better.
+ * Lines in the interfaces file cannot wrap.
+ * To adhere to the FHS, the default state file is /var/run/ifstate
+ * (defined via CONFIG_IFUPDOWN_IFSTATE_PATH) and can be overridden by build
+ * configuration.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include <sys/utsname.h>
+#include <fnmatch.h>
+
+#include "libbb.h"
+
+#define MAX_OPT_DEPTH 10
+#define EUNBALBRACK 10001
+#define EUNDEFVAR 10002
+#define EUNBALPER 10000
+
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+#define MAX_INTERFACE_LENGTH 10
+#endif
+
+#define debug_noise(args...) /*fprintf(stderr, args)*/
+
+/* Forward declaration */
+struct interface_defn_t;
+
+typedef int execfn(char *command);
+
+struct method_t {
+ const char *name;
+ int (*up)(struct interface_defn_t *ifd, execfn *e);
+ int (*down)(struct interface_defn_t *ifd, execfn *e);
+};
+
+struct address_family_t {
+ const char *name;
+ int n_methods;
+ const struct method_t *method;
+};
+
+struct mapping_defn_t {
+ struct mapping_defn_t *next;
+
+ int max_matches;
+ int n_matches;
+ char **match;
+
+ char *script;
+
+ int max_mappings;
+ int n_mappings;
+ char **mapping;
+};
+
+struct variable_t {
+ char *name;
+ char *value;
+};
+
+struct interface_defn_t {
+ const struct address_family_t *address_family;
+ const struct method_t *method;
+
+ char *iface;
+ int max_options;
+ int n_options;
+ struct variable_t *option;
+};
+
+struct interfaces_file_t {
+ llist_t *autointerfaces;
+ llist_t *ifaces;
+ struct mapping_defn_t *mappings;
+};
+
+#define OPTION_STR "anvf" USE_FEATURE_IFUPDOWN_MAPPING("m") "i:"
+enum {
+ OPT_do_all = 0x1,
+ OPT_no_act = 0x2,
+ OPT_verbose = 0x4,
+ OPT_force = 0x8,
+ OPT_no_mappings = 0x10,
+};
+#define DO_ALL (option_mask32 & OPT_do_all)
+#define NO_ACT (option_mask32 & OPT_no_act)
+#define VERBOSE (option_mask32 & OPT_verbose)
+#define FORCE (option_mask32 & OPT_force)
+#define NO_MAPPINGS (option_mask32 & OPT_no_mappings)
+
+static char **my_environ;
+
+static const char *startup_PATH;
+
+#if ENABLE_FEATURE_IFUPDOWN_IPV4 || ENABLE_FEATURE_IFUPDOWN_IPV6
+
+static void addstr(char **bufp, const char *str, size_t str_length)
+{
+ /* xasprintf trick will be smaller, but we are often
+ * called with str_length == 1 - don't want to have
+ * THAT much of malloc/freeing! */
+ char *buf = *bufp;
+ int len = (buf ? strlen(buf) : 0);
+ str_length++;
+ buf = xrealloc(buf, len + str_length);
+ /* copies at most str_length-1 chars! */
+ safe_strncpy(buf + len, str, str_length);
+ *bufp = buf;
+}
+
+static int strncmpz(const char *l, const char *r, size_t llen)
+{
+ int i = strncmp(l, r, llen);
+
+ if (i == 0)
+ return -r[llen];
+ return i;
+}
+
+static char *get_var(const char *id, size_t idlen, struct interface_defn_t *ifd)
+{
+ int i;
+
+ if (strncmpz(id, "iface", idlen) == 0) {
+ static char *label_buf;
+ //char *result;
+
+ free(label_buf);
+ label_buf = xstrdup(ifd->iface);
+ // Remove virtual iface suffix - why?
+ // ubuntu's ifup doesn't do this
+ //result = strchrnul(label_buf, ':');
+ //*result = '\0';
+ return label_buf;
+ }
+ if (strncmpz(id, "label", idlen) == 0) {
+ return ifd->iface;
+ }
+ for (i = 0; i < ifd->n_options; i++) {
+ if (strncmpz(id, ifd->option[i].name, idlen) == 0) {
+ return ifd->option[i].value;
+ }
+ }
+ return NULL;
+}
+
+#if ENABLE_FEATURE_IFUPDOWN_IP
+static int count_netmask_bits(const char *dotted_quad)
+{
+// int result;
+// unsigned a, b, c, d;
+// /* Found a netmask... Check if it is dotted quad */
+// if (sscanf(dotted_quad, "%u.%u.%u.%u", &a, &b, &c, &d) != 4)
+// return -1;
+// if ((a|b|c|d) >> 8)
+// return -1; /* one of numbers is >= 256 */
+// d |= (a << 24) | (b << 16) | (c << 8); /* IP */
+// d = ~d; /* 11110000 -> 00001111 */
+
+ /* Shorter version */
+ int result;
+ struct in_addr ip;
+ unsigned d;
+
+ if (inet_aton(dotted_quad, &ip) == 0)
+ return -1; /* malformed dotted IP */
+ d = ntohl(ip.s_addr); /* IP in host order */
+ d = ~d; /* 11110000 -> 00001111 */
+ if (d & (d+1)) /* check that it is in 00001111 form */
+ return -1; /* no it is not */
+ result = 32;
+ while (d) {
+ d >>= 1;
+ result--;
+ }
+ return result;
+}
+#endif
+
+static char *parse(const char *command, struct interface_defn_t *ifd)
+{
+ size_t old_pos[MAX_OPT_DEPTH] = { 0 };
+ int okay[MAX_OPT_DEPTH] = { 1 };
+ int opt_depth = 1;
+ char *result = NULL;
+
+ while (*command) {
+ switch (*command) {
+ default:
+ addstr(&result, command, 1);
+ command++;
+ break;
+ case '\\':
+ if (command[1]) {
+ addstr(&result, command + 1, 1);
+ command += 2;
+ } else {
+ addstr(&result, command, 1);
+ command++;
+ }
+ break;
+ case '[':
+ if (command[1] == '[' && opt_depth < MAX_OPT_DEPTH) {
+ old_pos[opt_depth] = result ? strlen(result) : 0;
+ okay[opt_depth] = 1;
+ opt_depth++;
+ command += 2;
+ } else {
+ addstr(&result, "[", 1);
+ command++;
+ }
+ break;
+ case ']':
+ if (command[1] == ']' && opt_depth > 1) {
+ opt_depth--;
+ if (!okay[opt_depth]) {
+ result[old_pos[opt_depth]] = '\0';
+ }
+ command += 2;
+ } else {
+ addstr(&result, "]", 1);
+ command++;
+ }
+ break;
+ case '%':
+ {
+ char *nextpercent;
+ char *varvalue;
+
+ command++;
+ nextpercent = strchr(command, '%');
+ if (!nextpercent) {
+ errno = EUNBALPER;
+ free(result);
+ return NULL;
+ }
+
+ varvalue = get_var(command, nextpercent - command, ifd);
+
+ if (varvalue) {
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ /* "hwaddress <class> <address>":
+ * unlike ifconfig, ip doesnt want <class>
+ * (usually "ether" keyword). Skip it. */
+ if (strncmp(command, "hwaddress", 9) == 0) {
+ varvalue = skip_whitespace(skip_non_whitespace(varvalue));
+ }
+#endif
+ addstr(&result, varvalue, strlen(varvalue));
+ } else {
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ /* Sigh... Add a special case for 'ip' to convert from
+ * dotted quad to bit count style netmasks. */
+ if (strncmp(command, "bnmask", 6) == 0) {
+ unsigned res;
+ varvalue = get_var("netmask", 7, ifd);
+ if (varvalue) {
+ res = count_netmask_bits(varvalue);
+ if (res > 0) {
+ const char *argument = utoa(res);
+ addstr(&result, argument, strlen(argument));
+ command = nextpercent + 1;
+ break;
+ }
+ }
+ }
+#endif
+ okay[opt_depth - 1] = 0;
+ }
+
+ command = nextpercent + 1;
+ }
+ break;
+ }
+ }
+
+ if (opt_depth > 1) {
+ errno = EUNBALBRACK;
+ free(result);
+ return NULL;
+ }
+
+ if (!okay[0]) {
+ errno = EUNDEFVAR;
+ free(result);
+ return NULL;
+ }
+
+ return result;
+}
+
+/* execute() returns 1 for success and 0 for failure */
+static int execute(const char *command, struct interface_defn_t *ifd, execfn *exec)
+{
+ char *out;
+ int ret;
+
+ out = parse(command, ifd);
+ if (!out) {
+ /* parse error? */
+ return 0;
+ }
+ /* out == "": parsed ok but not all needed variables known, skip */
+ ret = out[0] ? (*exec)(out) : 1;
+
+ free(out);
+ if (ret != 1) {
+ return 0;
+ }
+ return 1;
+}
+#endif
+
+#if ENABLE_FEATURE_IFUPDOWN_IPV6
+static int loopback_up6(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ int result;
+ result = execute("ip addr add ::1 dev %iface%", ifd, exec);
+ result += execute("ip link set %iface% up", ifd, exec);
+ return ((result == 2) ? 2 : 0);
+#else
+ return execute("ifconfig %iface% add ::1", ifd, exec);
+#endif
+}
+
+static int loopback_down6(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ return execute("ip link set %iface% down", ifd, exec);
+#else
+ return execute("ifconfig %iface% del ::1", ifd, exec);
+#endif
+}
+
+static int static_up6(struct interface_defn_t *ifd, execfn *exec)
+{
+ int result;
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ result = execute("ip addr add %address%/%netmask% dev %iface%[[ label %label%]]", ifd, exec);
+ result += execute("ip link set[[ mtu %mtu%]][[ addr %hwaddress%]] %iface% up", ifd, exec);
+ /* Was: "[[ ip ....%gateway% ]]". Removed extra spaces w/o checking */
+ result += execute("[[ip route add ::/0 via %gateway%]]", ifd, exec);
+#else
+ result = execute("ifconfig %iface%[[ media %media%]][[ hw %hwaddress%]][[ mtu %mtu%]] up", ifd, exec);
+ result += execute("ifconfig %iface% add %address%/%netmask%", ifd, exec);
+ result += execute("[[route -A inet6 add ::/0 gw %gateway%]]", ifd, exec);
+#endif
+ return ((result == 3) ? 3 : 0);
+}
+
+static int static_down6(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ return execute("ip link set %iface% down", ifd, exec);
+#else
+ return execute("ifconfig %iface% down", ifd, exec);
+#endif
+}
+
+#if ENABLE_FEATURE_IFUPDOWN_IP
+static int v4tunnel_up(struct interface_defn_t *ifd, execfn *exec)
+{
+ int result;
+ result = execute("ip tunnel add %iface% mode sit remote "
+ "%endpoint%[[ local %local%]][[ ttl %ttl%]]", ifd, exec);
+ result += execute("ip link set %iface% up", ifd, exec);
+ result += execute("ip addr add %address%/%netmask% dev %iface%", ifd, exec);
+ result += execute("[[ip route add ::/0 via %gateway%]]", ifd, exec);
+ return ((result == 4) ? 4 : 0);
+}
+
+static int v4tunnel_down(struct interface_defn_t * ifd, execfn * exec)
+{
+ return execute("ip tunnel del %iface%", ifd, exec);
+}
+#endif
+
+static const struct method_t methods6[] = {
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ { "v4tunnel", v4tunnel_up, v4tunnel_down, },
+#endif
+ { "static", static_up6, static_down6, },
+ { "loopback", loopback_up6, loopback_down6, },
+};
+
+static const struct address_family_t addr_inet6 = {
+ "inet6",
+ ARRAY_SIZE(methods6),
+ methods6
+};
+#endif /* FEATURE_IFUPDOWN_IPV6 */
+
+#if ENABLE_FEATURE_IFUPDOWN_IPV4
+static int loopback_up(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ int result;
+ result = execute("ip addr add 127.0.0.1/8 dev %iface%", ifd, exec);
+ result += execute("ip link set %iface% up", ifd, exec);
+ return ((result == 2) ? 2 : 0);
+#else
+ return execute("ifconfig %iface% 127.0.0.1 up", ifd, exec);
+#endif
+}
+
+static int loopback_down(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ int result;
+ result = execute("ip addr flush dev %iface%", ifd, exec);
+ result += execute("ip link set %iface% down", ifd, exec);
+ return ((result == 2) ? 2 : 0);
+#else
+ return execute("ifconfig %iface% 127.0.0.1 down", ifd, exec);
+#endif
+}
+
+static int static_up(struct interface_defn_t *ifd, execfn *exec)
+{
+ int result;
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ result = execute("ip addr add %address%/%bnmask%[[ broadcast %broadcast%]] "
+ "dev %iface%[[ peer %pointopoint%]][[ label %label%]]", ifd, exec);
+ result += execute("ip link set[[ mtu %mtu%]][[ addr %hwaddress%]] %iface% up", ifd, exec);
+ result += execute("[[ip route add default via %gateway% dev %iface%]]", ifd, exec);
+ return ((result == 3) ? 3 : 0);
+#else
+ /* ifconfig said to set iface up before it processes hw %hwaddress%,
+ * which then of course fails. Thus we run two separate ifconfig */
+ result = execute("ifconfig %iface%[[ hw %hwaddress%]][[ media %media%]][[ mtu %mtu%]] up",
+ ifd, exec);
+ result += execute("ifconfig %iface% %address% netmask %netmask%"
+ "[[ broadcast %broadcast%]][[ pointopoint %pointopoint%]] ",
+ ifd, exec);
+ result += execute("[[route add default gw %gateway% %iface%]]", ifd, exec);
+ return ((result == 3) ? 3 : 0);
+#endif
+}
+
+static int static_down(struct interface_defn_t *ifd, execfn *exec)
+{
+ int result;
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ result = execute("ip addr flush dev %iface%", ifd, exec);
+ result += execute("ip link set %iface% down", ifd, exec);
+#else
+ /* result = execute("[[route del default gw %gateway% %iface%]]", ifd, exec); */
+ /* Bringing the interface down deletes the routes in itself.
+ Otherwise this fails if we reference 'gateway' when using this from dhcp_down */
+ result = 1;
+ result += execute("ifconfig %iface% down", ifd, exec);
+#endif
+ return ((result == 2) ? 2 : 0);
+}
+
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+struct dhcp_client_t
+{
+ const char *name;
+ const char *startcmd;
+ const char *stopcmd;
+};
+
+static const struct dhcp_client_t ext_dhcp_clients[] = {
+ { "dhcpcd",
+ "dhcpcd[[ -h %hostname%]][[ -i %vendor%]][[ -I %clientid%]][[ -l %leasetime%]] %iface%",
+ "dhcpcd -k %iface%",
+ },
+ { "dhclient",
+ "dhclient -pf /var/run/dhclient.%iface%.pid %iface%",
+ "kill -9 `cat /var/run/dhclient.%iface%.pid` 2>/dev/null",
+ },
+ { "pump",
+ "pump -i %iface%[[ -h %hostname%]][[ -l %leasehours%]]",
+ "pump -i %iface% -k",
+ },
+ { "udhcpc",
+ "udhcpc -R -n -p /var/run/udhcpc.%iface%.pid -i %iface%[[ -H %hostname%]][[ -c %clientid%]]"
+ "[[ -s %script%]][[ %udhcpc_opts%]]",
+ "kill `cat /var/run/udhcpc.%iface%.pid` 2>/dev/null",
+ },
+};
+#endif /* ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCPC */
+
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+static int dhcp_up(struct interface_defn_t *ifd, execfn *exec)
+{
+ unsigned i;
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ /* ip doesn't up iface when it configures it (unlike ifconfig) */
+ if (!execute("ip link set[[ addr %hwaddress%]] %iface% up", ifd, exec))
+ return 0;
+#else
+ /* needed if we have hwaddress on dhcp iface */
+ if (!execute("ifconfig %iface%[[ hw %hwaddress%]] up", ifd, exec))
+ return 0;
+#endif
+ for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) {
+ if (exists_execable(ext_dhcp_clients[i].name))
+ return execute(ext_dhcp_clients[i].startcmd, ifd, exec);
+ }
+ bb_error_msg("no dhcp clients found");
+ return 0;
+}
+#elif ENABLE_APP_UDHCPC
+static int dhcp_up(struct interface_defn_t *ifd, execfn *exec)
+{
+#if ENABLE_FEATURE_IFUPDOWN_IP
+ /* ip doesn't up iface when it configures it (unlike ifconfig) */
+ if (!execute("ip link set[[ addr %hwaddress%]] %iface% up", ifd, exec))
+ return 0;
+#else
+ /* needed if we have hwaddress on dhcp iface */
+ if (!execute("ifconfig %iface%[[ hw %hwaddress%]] up", ifd, exec))
+ return 0;
+#endif
+ return execute("udhcpc -R -n -p /var/run/udhcpc.%iface%.pid "
+ "-i %iface%[[ -H %hostname%]][[ -c %clientid%]][[ -s %script%]][[ %udhcpc_opts%]]",
+ ifd, exec);
+}
+#else
+static int dhcp_up(struct interface_defn_t *ifd UNUSED_PARAM,
+ execfn *exec UNUSED_PARAM)
+{
+ return 0; /* no dhcp support */
+}
+#endif
+
+#if ENABLE_FEATURE_IFUPDOWN_EXTERNAL_DHCP
+static int dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+ int result = 0;
+ unsigned i;
+
+ for (i = 0; i < ARRAY_SIZE(ext_dhcp_clients); i++) {
+ if (exists_execable(ext_dhcp_clients[i].name)) {
+ result += execute(ext_dhcp_clients[i].stopcmd, ifd, exec);
+ if (result)
+ break;
+ }
+ }
+
+ if (!result)
+ bb_error_msg("warning: no dhcp clients found and stopped");
+
+ /* Sleep a bit, otherwise static_down tries to bring down interface too soon,
+ and it may come back up because udhcpc is still shutting down */
+ usleep(100000);
+ result += static_down(ifd, exec);
+ return ((result == 3) ? 3 : 0);
+}
+#elif ENABLE_APP_UDHCPC
+static int dhcp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+ int result;
+ result = execute("kill "
+ "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec);
+ /* Also bring the hardware interface down since
+ killing the dhcp client alone doesn't do it.
+ This enables consecutive ifup->ifdown->ifup */
+ /* Sleep a bit, otherwise static_down tries to bring down interface too soon,
+ and it may come back up because udhcpc is still shutting down */
+ usleep(100000);
+ result += static_down(ifd, exec);
+ return ((result == 3) ? 3 : 0);
+}
+#else
+static int dhcp_down(struct interface_defn_t *ifd UNUSED_PARAM,
+ execfn *exec UNUSED_PARAM)
+{
+ return 0; /* no dhcp support */
+}
+#endif
+
+static int manual_up_down(struct interface_defn_t *ifd UNUSED_PARAM, execfn *exec UNUSED_PARAM)
+{
+ return 1;
+}
+
+static int bootp_up(struct interface_defn_t *ifd, execfn *exec)
+{
+ return execute("bootpc[[ --bootfile %bootfile%]] --dev %iface%"
+ "[[ --server %server%]][[ --hwaddr %hwaddr%]]"
+ " --returniffail --serverbcast", ifd, exec);
+}
+
+static int ppp_up(struct interface_defn_t *ifd, execfn *exec)
+{
+ return execute("pon[[ %provider%]]", ifd, exec);
+}
+
+static int ppp_down(struct interface_defn_t *ifd, execfn *exec)
+{
+ return execute("poff[[ %provider%]]", ifd, exec);
+}
+
+static int wvdial_up(struct interface_defn_t *ifd, execfn *exec)
+{
+ return execute("start-stop-daemon --start -x wvdial "
+ "-p /var/run/wvdial.%iface% -b -m --[[ %provider%]]", ifd, exec);
+}
+
+static int wvdial_down(struct interface_defn_t *ifd, execfn *exec)
+{
+ return execute("start-stop-daemon --stop -x wvdial "
+ "-p /var/run/wvdial.%iface% -s 2", ifd, exec);
+}
+
+static const struct method_t methods[] = {
+ { "manual", manual_up_down, manual_up_down, },
+ { "wvdial", wvdial_up, wvdial_down, },
+ { "ppp", ppp_up, ppp_down, },
+ { "static", static_up, static_down, },
+ { "bootp", bootp_up, static_down, },
+ { "dhcp", dhcp_up, dhcp_down, },
+ { "loopback", loopback_up, loopback_down, },
+};
+
+static const struct address_family_t addr_inet = {
+ "inet",
+ ARRAY_SIZE(methods),
+ methods
+};
+
+#endif /* if ENABLE_FEATURE_IFUPDOWN_IPV4 */
+
+static char *next_word(char **buf)
+{
+ unsigned length;
+ char *word;
+
+ /* Skip over leading whitespace */
+ word = skip_whitespace(*buf);
+
+ /* Stop on EOL */
+ if (*word == '\0')
+ return NULL;
+
+ /* Find the length of this word (can't be 0) */
+ length = strcspn(word, " \t\n");
+
+ /* Unless we are already at NUL, store NUL and advance */
+ if (word[length] != '\0')
+ word[length++] = '\0';
+
+ *buf = word + length;
+
+ return word;
+}
+
+static const struct address_family_t *get_address_family(const struct address_family_t *const af[], char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+
+ for (i = 0; af[i]; i++) {
+ if (strcmp(af[i]->name, name) == 0) {
+ return af[i];
+ }
+ }
+ return NULL;
+}
+
+static const struct method_t *get_method(const struct address_family_t *af, char *name)
+{
+ int i;
+
+ if (!name)
+ return NULL;
+ /* TODO: use index_in_str_array() */
+ for (i = 0; i < af->n_methods; i++) {
+ if (strcmp(af->method[i].name, name) == 0) {
+ return &af->method[i];
+ }
+ }
+ return NULL;
+}
+
+static const llist_t *find_list_string(const llist_t *list, const char *string)
+{
+ if (string == NULL)
+ return NULL;
+
+ while (list) {
+ if (strcmp(list->data, string) == 0) {
+ return list;
+ }
+ list = list->link;
+ }
+ return NULL;
+}
+
+static struct interfaces_file_t *read_interfaces(const char *filename)
+{
+ /* Let's try to be compatible.
+ *
+ * "man 5 interfaces" says:
+ * Lines starting with "#" are ignored. Note that end-of-line
+ * comments are NOT supported, comments must be on a line of their own.
+ * A line may be extended across multiple lines by making
+ * the last character a backslash.
+ *
+ * Seen elsewhere in example config file:
+ * A first non-blank "#" character makes the rest of the line
+ * be ignored. Blank lines are ignored. Lines may be indented freely.
+ * A "\" character at the very end of the line indicates the next line
+ * should be treated as a continuation of the current one.
+ */
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+ struct mapping_defn_t *currmap = NULL;
+#endif
+ struct interface_defn_t *currif = NULL;
+ struct interfaces_file_t *defn;
+ FILE *f;
+ char *buf;
+ char *first_word;
+ char *rest_of_line;
+ enum { NONE, IFACE, MAPPING } currently_processing = NONE;
+
+ defn = xzalloc(sizeof(*defn));
+ f = xfopen_for_read(filename);
+
+ while ((buf = xmalloc_fgetline(f)) != NULL) {
+#if ENABLE_DESKTOP
+ /* Trailing "\" concatenates lines */
+ char *p;
+ while ((p = last_char_is(buf, '\\')) != NULL) {
+ *p = '\0';
+ rest_of_line = xmalloc_fgetline(f);
+ if (!rest_of_line)
+ break;
+ p = xasprintf("%s%s", buf, rest_of_line);
+ free(buf);
+ free(rest_of_line);
+ buf = p;
+ }
+#endif
+ rest_of_line = buf;
+ first_word = next_word(&rest_of_line);
+ if (!first_word || *first_word == '#') {
+ free(buf);
+ continue; /* blank/comment line */
+ }
+
+ if (strcmp(first_word, "mapping") == 0) {
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+ currmap = xzalloc(sizeof(*currmap));
+
+ while ((first_word = next_word(&rest_of_line)) != NULL) {
+ currmap->match = xrealloc_vector(currmap->match, 4, currmap->n_matches);
+ currmap->match[currmap->n_matches++] = xstrdup(first_word);
+ }
+ /*currmap->max_mappings = 0; - done by xzalloc */
+ /*currmap->n_mappings = 0;*/
+ /*currmap->mapping = NULL;*/
+ /*currmap->script = NULL;*/
+ {
+ struct mapping_defn_t **where = &defn->mappings;
+ while (*where != NULL) {
+ where = &(*where)->next;
+ }
+ *where = currmap;
+ /*currmap->next = NULL;*/
+ }
+ debug_noise("Added mapping\n");
+#endif
+ currently_processing = MAPPING;
+ } else if (strcmp(first_word, "iface") == 0) {
+ static const struct address_family_t *const addr_fams[] = {
+#if ENABLE_FEATURE_IFUPDOWN_IPV4
+ &addr_inet,
+#endif
+#if ENABLE_FEATURE_IFUPDOWN_IPV6
+ &addr_inet6,
+#endif
+ NULL
+ };
+ char *iface_name;
+ char *address_family_name;
+ char *method_name;
+ llist_t *iface_list;
+
+ currif = xzalloc(sizeof(*currif));
+ iface_name = next_word(&rest_of_line);
+ address_family_name = next_word(&rest_of_line);
+ method_name = next_word(&rest_of_line);
+
+ if (method_name == NULL)
+ bb_error_msg_and_die("too few parameters for line \"%s\"", buf);
+
+ /* ship any trailing whitespace */
+ rest_of_line = skip_whitespace(rest_of_line);
+
+ if (rest_of_line[0] != '\0' /* && rest_of_line[0] != '#' */)
+ bb_error_msg_and_die("too many parameters \"%s\"", buf);
+
+ currif->iface = xstrdup(iface_name);
+
+ currif->address_family = get_address_family(addr_fams, address_family_name);
+ if (!currif->address_family)
+ bb_error_msg_and_die("unknown address type \"%s\"", address_family_name);
+
+ currif->method = get_method(currif->address_family, method_name);
+ if (!currif->method)
+ bb_error_msg_and_die("unknown method \"%s\"", method_name);
+
+ for (iface_list = defn->ifaces; iface_list; iface_list = iface_list->link) {
+ struct interface_defn_t *tmp = (struct interface_defn_t *) iface_list->data;
+ if ((strcmp(tmp->iface, currif->iface) == 0)
+ && (tmp->address_family == currif->address_family)
+ ) {
+ bb_error_msg_and_die("duplicate interface \"%s\"", tmp->iface);
+ }
+ }
+ llist_add_to_end(&(defn->ifaces), (char*)currif);
+
+ debug_noise("iface %s %s %s\n", currif->iface, address_family_name, method_name);
+ currently_processing = IFACE;
+ } else if (strcmp(first_word, "auto") == 0) {
+ while ((first_word = next_word(&rest_of_line)) != NULL) {
+
+ /* Check the interface isnt already listed */
+ if (find_list_string(defn->autointerfaces, first_word)) {
+ bb_perror_msg_and_die("interface declared auto twice \"%s\"", buf);
+ }
+
+ /* Add the interface to the list */
+ llist_add_to_end(&(defn->autointerfaces), xstrdup(first_word));
+ debug_noise("\nauto %s\n", first_word);
+ }
+ currently_processing = NONE;
+ } else {
+ switch (currently_processing) {
+ case IFACE:
+ if (rest_of_line[0] == '\0')
+ bb_error_msg_and_die("option with empty value \"%s\"", buf);
+
+ if (strcmp(first_word, "up") != 0
+ && strcmp(first_word, "down") != 0
+ && strcmp(first_word, "pre-up") != 0
+ && strcmp(first_word, "post-down") != 0
+ ) {
+ int i;
+ for (i = 0; i < currif->n_options; i++) {
+ if (strcmp(currif->option[i].name, first_word) == 0)
+ bb_error_msg_and_die("duplicate option \"%s\"", buf);
+ }
+ }
+ if (currif->n_options >= currif->max_options) {
+ currif->max_options += 10;
+ currif->option = xrealloc(currif->option,
+ sizeof(*currif->option) * currif->max_options);
+ }
+ debug_noise("\t%s=%s\n", first_word, rest_of_line);
+ currif->option[currif->n_options].name = xstrdup(first_word);
+ currif->option[currif->n_options].value = xstrdup(rest_of_line);
+ currif->n_options++;
+ break;
+ case MAPPING:
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+ if (strcmp(first_word, "script") == 0) {
+ if (currmap->script != NULL)
+ bb_error_msg_and_die("duplicate script in mapping \"%s\"", buf);
+ currmap->script = xstrdup(next_word(&rest_of_line));
+ } else if (strcmp(first_word, "map") == 0) {
+ if (currmap->n_mappings >= currmap->max_mappings) {
+ currmap->max_mappings = currmap->max_mappings * 2 + 1;
+ currmap->mapping = xrealloc(currmap->mapping,
+ sizeof(char *) * currmap->max_mappings);
+ }
+ currmap->mapping[currmap->n_mappings] = xstrdup(next_word(&rest_of_line));
+ currmap->n_mappings++;
+ } else {
+ bb_error_msg_and_die("misplaced option \"%s\"", buf);
+ }
+#endif
+ break;
+ case NONE:
+ default:
+ bb_error_msg_and_die("misplaced option \"%s\"", buf);
+ }
+ }
+ free(buf);
+ } /* while (fgets) */
+
+ if (ferror(f) != 0) {
+ /* ferror does NOT set errno! */
+ bb_error_msg_and_die("%s: I/O error", filename);
+ }
+ fclose(f);
+
+ return defn;
+}
+
+static char *setlocalenv(const char *format, const char *name, const char *value)
+{
+ char *result;
+ char *here;
+ char *there;
+
+ result = xasprintf(format, name, value);
+
+ for (here = there = result; *there != '=' && *there; there++) {
+ if (*there == '-')
+ *there = '_';
+ if (isalpha(*there))
+ *there = toupper(*there);
+
+ if (isalnum(*there) || *there == '_') {
+ *here = *there;
+ here++;
+ }
+ }
+ memmove(here, there, strlen(there) + 1);
+
+ return result;
+}
+
+static void set_environ(struct interface_defn_t *iface, const char *mode)
+{
+ char **environend;
+ int i;
+ const int n_env_entries = iface->n_options + 5;
+ char **ppch;
+
+ if (my_environ != NULL) {
+ for (ppch = my_environ; *ppch; ppch++) {
+ free(*ppch);
+ *ppch = NULL;
+ }
+ free(my_environ);
+ }
+ my_environ = xzalloc(sizeof(char *) * (n_env_entries + 1 /* for final NULL */ ));
+ environend = my_environ;
+
+ for (i = 0; i < iface->n_options; i++) {
+ if (strcmp(iface->option[i].name, "up") == 0
+ || strcmp(iface->option[i].name, "down") == 0
+ || strcmp(iface->option[i].name, "pre-up") == 0
+ || strcmp(iface->option[i].name, "post-down") == 0
+ ) {
+ continue;
+ }
+ *(environend++) = setlocalenv("IF_%s=%s", iface->option[i].name, iface->option[i].value);
+ }
+
+ *(environend++) = setlocalenv("%s=%s", "IFACE", iface->iface);
+ *(environend++) = setlocalenv("%s=%s", "ADDRFAM", iface->address_family->name);
+ *(environend++) = setlocalenv("%s=%s", "METHOD", iface->method->name);
+ *(environend++) = setlocalenv("%s=%s", "MODE", mode);
+ *(environend++) = setlocalenv("%s=%s", "PATH", startup_PATH);
+}
+
+static int doit(char *str)
+{
+ if (option_mask32 & (OPT_no_act|OPT_verbose)) {
+ puts(str);
+ }
+ if (!(option_mask32 & OPT_no_act)) {
+ pid_t child;
+ int status;
+
+ fflush(NULL);
+ child = vfork();
+ switch (child) {
+ case -1: /* failure */
+ return 0;
+ case 0: /* child */
+ execle(DEFAULT_SHELL, DEFAULT_SHELL, "-c", str, NULL, my_environ);
+ _exit(127);
+ }
+ safe_waitpid(child, &status, 0);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int execute_all(struct interface_defn_t *ifd, const char *opt)
+{
+ int i;
+ char *buf;
+ for (i = 0; i < ifd->n_options; i++) {
+ if (strcmp(ifd->option[i].name, opt) == 0) {
+ if (!doit(ifd->option[i].value)) {
+ return 0;
+ }
+ }
+ }
+
+ buf = xasprintf("run-parts /etc/network/if-%s.d", opt);
+ /* heh, we don't bother free'ing it */
+ return doit(buf);
+}
+
+static int check(char *str)
+{
+ return str != NULL;
+}
+
+static int iface_up(struct interface_defn_t *iface)
+{
+ if (!iface->method->up(iface, check)) return -1;
+ set_environ(iface, "start");
+ if (!execute_all(iface, "pre-up")) return 0;
+ if (!iface->method->up(iface, doit)) return 0;
+ if (!execute_all(iface, "up")) return 0;
+ return 1;
+}
+
+static int iface_down(struct interface_defn_t *iface)
+{
+ if (!iface->method->down(iface,check)) return -1;
+ set_environ(iface, "stop");
+ if (!execute_all(iface, "down")) return 0;
+ if (!iface->method->down(iface, doit)) return 0;
+ if (!execute_all(iface, "post-down")) return 0;
+ return 1;
+}
+
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+static int popen2(FILE **in, FILE **out, char *command, char *param)
+{
+ char *argv[3] = { command, param, NULL };
+ struct fd_pair infd, outfd;
+ pid_t pid;
+
+ xpiped_pair(infd);
+ xpiped_pair(outfd);
+
+ fflush(NULL);
+ pid = vfork();
+
+ switch (pid) {
+ case -1: /* failure */
+ bb_perror_msg_and_die("vfork");
+ case 0: /* child */
+ /* NB: close _first_, then move fds! */
+ close(infd.wr);
+ close(outfd.rd);
+ xmove_fd(infd.rd, 0);
+ xmove_fd(outfd.wr, 1);
+ BB_EXECVP(command, argv);
+ _exit(127);
+ }
+ /* parent */
+ close(infd.rd);
+ close(outfd.wr);
+ *in = fdopen(infd.wr, "w");
+ *out = fdopen(outfd.rd, "r");
+ return pid;
+}
+
+static char *run_mapping(char *physical, struct mapping_defn_t *map)
+{
+ FILE *in, *out;
+ int i, status;
+ pid_t pid;
+
+ char *logical = xstrdup(physical);
+
+ /* Run the mapping script. Never fails. */
+ pid = popen2(&in, &out, map->script, physical);
+
+ /* Write mappings to stdin of mapping script. */
+ for (i = 0; i < map->n_mappings; i++) {
+ fprintf(in, "%s\n", map->mapping[i]);
+ }
+ fclose(in);
+ safe_waitpid(pid, &status, 0);
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
+ /* If the mapping script exited successfully, try to
+ * grab a line of output and use that as the name of the
+ * logical interface. */
+ char *new_logical = xmalloc_fgetline(out);
+
+ if (new_logical) {
+ /* If we are able to read a line of output from the script,
+ * remove any trailing whitespace and use this value
+ * as the name of the logical interface. */
+ char *pch = new_logical + strlen(new_logical) - 1;
+
+ while (pch >= new_logical && isspace(*pch))
+ *(pch--) = '\0';
+
+ free(logical);
+ logical = new_logical;
+ }
+ }
+
+ fclose(out);
+
+ return logical;
+}
+#endif /* FEATURE_IFUPDOWN_MAPPING */
+
+static llist_t *find_iface_state(llist_t *state_list, const char *iface)
+{
+ unsigned iface_len = strlen(iface);
+ llist_t *search = state_list;
+
+ while (search) {
+ if ((strncmp(search->data, iface, iface_len) == 0)
+ && (search->data[iface_len] == '=')
+ ) {
+ return search;
+ }
+ search = search->link;
+ }
+ return NULL;
+}
+
+/* read the previous state from the state file */
+static llist_t *read_iface_state(void)
+{
+ llist_t *state_list = NULL;
+ FILE *state_fp = fopen_for_read(CONFIG_IFUPDOWN_IFSTATE_PATH);
+
+ if (state_fp) {
+ char *start, *end_ptr;
+ while ((start = xmalloc_fgets(state_fp)) != NULL) {
+ /* We should only need to check for a single character */
+ end_ptr = start + strcspn(start, " \t\n");
+ *end_ptr = '\0';
+ llist_add_to(&state_list, start);
+ }
+ fclose(state_fp);
+ }
+ return state_list;
+}
+
+
+int ifupdown_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifupdown_main(int argc, char **argv)
+{
+ int (*cmds)(struct interface_defn_t *);
+ struct interfaces_file_t *defn;
+ llist_t *target_list = NULL;
+ const char *interfaces = "/etc/network/interfaces";
+ bool any_failures = 0;
+
+ cmds = iface_down;
+ if (applet_name[2] == 'u') {
+ /* ifup command */
+ cmds = iface_up;
+ }
+
+ getopt32(argv, OPTION_STR, &interfaces);
+ if (argc - optind > 0) {
+ if (DO_ALL) bb_show_usage();
+ } else {
+ if (!DO_ALL) bb_show_usage();
+ }
+
+ debug_noise("reading %s file:\n", interfaces);
+ defn = read_interfaces(interfaces);
+ debug_noise("\ndone reading %s\n\n", interfaces);
+
+ startup_PATH = getenv("PATH");
+ if (!startup_PATH) startup_PATH = "";
+
+ /* Create a list of interfaces to work on */
+ if (DO_ALL) {
+ target_list = defn->autointerfaces;
+ } else {
+ llist_add_to_end(&target_list, argv[optind]);
+ }
+
+ /* Update the interfaces */
+ while (target_list) {
+ llist_t *iface_list;
+ struct interface_defn_t *currif;
+ char *iface;
+ char *liface;
+ char *pch;
+ bool okay = 0;
+ int cmds_ret;
+
+ iface = xstrdup(target_list->data);
+ target_list = target_list->link;
+
+ pch = strchr(iface, '=');
+ if (pch) {
+ *pch = '\0';
+ liface = xstrdup(pch + 1);
+ } else {
+ liface = xstrdup(iface);
+ }
+
+ if (!FORCE) {
+ llist_t *state_list = read_iface_state();
+ const llist_t *iface_state = find_iface_state(state_list, iface);
+
+ if (cmds == iface_up) {
+ /* ifup */
+ if (iface_state) {
+ bb_error_msg("interface %s already configured", iface);
+ continue;
+ }
+ } else {
+ /* ifdown */
+ if (!iface_state) {
+ bb_error_msg("interface %s not configured", iface);
+ continue;
+ }
+ }
+ llist_free(state_list, free);
+ }
+
+#if ENABLE_FEATURE_IFUPDOWN_MAPPING
+ if ((cmds == iface_up) && !NO_MAPPINGS) {
+ struct mapping_defn_t *currmap;
+
+ for (currmap = defn->mappings; currmap; currmap = currmap->next) {
+ int i;
+ for (i = 0; i < currmap->n_matches; i++) {
+ if (fnmatch(currmap->match[i], liface, 0) != 0)
+ continue;
+ if (VERBOSE) {
+ printf("Running mapping script %s on %s\n", currmap->script, liface);
+ }
+ liface = run_mapping(iface, currmap);
+ break;
+ }
+ }
+ }
+#endif
+
+ iface_list = defn->ifaces;
+ while (iface_list) {
+ currif = (struct interface_defn_t *) iface_list->data;
+ if (strcmp(liface, currif->iface) == 0) {
+ char *oldiface = currif->iface;
+
+ okay = 1;
+ currif->iface = iface;
+
+ debug_noise("\nConfiguring interface %s (%s)\n", liface, currif->address_family->name);
+
+ /* Call the cmds function pointer, does either iface_up() or iface_down() */
+ cmds_ret = cmds(currif);
+ if (cmds_ret == -1) {
+ bb_error_msg("don't seem to have all the variables for %s/%s",
+ liface, currif->address_family->name);
+ any_failures = 1;
+ } else if (cmds_ret == 0) {
+ any_failures = 1;
+ }
+
+ currif->iface = oldiface;
+ }
+ iface_list = iface_list->link;
+ }
+ if (VERBOSE) {
+ bb_putchar('\n');
+ }
+
+ if (!okay && !FORCE) {
+ bb_error_msg("ignoring unknown interface %s", liface);
+ any_failures = 1;
+ } else if (!NO_ACT) {
+ /* update the state file */
+ FILE *state_fp;
+ llist_t *state;
+ llist_t *state_list = read_iface_state();
+ llist_t *iface_state = find_iface_state(state_list, iface);
+
+ if (cmds == iface_up) {
+ char * const newiface = xasprintf("%s=%s", iface, liface);
+ if (iface_state == NULL) {
+ llist_add_to_end(&state_list, newiface);
+ } else {
+ free(iface_state->data);
+ iface_state->data = newiface;
+ }
+ } else {
+ /* Remove an interface from state_list */
+ llist_unlink(&state_list, iface_state);
+ free(llist_pop(&iface_state));
+ }
+
+ /* Actually write the new state */
+ state_fp = xfopen_for_write(CONFIG_IFUPDOWN_IFSTATE_PATH);
+ state = state_list;
+ while (state) {
+ if (state->data) {
+ fprintf(state_fp, "%s\n", state->data);
+ }
+ state = state->link;
+ }
+ fclose(state_fp);
+ llist_free(state_list, free);
+ }
+ }
+
+ return any_failures;
+}
diff --git a/networking/inetd.c b/networking/inetd.c
new file mode 100644
index 0000000..bf018d7
--- /dev/null
+++ b/networking/inetd.c
@@ -0,0 +1,1592 @@
+/* vi: set sw=4 ts=4: */
+/* $Slackware: inetd.c 1.79s 2001/02/06 13:18:00 volkerdi Exp $ */
+/* $OpenBSD: inetd.c,v 1.79 2001/01/30 08:30:57 deraadt Exp $ */
+/* $NetBSD: inetd.c,v 1.11 1996/02/22 11:14:41 mycroft Exp $ */
+/* Busybox port by Vladimir Oleynik (C) 2001-2005 <dzo@simtreas.ru> */
+/* IPv6 support, many bug fixes by Denys Vlasenko (c) 2008 */
+/*
+ * Copyright (c) 1983,1991 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* Inetd - Internet super-server
+ *
+ * This program invokes configured services when a connection
+ * from a peer is established or a datagram arrives.
+ * Connection-oriented services are invoked each time a
+ * connection is made, by creating a process. This process
+ * is passed the connection as file descriptor 0 and is
+ * expected to do a getpeername to find out peer's host
+ * and port.
+ * Datagram oriented services are invoked when a datagram
+ * arrives; a process is created and passed a pending message
+ * on file descriptor 0. peer's address can be obtained
+ * using recvfrom.
+ *
+ * Inetd uses a configuration file which is read at startup
+ * and, possibly, at some later time in response to a hangup signal.
+ * The configuration file is "free format" with fields given in the
+ * order shown below. Continuation lines for an entry must begin with
+ * a space or tab. All fields must be present in each entry.
+ *
+ * service_name must be in /etc/services
+ * socket_type stream/dgram/raw/rdm/seqpacket
+ * protocol must be in /etc/protocols
+ * (usually "tcp" or "udp")
+ * wait/nowait[.max] single-threaded/multi-threaded, max #
+ * user[.group] or user[:group] user/group to run daemon as
+ * server_program full path name
+ * server_program_arguments maximum of MAXARGS (20)
+ *
+ * For RPC services
+ * service_name/version must be in /etc/rpc
+ * socket_type stream/dgram/raw/rdm/seqpacket
+ * rpc/protocol "rpc/tcp" etc
+ * wait/nowait[.max] single-threaded/multi-threaded
+ * user[.group] or user[:group] user to run daemon as
+ * server_program full path name
+ * server_program_arguments maximum of MAXARGS (20)
+ *
+ * For non-RPC services, the "service name" can be of the form
+ * hostaddress:servicename, in which case the hostaddress is used
+ * as the host portion of the address to listen on. If hostaddress
+ * consists of a single '*' character, INADDR_ANY is used.
+ *
+ * A line can also consist of just
+ * hostaddress:
+ * where hostaddress is as in the preceding paragraph. Such a line must
+ * have no further fields; the specified hostaddress is remembered and
+ * used for all further lines that have no hostaddress specified,
+ * until the next such line (or EOF). (This is why * is provided to
+ * allow explicit specification of INADDR_ANY.) A line
+ * *:
+ * is implicitly in effect at the beginning of the file.
+ *
+ * The hostaddress specifier may (and often will) contain dots;
+ * the service name must not.
+ *
+ * For RPC services, host-address specifiers are accepted and will
+ * work to some extent; however, because of limitations in the
+ * portmapper interface, it will not work to try to give more than
+ * one line for any given RPC service, even if the host-address
+ * specifiers are different.
+ *
+ * Comment lines are indicated by a '#' in column 1.
+ */
+
+/* inetd rules for passing file descriptors to children
+ * (http://www.freebsd.org/cgi/man.cgi?query=inetd):
+ *
+ * The wait/nowait entry specifies whether the server that is invoked by
+ * inetd will take over the socket associated with the service access point,
+ * and thus whether inetd should wait for the server to exit before listen-
+ * ing for new service requests. Datagram servers must use "wait", as
+ * they are always invoked with the original datagram socket bound to the
+ * specified service address. These servers must read at least one datagram
+ * from the socket before exiting. If a datagram server connects to its
+ * peer, freeing the socket so inetd can receive further messages on the
+ * socket, it is said to be a "multi-threaded" server; it should read one
+ * datagram from the socket and create a new socket connected to the peer.
+ * It should fork, and the parent should then exit to allow inetd to check
+ * for new service requests to spawn new servers. Datagram servers which
+ * process all incoming datagrams on a socket and eventually time out are
+ * said to be "single-threaded". The comsat(8), biff(1) and talkd(8)
+ * utilities are both examples of the latter type of datagram server. The
+ * tftpd(8) utility is an example of a multi-threaded datagram server.
+ *
+ * Servers using stream sockets generally are multi-threaded and use the
+ * "nowait" entry. Connection requests for these services are accepted by
+ * inetd, and the server is given only the newly-accepted socket connected
+ * to a client of the service. Most stream-based services operate in this
+ * manner. Stream-based servers that use "wait" are started with the lis-
+ * tening service socket, and must accept at least one connection request
+ * before exiting. Such a server would normally accept and process incoming
+ * connection requests until a timeout.
+ */
+
+/* Despite of above doc saying that dgram services must use "wait",
+ * "udp nowait" servers are implemented in busyboxed inetd.
+ * IPv6 addresses are also implemented. However, they may look ugly -
+ * ":::service..." means "address '::' (IPv6 wildcard addr)":"service"...
+ * You have to put "tcp6"/"udp6" in protocol field to select IPv6.
+ */
+
+/* Here's the scoop concerning the user[:group] feature:
+ * 1) group is not specified:
+ * a) user = root: NO setuid() or setgid() is done
+ * b) other: initgroups(name, primary group)
+ * setgid(primary group as found in passwd)
+ * setuid()
+ * 2) group is specified:
+ * a) user = root: setgid(specified group)
+ * NO initgroups()
+ * NO setuid()
+ * b) other: initgroups(name, specified group)
+ * setgid(specified group)
+ * setuid()
+ */
+
+#include <syslog.h>
+#include <sys/un.h>
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_INETD_RPC
+#include <rpc/rpc.h>
+#include <rpc/pmap_clnt.h>
+#endif
+
+#if !BB_MMU
+/* stream version of chargen is forking but not execing,
+ * can't do that (easily) on NOMMU */
+#undef ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+#define ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN 0
+#endif
+
+#define _PATH_INETDPID "/var/run/inetd.pid"
+
+#define CNT_INTERVAL 60 /* servers in CNT_INTERVAL sec. */
+#define RETRYTIME 60 /* retry after bind or server fail */
+
+// TODO: explain, or get rid of setrlimit games
+
+#ifndef RLIMIT_NOFILE
+#define RLIMIT_NOFILE RLIMIT_OFILE
+#endif
+
+#ifndef OPEN_MAX
+#define OPEN_MAX 64
+#endif
+
+/* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */
+#define FD_MARGIN 8
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME \
+ || ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+# define INETD_BUILTINS_ENABLED
+#endif
+
+typedef struct servtab_t {
+ /* The most frequently referenced one: */
+ int se_fd; /* open descriptor */
+ /* NB: 'biggest fields last' saves on code size (~250 bytes) */
+ /* [addr:]service socktype proto wait user[:group] prog [args] */
+ char *se_local_hostname; /* addr to listen on */
+ char *se_service; /* "80" or "www" or "mount/2[-3]" */
+ /* socktype is in se_socktype */ /* "stream" "dgram" "raw" "rdm" "seqpacket" */
+ char *se_proto; /* "unix" or "[rpc/]tcp[6]" */
+#if ENABLE_FEATURE_INETD_RPC
+ int se_rpcprog; /* rpc program number */
+ int se_rpcver_lo; /* rpc program lowest version */
+ int se_rpcver_hi; /* rpc program highest version */
+#define is_rpc_service(sep) ((sep)->se_rpcver_lo != 0)
+#else
+#define is_rpc_service(sep) 0
+#endif
+ pid_t se_wait; /* 0:"nowait", 1:"wait", >1:"wait" */
+ /* and waiting for this pid */
+ socktype_t se_socktype; /* SOCK_STREAM/DGRAM/RDM/... */
+ family_t se_family; /* AF_UNIX/INET[6] */
+ /* se_proto_no is used by RPC code only... hmm */
+ smallint se_proto_no; /* IPPROTO_TCP/UDP, n/a for AF_UNIX */
+ smallint se_checked; /* looked at during merge */
+ unsigned se_max; /* allowed instances per minute */
+ unsigned se_count; /* number started since se_time */
+ unsigned se_time; /* when we started counting */
+ char *se_user; /* user name to run as */
+ char *se_group; /* group name to run as, can be NULL */
+#ifdef INETD_BUILTINS_ENABLED
+ const struct builtin *se_builtin; /* if built-in, description */
+#endif
+ struct servtab_t *se_next;
+ len_and_sockaddr *se_lsa;
+ char *se_program; /* server program */
+#define MAXARGV 20
+ char *se_argv[MAXARGV + 1]; /* program arguments */
+} servtab_t;
+
+#ifdef INETD_BUILTINS_ENABLED
+/* Echo received data */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO
+static void echo_stream(int, servtab_t *);
+static void echo_dg(int, servtab_t *);
+#endif
+/* Internet /dev/null */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+static void discard_stream(int, servtab_t *);
+static void discard_dg(int, servtab_t *);
+#endif
+/* Return 32 bit time since 1900 */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
+static void machtime_stream(int, servtab_t *);
+static void machtime_dg(int, servtab_t *);
+#endif
+/* Return human-readable time */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+static void daytime_stream(int, servtab_t *);
+static void daytime_dg(int, servtab_t *);
+#endif
+/* Familiar character generator */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+static void chargen_stream(int, servtab_t *);
+static void chargen_dg(int, servtab_t *);
+#endif
+
+struct builtin {
+ /* NB: not necessarily NUL terminated */
+ char bi_service7[7]; /* internally provided service name */
+ uint8_t bi_fork; /* 1 if stream fn should run in child */
+ void (*bi_stream_fn)(int, servtab_t *);
+ void (*bi_dgram_fn)(int, servtab_t *);
+};
+
+static const struct builtin builtins[] = {
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO
+ { "echo", 1, echo_stream, echo_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+ { "discard", 1, discard_stream, discard_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+ { "chargen", 1, chargen_stream, chargen_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
+ { "time", 0, machtime_stream, machtime_dg },
+#endif
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+ { "daytime", 0, daytime_stream, daytime_dg },
+#endif
+};
+#endif /* INETD_BUILTINS_ENABLED */
+
+struct globals {
+ rlim_t rlim_ofile_cur;
+ struct rlimit rlim_ofile;
+ servtab_t *serv_list;
+ int global_queuelen;
+ int maxsock; /* max fd# in allsock, -1: unknown */
+ /* whenever maxsock grows, prev_maxsock is set to new maxsock,
+ * but if maxsock is set to -1, prev_maxsock is not changed */
+ int prev_maxsock;
+ unsigned max_concurrency;
+ smallint alarm_armed;
+ uid_t real_uid; /* user ID who ran us */
+ const char *config_filename;
+ parser_t *parser;
+ char *default_local_hostname;
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+ char *end_ring;
+ char *ring_pos;
+ char ring[128];
+#endif
+ fd_set allsock;
+ /* Used in next_line(), and as scratch read buffer */
+ char line[256]; /* _at least_ 256, see LINE_SIZE */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+enum { LINE_SIZE = COMMON_BUFSIZE - offsetof(struct globals, line) };
+struct BUG_G_too_big {
+ char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define rlim_ofile_cur (G.rlim_ofile_cur )
+#define rlim_ofile (G.rlim_ofile )
+#define serv_list (G.serv_list )
+#define global_queuelen (G.global_queuelen)
+#define maxsock (G.maxsock )
+#define prev_maxsock (G.prev_maxsock )
+#define max_concurrency (G.max_concurrency)
+#define alarm_armed (G.alarm_armed )
+#define real_uid (G.real_uid )
+#define config_filename (G.config_filename)
+#define parser (G.parser )
+#define default_local_hostname (G.default_local_hostname)
+#define first_ps_byte (G.first_ps_byte )
+#define last_ps_byte (G.last_ps_byte )
+#define end_ring (G.end_ring )
+#define ring_pos (G.ring_pos )
+#define ring (G.ring )
+#define allsock (G.allsock )
+#define line (G.line )
+#define INIT_G() do { \
+ rlim_ofile_cur = OPEN_MAX; \
+ global_queuelen = 128; \
+ config_filename = "/etc/inetd.conf"; \
+} while (0)
+
+static void maybe_close(int fd)
+{
+ if (fd >= 0)
+ close(fd);
+}
+
+// TODO: move to libbb?
+static len_and_sockaddr *xzalloc_lsa(int family)
+{
+ len_and_sockaddr *lsa;
+ int sz;
+
+ sz = sizeof(struct sockaddr_in);
+ if (family == AF_UNIX)
+ sz = sizeof(struct sockaddr_un);
+#if ENABLE_FEATURE_IPV6
+ if (family == AF_INET6)
+ sz = sizeof(struct sockaddr_in6);
+#endif
+ lsa = xzalloc(LSA_LEN_SIZE + sz);
+ lsa->len = sz;
+ lsa->u.sa.sa_family = family;
+ return lsa;
+}
+
+static void rearm_alarm(void)
+{
+ if (!alarm_armed) {
+ alarm_armed = 1;
+ alarm(RETRYTIME);
+ }
+}
+
+static void block_CHLD_HUP_ALRM(sigset_t *m)
+{
+ sigemptyset(m);
+ sigaddset(m, SIGCHLD);
+ sigaddset(m, SIGHUP);
+ sigaddset(m, SIGALRM);
+ sigprocmask(SIG_BLOCK, m, m); /* old sigmask is stored in m */
+}
+
+static void restore_sigmask(sigset_t *m)
+{
+ sigprocmask(SIG_SETMASK, m, NULL);
+}
+
+#if ENABLE_FEATURE_INETD_RPC
+static void register_rpc(servtab_t *sep)
+{
+ int n;
+ struct sockaddr_in ir_sin;
+ socklen_t size;
+
+ size = sizeof(ir_sin);
+ if (getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, &size) < 0) {
+ bb_perror_msg("getsockname");
+ return;
+ }
+
+ for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) {
+ pmap_unset(sep->se_rpcprog, n);
+ if (!pmap_set(sep->se_rpcprog, n, sep->se_proto_no, ntohs(ir_sin.sin_port)))
+ bb_perror_msg("%s %s: pmap_set(%u,%u,%u,%u)",
+ sep->se_service, sep->se_proto,
+ sep->se_rpcprog, n, sep->se_proto_no, ntohs(ir_sin.sin_port));
+ }
+}
+
+static void unregister_rpc(servtab_t *sep)
+{
+ int n;
+
+ for (n = sep->se_rpcver_lo; n <= sep->se_rpcver_hi; n++) {
+ if (!pmap_unset(sep->se_rpcprog, n))
+ bb_perror_msg("pmap_unset(%u,%u)", sep->se_rpcprog, n);
+ }
+}
+#endif /* FEATURE_INETD_RPC */
+
+static void bump_nofile(void)
+{
+ enum { FD_CHUNK = 32 };
+ struct rlimit rl;
+
+ /* Never fails under Linux (except if you pass it bad arguments) */
+ getrlimit(RLIMIT_NOFILE, &rl);
+ rl.rlim_cur = MIN(rl.rlim_max, rl.rlim_cur + FD_CHUNK);
+ rl.rlim_cur = MIN(FD_SETSIZE, rl.rlim_cur + FD_CHUNK);
+ if (rl.rlim_cur <= rlim_ofile_cur) {
+ bb_error_msg("can't extend file limit, max = %d",
+ (int) rl.rlim_cur);
+ return;
+ }
+
+ if (setrlimit(RLIMIT_NOFILE, &rl) < 0) {
+ bb_perror_msg("setrlimit");
+ return;
+ }
+
+ rlim_ofile_cur = rl.rlim_cur;
+}
+
+static void remove_fd_from_set(int fd)
+{
+ if (fd >= 0) {
+ FD_CLR(fd, &allsock);
+ maxsock = -1;
+ }
+}
+
+static void add_fd_to_set(int fd)
+{
+ if (fd >= 0) {
+ FD_SET(fd, &allsock);
+ if (maxsock >= 0 && fd > maxsock) {
+ prev_maxsock = maxsock = fd;
+ if ((rlim_t)fd > rlim_ofile_cur - FD_MARGIN)
+ bump_nofile();
+ }
+ }
+}
+
+static void recalculate_maxsock(void)
+{
+ int fd = 0;
+
+ /* We may have no services, in this case maxsock should still be >= 0
+ * (code elsewhere is not happy with maxsock == -1) */
+ maxsock = 0;
+ while (fd <= prev_maxsock) {
+ if (FD_ISSET(fd, &allsock))
+ maxsock = fd;
+ fd++;
+ }
+ prev_maxsock = maxsock;
+ if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN)
+ bump_nofile();
+}
+
+static void prepare_socket_fd(servtab_t *sep)
+{
+ int r, fd;
+
+ fd = socket(sep->se_family, sep->se_socktype, 0);
+ if (fd < 0) {
+ bb_perror_msg("socket");
+ return;
+ }
+ setsockopt_reuseaddr(fd);
+
+#if ENABLE_FEATURE_INETD_RPC
+ if (is_rpc_service(sep)) {
+ struct passwd *pwd;
+
+ /* zero out the port for all RPC services; let bind()
+ * find one. */
+ set_nport(sep->se_lsa, 0);
+
+ /* for RPC services, attempt to use a reserved port
+ * if they are going to be running as root. */
+ if (real_uid == 0 && sep->se_family == AF_INET
+ && (pwd = getpwnam(sep->se_user)) != NULL
+ && pwd->pw_uid == 0
+ ) {
+ r = bindresvport(fd, &sep->se_lsa->u.sin);
+ } else {
+ r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len);
+ }
+ if (r == 0) {
+ int saveerrno = errno;
+ /* update lsa with port# */
+ getsockname(fd, &sep->se_lsa->u.sa, &sep->se_lsa->len);
+ errno = saveerrno;
+ }
+ } else
+#endif
+ {
+ if (sep->se_family == AF_UNIX) {
+ struct sockaddr_un *sun;
+ sun = (struct sockaddr_un*)&(sep->se_lsa->u.sa);
+ unlink(sun->sun_path);
+ }
+ r = bind(fd, &sep->se_lsa->u.sa, sep->se_lsa->len);
+ }
+ if (r < 0) {
+ bb_perror_msg("%s/%s: bind",
+ sep->se_service, sep->se_proto);
+ close(fd);
+ rearm_alarm();
+ return;
+ }
+ if (sep->se_socktype == SOCK_STREAM)
+ listen(fd, global_queuelen);
+
+ add_fd_to_set(fd);
+ sep->se_fd = fd;
+}
+
+static int reopen_config_file(void)
+{
+ free(default_local_hostname);
+ default_local_hostname = xstrdup("*");
+ if (parser != NULL)
+ config_close(parser);
+ parser = config_open(config_filename);
+ return (parser != NULL);
+}
+
+static void close_config_file(void)
+{
+ if (parser) {
+ config_close(parser);
+ parser = NULL;
+ }
+}
+
+static void free_servtab_strings(servtab_t *cp)
+{
+ int i;
+
+ free(cp->se_local_hostname);
+ free(cp->se_service);
+ free(cp->se_proto);
+ free(cp->se_user);
+ free(cp->se_group);
+ free(cp->se_lsa); /* not a string in fact */
+ free(cp->se_program);
+ for (i = 0; i < MAXARGV; i++)
+ free(cp->se_argv[i]);
+}
+
+static servtab_t *new_servtab(void)
+{
+ servtab_t *newtab = xzalloc(sizeof(servtab_t));
+ newtab->se_fd = -1; /* paranoia */
+ return newtab;
+}
+
+static servtab_t *dup_servtab(servtab_t *sep)
+{
+ servtab_t *newtab;
+ int argc;
+
+ newtab = new_servtab();
+ *newtab = *sep; /* struct copy */
+ /* deep-copying strings */
+ newtab->se_service = xstrdup(newtab->se_service);
+ newtab->se_proto = xstrdup(newtab->se_proto);
+ newtab->se_user = xstrdup(newtab->se_user);
+ newtab->se_group = xstrdup(newtab->se_group);
+ newtab->se_program = xstrdup(newtab->se_program);
+ for (argc = 0; argc <= MAXARGV; argc++)
+ newtab->se_argv[argc] = xstrdup(newtab->se_argv[argc]);
+ /* NB: se_fd, se_hostaddr and se_next are always
+ * overwrittend by callers, so we don't bother resetting them
+ * to NULL/0/-1 etc */
+
+ return newtab;
+}
+
+/* gcc generates much more code if this is inlined */
+static servtab_t *parse_one_line(void)
+{
+ int argc;
+ char *token[6+MAXARGV];
+ char *p, *arg;
+ char *hostdelim;
+ servtab_t *sep;
+ servtab_t *nsep;
+ new:
+ sep = new_servtab();
+ more:
+ argc = config_read(parser, token, 6+MAXARGV, 1, "# \t", PARSE_NORMAL);
+ if (!argc) {
+ free(sep);
+ return NULL;
+ }
+
+ /* [host:]service socktype proto wait user[:group] prog [args] */
+ /* Check for "host:...." line */
+ arg = token[0];
+ hostdelim = strrchr(arg, ':');
+ if (hostdelim) {
+ *hostdelim = '\0';
+ sep->se_local_hostname = xstrdup(arg);
+ arg = hostdelim + 1;
+ if (*arg == '\0' && argc == 1) {
+ /* Line has just "host:", change the
+ * default host for the following lines. */
+ free(default_local_hostname);
+ default_local_hostname = sep->se_local_hostname;
+ goto more;
+ }
+ } else
+ sep->se_local_hostname = xstrdup(default_local_hostname);
+
+ /* service socktype proto wait user[:group] prog [args] */
+ sep->se_service = xstrdup(arg);
+
+ /* socktype proto wait user[:group] prog [args] */
+ if (argc < 6) {
+ parse_err:
+ bb_error_msg("parse error on line %u, line is ignored",
+ parser->lineno);
+ free_servtab_strings(sep);
+ /* Just "goto more" can make sep to carry over e.g.
+ * "rpc"-ness (by having se_rpcver_lo != 0).
+ * We will be more paranoid: */
+ free(sep);
+ goto new;
+ }
+
+ {
+ static int8_t SOCK_xxx[] ALIGN1 = {
+ -1,
+ SOCK_STREAM, SOCK_DGRAM, SOCK_RDM,
+ SOCK_SEQPACKET, SOCK_RAW
+ };
+ sep->se_socktype = SOCK_xxx[1 + index_in_strings(
+ "stream""\0" "dgram""\0" "rdm""\0"
+ "seqpacket""\0" "raw""\0"
+ , token[1])];
+ }
+
+ /* {unix,[rpc/]{tcp,udp}[6]} wait user[:group] prog [args] */
+ sep->se_proto = arg = xstrdup(token[2]);
+ if (strcmp(arg, "unix") == 0) {
+ sep->se_family = AF_UNIX;
+ } else {
+ char *six;
+ sep->se_family = AF_INET;
+ six = last_char_is(arg, '6');
+ if (six) {
+#if ENABLE_FEATURE_IPV6
+ *six = '\0';
+ sep->se_family = AF_INET6;
+#else
+ bb_error_msg("%s: no support for IPv6", sep->se_proto);
+ goto parse_err;
+#endif
+ }
+ if (strncmp(arg, "rpc/", 4) == 0) {
+#if ENABLE_FEATURE_INETD_RPC
+ unsigned n;
+ arg += 4;
+ p = strchr(sep->se_service, '/');
+ if (p == NULL) {
+ bb_error_msg("no rpc version: '%s'", sep->se_service);
+ goto parse_err;
+ }
+ *p++ = '\0';
+ n = bb_strtou(p, &p, 10);
+ if (n > INT_MAX) {
+ bad_ver_spec:
+ bb_error_msg("bad rpc version");
+ goto parse_err;
+ }
+ sep->se_rpcver_lo = sep->se_rpcver_hi = n;
+ if (*p == '-') {
+ p++;
+ n = bb_strtou(p, &p, 10);
+ if (n > INT_MAX || (int)n < sep->se_rpcver_lo)
+ goto bad_ver_spec;
+ sep->se_rpcver_hi = n;
+ }
+ if (*p != '\0')
+ goto bad_ver_spec;
+#else
+ bb_error_msg("no support for rpc services");
+ goto parse_err;
+#endif
+ }
+ /* we don't really need getprotobyname()! */
+ if (strcmp(arg, "tcp") == 0)
+ sep->se_proto_no = IPPROTO_TCP; /* = 6 */
+ if (strcmp(arg, "udp") == 0)
+ sep->se_proto_no = IPPROTO_UDP; /* = 17 */
+ if (six)
+ *six = '6';
+ if (!sep->se_proto_no) /* not tcp/udp?? */
+ goto parse_err;
+ }
+
+ /* [no]wait[.max] user[:group] prog [args] */
+ arg = token[3];
+ sep->se_max = max_concurrency;
+ p = strchr(arg, '.');
+ if (p) {
+ *p++ = '\0';
+ sep->se_max = bb_strtou(p, NULL, 10);
+ if (errno)
+ goto parse_err;
+ }
+ sep->se_wait = (arg[0] != 'n' || arg[1] != 'o');
+ if (!sep->se_wait) /* "no" seen */
+ arg += 2;
+ if (strcmp(arg, "wait") != 0)
+ goto parse_err;
+
+ /* user[:group] prog [args] */
+ sep->se_user = xstrdup(token[4]);
+ arg = strchr(sep->se_user, '.');
+ if (arg == NULL)
+ arg = strchr(sep->se_user, ':');
+ if (arg) {
+ *arg++ = '\0';
+ sep->se_group = xstrdup(arg);
+ }
+
+ /* prog [args] */
+ sep->se_program = xstrdup(token[5]);
+#ifdef INETD_BUILTINS_ENABLED
+ if (strcmp(sep->se_program, "internal") == 0
+ && strlen(sep->se_service) <= 7
+ && (sep->se_socktype == SOCK_STREAM
+ || sep->se_socktype == SOCK_DGRAM)
+ ) {
+ unsigned i;
+ for (i = 0; i < ARRAY_SIZE(builtins); i++)
+ if (strncmp(builtins[i].bi_service7, sep->se_service, 7) == 0)
+ goto found_bi;
+ bb_error_msg("unknown internal service %s", sep->se_service);
+ goto parse_err;
+ found_bi:
+ sep->se_builtin = &builtins[i];
+ /* stream builtins must be "nowait", dgram must be "wait" */
+ if (sep->se_wait != (sep->se_socktype == SOCK_DGRAM))
+ goto parse_err;
+ }
+#endif
+ argc = 0;
+ while ((arg = token[6+argc]) != NULL && argc < MAXARGV)
+ sep->se_argv[argc++] = xstrdup(arg);
+
+ /* catch mixups. "<service> stream udp ..." == wtf */
+ if (sep->se_socktype == SOCK_STREAM) {
+ if (sep->se_proto_no == IPPROTO_UDP)
+ goto parse_err;
+ }
+ if (sep->se_socktype == SOCK_DGRAM) {
+ if (sep->se_proto_no == IPPROTO_TCP)
+ goto parse_err;
+ }
+
+// bb_info_msg(
+// "ENTRY[%s][%s][%s][%d][%d][%d][%d][%d][%s][%s][%s]",
+// sep->se_local_hostname, sep->se_service, sep->se_proto, sep->se_wait, sep->se_proto_no,
+// sep->se_max, sep->se_count, sep->se_time, sep->se_user, sep->se_group, sep->se_program);
+
+ /* check if the hostname specifier is a comma separated list
+ * of hostnames. we'll make new entries for each address. */
+ while ((hostdelim = strrchr(sep->se_local_hostname, ',')) != NULL) {
+ nsep = dup_servtab(sep);
+ /* NUL terminate the hostname field of the existing entry,
+ * and make a dup for the new entry. */
+ *hostdelim++ = '\0';
+ nsep->se_local_hostname = xstrdup(hostdelim);
+ nsep->se_next = sep->se_next;
+ sep->se_next = nsep;
+ }
+
+ /* was doing it here: */
+ /* DNS resolution, create copies for each IP address */
+ /* IPv6-ization destroyed it :( */
+
+ return sep;
+}
+
+static servtab_t *insert_in_servlist(servtab_t *cp)
+{
+ servtab_t *sep;
+ sigset_t omask;
+
+ sep = new_servtab();
+ *sep = *cp; /* struct copy */
+ sep->se_fd = -1;
+#if ENABLE_FEATURE_INETD_RPC
+ sep->se_rpcprog = -1;
+#endif
+ block_CHLD_HUP_ALRM(&omask);
+ sep->se_next = serv_list;
+ serv_list = sep;
+ restore_sigmask(&omask);
+ return sep;
+}
+
+static int same_serv_addr_proto(servtab_t *old, servtab_t *new)
+{
+ if (strcmp(old->se_local_hostname, new->se_local_hostname) != 0)
+ return 0;
+ if (strcmp(old->se_service, new->se_service) != 0)
+ return 0;
+ if (strcmp(old->se_proto, new->se_proto) != 0)
+ return 0;
+ return 1;
+}
+
+static void reread_config_file(int sig UNUSED_PARAM)
+{
+ servtab_t *sep, *cp, **sepp;
+ len_and_sockaddr *lsa;
+ sigset_t omask;
+ unsigned n;
+ uint16_t port;
+ int save_errno = errno;
+
+ if (!reopen_config_file())
+ goto ret;
+ for (sep = serv_list; sep; sep = sep->se_next)
+ sep->se_checked = 0;
+
+ goto first_line;
+ while (1) {
+ if (cp == NULL) {
+ first_line:
+ cp = parse_one_line();
+ if (cp == NULL)
+ break;
+ }
+ for (sep = serv_list; sep; sep = sep->se_next)
+ if (same_serv_addr_proto(sep, cp))
+ goto equal_servtab;
+ /* not an "equal" servtab */
+ sep = insert_in_servlist(cp);
+ goto after_check;
+ equal_servtab:
+ {
+ int i;
+
+ block_CHLD_HUP_ALRM(&omask);
+#if ENABLE_FEATURE_INETD_RPC
+ if (is_rpc_service(sep))
+ unregister_rpc(sep);
+ sep->se_rpcver_lo = cp->se_rpcver_lo;
+ sep->se_rpcver_hi = cp->se_rpcver_hi;
+#endif
+ if (cp->se_wait == 0) {
+ /* New config says "nowait". If old one
+ * was "wait", we currently may be waiting
+ * for a child (and not accepting connects).
+ * Stop waiting, start listening again.
+ * (if it's not true, this op is harmless) */
+ add_fd_to_set(sep->se_fd);
+ }
+ sep->se_wait = cp->se_wait;
+ sep->se_max = cp->se_max;
+ /* string fields need more love - we don't want to leak them */
+#define SWAP(type, a, b) do { type c = (type)a; a = (type)b; b = (type)c; } while (0)
+ SWAP(char*, sep->se_user, cp->se_user);
+ SWAP(char*, sep->se_group, cp->se_group);
+ SWAP(char*, sep->se_program, cp->se_program);
+ for (i = 0; i < MAXARGV; i++)
+ SWAP(char*, sep->se_argv[i], cp->se_argv[i]);
+#undef SWAP
+ restore_sigmask(&omask);
+ free_servtab_strings(cp);
+ }
+ after_check:
+ /* cp->string_fields are consumed by insert_in_servlist()
+ * or freed at this point, cp itself is not yet freed. */
+ sep->se_checked = 1;
+
+ /* create new len_and_sockaddr */
+ switch (sep->se_family) {
+ struct sockaddr_un *sun;
+ case AF_UNIX:
+ lsa = xzalloc_lsa(AF_UNIX);
+ sun = (struct sockaddr_un*)&lsa->u.sa;
+ safe_strncpy(sun->sun_path, sep->se_service, sizeof(sun->sun_path));
+ break;
+
+ default: /* case AF_INET, case AF_INET6 */
+ n = bb_strtou(sep->se_service, NULL, 10);
+#if ENABLE_FEATURE_INETD_RPC
+ if (is_rpc_service(sep)) {
+ sep->se_rpcprog = n;
+ if (errno) { /* se_service is not numeric */
+ struct rpcent *rp = getrpcbyname(sep->se_service);
+ if (rp == NULL) {
+ bb_error_msg("%s: unknown rpc service", sep->se_service);
+ goto next_cp;
+ }
+ sep->se_rpcprog = rp->r_number;
+ }
+ if (sep->se_fd == -1)
+ prepare_socket_fd(sep);
+ if (sep->se_fd != -1)
+ register_rpc(sep);
+ goto next_cp;
+ }
+#endif
+ /* what port to listen on? */
+ port = htons(n);
+ if (errno || n > 0xffff) { /* se_service is not numeric */
+ char protoname[4];
+ struct servent *sp;
+ /* can result only in "tcp" or "udp": */
+ safe_strncpy(protoname, sep->se_proto, 4);
+ sp = getservbyname(sep->se_service, protoname);
+ if (sp == NULL) {
+ bb_error_msg("%s/%s: unknown service",
+ sep->se_service, sep->se_proto);
+ goto next_cp;
+ }
+ port = sp->s_port;
+ }
+ if (LONE_CHAR(sep->se_local_hostname, '*')) {
+ lsa = xzalloc_lsa(sep->se_family);
+ set_nport(lsa, port);
+ } else {
+ lsa = host_and_af2sockaddr(sep->se_local_hostname,
+ ntohs(port), sep->se_family);
+ if (!lsa) {
+ bb_error_msg("%s/%s: unknown host '%s'",
+ sep->se_service, sep->se_proto,
+ sep->se_local_hostname);
+ goto next_cp;
+ }
+ }
+ break;
+ } /* end of "switch (sep->se_family)" */
+
+ /* did lsa change? Then close/open */
+ if (sep->se_lsa == NULL
+ || lsa->len != sep->se_lsa->len
+ || memcmp(&lsa->u.sa, &sep->se_lsa->u.sa, lsa->len) != 0
+ ) {
+ remove_fd_from_set(sep->se_fd);
+ maybe_close(sep->se_fd);
+ free(sep->se_lsa);
+ sep->se_lsa = lsa;
+ sep->se_fd = -1;
+ } else {
+ free(lsa);
+ }
+ if (sep->se_fd == -1)
+ prepare_socket_fd(sep);
+ next_cp:
+ sep = cp->se_next;
+ free(cp);
+ cp = sep;
+ } /* end of "while (1) parse lines" */
+ close_config_file();
+
+ /* Purge anything not looked at above - these are stale entries,
+ * new config file doesnt have them. */
+ block_CHLD_HUP_ALRM(&omask);
+ sepp = &serv_list;
+ while ((sep = *sepp)) {
+ if (sep->se_checked) {
+ sepp = &sep->se_next;
+ continue;
+ }
+ *sepp = sep->se_next;
+ remove_fd_from_set(sep->se_fd);
+ maybe_close(sep->se_fd);
+#if ENABLE_FEATURE_INETD_RPC
+ if (is_rpc_service(sep))
+ unregister_rpc(sep);
+#endif
+ if (sep->se_family == AF_UNIX)
+ unlink(sep->se_service);
+ free_servtab_strings(sep);
+ free(sep);
+ }
+ restore_sigmask(&omask);
+ ret:
+ errno = save_errno;
+}
+
+static void reap_child(int sig UNUSED_PARAM)
+{
+ pid_t pid;
+ int status;
+ servtab_t *sep;
+ int save_errno = errno;
+
+ for (;;) {
+ pid = wait_any_nohang(&status);
+ if (pid <= 0)
+ break;
+ for (sep = serv_list; sep; sep = sep->se_next) {
+ if (sep->se_wait != pid)
+ continue;
+ /* One of our "wait" services */
+ if (WIFEXITED(status) && WEXITSTATUS(status))
+ bb_error_msg("%s: exit status 0x%x",
+ sep->se_program, WEXITSTATUS(status));
+ else if (WIFSIGNALED(status))
+ bb_error_msg("%s: exit signal 0x%x",
+ sep->se_program, WTERMSIG(status));
+ sep->se_wait = 1;
+ add_fd_to_set(sep->se_fd);
+ break;
+ }
+ }
+ errno = save_errno;
+}
+
+static void retry_network_setup(int sig UNUSED_PARAM)
+{
+ int save_errno = errno;
+ servtab_t *sep;
+
+ alarm_armed = 0;
+ for (sep = serv_list; sep; sep = sep->se_next) {
+ if (sep->se_fd == -1) {
+ prepare_socket_fd(sep);
+#if ENABLE_FEATURE_INETD_RPC
+ if (sep->se_fd != -1 && is_rpc_service(sep))
+ register_rpc(sep);
+#endif
+ }
+ }
+ errno = save_errno;
+}
+
+static void clean_up_and_exit(int sig UNUSED_PARAM)
+{
+ servtab_t *sep;
+
+ /* XXX signal race walking sep list */
+ for (sep = serv_list; sep; sep = sep->se_next) {
+ if (sep->se_fd == -1)
+ continue;
+
+ switch (sep->se_family) {
+ case AF_UNIX:
+ unlink(sep->se_service);
+ break;
+ default: /* case AF_INET, AF_INET6 */
+#if ENABLE_FEATURE_INETD_RPC
+ if (sep->se_wait == 1 && is_rpc_service(sep))
+ unregister_rpc(sep); /* XXX signal race */
+#endif
+ break;
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(sep->se_fd);
+ }
+ remove_pidfile(_PATH_INETDPID);
+ exit(EXIT_SUCCESS);
+}
+
+int inetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int inetd_main(int argc UNUSED_PARAM, char **argv)
+{
+ struct sigaction sa, saved_pipe_handler;
+ servtab_t *sep, *sep2;
+ struct passwd *pwd;
+ struct group *grp = grp; /* for compiler */
+ int opt;
+ pid_t pid;
+ sigset_t omask;
+
+ INIT_G();
+
+ real_uid = getuid();
+ if (real_uid != 0) /* run by non-root user */
+ config_filename = NULL;
+
+ opt_complementary = "R+:q+"; /* -q N, -R N */
+ opt = getopt32(argv, "R:feq:", &max_concurrency, &global_queuelen);
+ argv += optind;
+ //argc -= optind;
+ if (argv[0])
+ config_filename = argv[0];
+ if (config_filename == NULL)
+ bb_error_msg_and_die("non-root must specify config file");
+ if (!(opt & 2))
+ bb_daemonize_or_rexec(0, argv - optind);
+ else
+ bb_sanitize_stdio();
+ if (!(opt & 4)) {
+ openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON);
+ logmode = LOGMODE_SYSLOG;
+ }
+
+ if (real_uid == 0) {
+ /* run by root, ensure groups vector gets trashed */
+ gid_t gid = getgid();
+ setgroups(1, &gid);
+ }
+
+ write_pidfile(_PATH_INETDPID);
+
+ /* never fails under Linux (except if you pass it bad arguments) */
+ getrlimit(RLIMIT_NOFILE, &rlim_ofile);
+ rlim_ofile_cur = rlim_ofile.rlim_cur;
+ if (rlim_ofile_cur == RLIM_INFINITY) /* ! */
+ rlim_ofile_cur = OPEN_MAX;
+
+ memset(&sa, 0, sizeof(sa));
+ /*sigemptyset(&sa.sa_mask); - memset did it */
+ sigaddset(&sa.sa_mask, SIGALRM);
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sa.sa_handler = retry_network_setup;
+ sigaction_set(SIGALRM, &sa);
+ sa.sa_handler = reread_config_file;
+ sigaction_set(SIGHUP, &sa);
+ sa.sa_handler = reap_child;
+ sigaction_set(SIGCHLD, &sa);
+ sa.sa_handler = clean_up_and_exit;
+ sigaction_set(SIGTERM, &sa);
+ sa.sa_handler = clean_up_and_exit;
+ sigaction_set(SIGINT, &sa);
+ sa.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sa, &saved_pipe_handler);
+
+ reread_config_file(SIGHUP); /* load config from file */
+
+ for (;;) {
+ int ready_fd_cnt;
+ int ctrl, accepted_fd, new_udp_fd;
+ fd_set readable;
+
+ if (maxsock < 0)
+ recalculate_maxsock();
+
+ readable = allsock; /* struct copy */
+ /* if there are no fds to wait on, we will block
+ * until signal wakes us up (maxsock == 0, but readable
+ * never contains fds 0 and 1...) */
+ ready_fd_cnt = select(maxsock + 1, &readable, NULL, NULL, NULL);
+ if (ready_fd_cnt < 0) {
+ if (errno != EINTR) {
+ bb_perror_msg("select");
+ sleep(1);
+ }
+ continue;
+ }
+
+ for (sep = serv_list; ready_fd_cnt && sep; sep = sep->se_next) {
+ if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable))
+ continue;
+
+ ready_fd_cnt--;
+ ctrl = sep->se_fd;
+ accepted_fd = -1;
+ new_udp_fd = -1;
+ if (!sep->se_wait) {
+ if (sep->se_socktype == SOCK_STREAM) {
+ ctrl = accepted_fd = accept(sep->se_fd, NULL, NULL);
+ if (ctrl < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("accept (for %s)", sep->se_service);
+ continue;
+ }
+ }
+ /* "nowait" udp */
+ if (sep->se_socktype == SOCK_DGRAM
+ && sep->se_family != AF_UNIX
+ ) {
+/* How udp "nowait" works:
+ * child peeks at (received and buffered by kernel) UDP packet,
+ * performs connect() on the socket so that it is linked only
+ * to this peer. But this also affects parent, because descriptors
+ * are shared after fork() a-la dup(). When parent performs
+ * select(), it will see this descriptor connected to the peer (!)
+ * and still readable, will act on it and mess things up
+ * (can create many copies of same child, etc).
+ * Parent must create and use new socket instead. */
+ new_udp_fd = socket(sep->se_family, SOCK_DGRAM, 0);
+ if (new_udp_fd < 0) { /* error: eat packet, forget about it */
+ udp_err:
+ recv(sep->se_fd, line, LINE_SIZE, MSG_DONTWAIT);
+ continue;
+ }
+ setsockopt_reuseaddr(new_udp_fd);
+ /* TODO: better do bind after vfork in parent,
+ * so that we don't have two wildcard bound sockets
+ * even for a brief moment? */
+ if (bind(new_udp_fd, &sep->se_lsa->u.sa, sep->se_lsa->len) < 0) {
+ close(new_udp_fd);
+ goto udp_err;
+ }
+ }
+ }
+
+ block_CHLD_HUP_ALRM(&omask);
+ pid = 0;
+#ifdef INETD_BUILTINS_ENABLED
+ /* do we need to fork? */
+ if (sep->se_builtin == NULL
+ || (sep->se_socktype == SOCK_STREAM
+ && sep->se_builtin->bi_fork))
+#endif
+ {
+ if (sep->se_max != 0) {
+ if (++sep->se_count == 1)
+ sep->se_time = monotonic_sec();
+ else if (sep->se_count >= sep->se_max) {
+ unsigned now = monotonic_sec();
+ /* did we accumulate se_max connects too quickly? */
+ if (now - sep->se_time <= CNT_INTERVAL) {
+ bb_error_msg("%s/%s: too many connections, pausing",
+ sep->se_service, sep->se_proto);
+ remove_fd_from_set(sep->se_fd);
+ close(sep->se_fd);
+ sep->se_fd = -1;
+ sep->se_count = 0;
+ rearm_alarm(); /* will revive it in RETRYTIME sec */
+ restore_sigmask(&omask);
+ maybe_close(accepted_fd);
+ continue; /* -> check next fd in fd set */
+ }
+ sep->se_count = 0;
+ }
+ }
+ /* on NOMMU, streamed chargen
+ * builtin wouldn't work, but it is
+ * not allowed on NOMMU (ifdefed out) */
+#ifdef INETD_BUILTINS_ENABLED
+ if (BB_MMU && sep->se_builtin)
+ pid = fork();
+ else
+#endif
+ pid = vfork();
+
+ if (pid < 0) { /* fork error */
+ bb_perror_msg("fork");
+ sleep(1);
+ restore_sigmask(&omask);
+ maybe_close(accepted_fd);
+ continue; /* -> check next fd in fd set */
+ }
+ if (pid == 0)
+ pid--; /* -1: "we did fork and we are child" */
+ }
+ /* if pid == 0 here, we never forked */
+
+ if (pid > 0) { /* parent */
+ if (sep->se_wait) {
+ /* tcp wait: we passed listening socket to child,
+ * will wait for child to terminate */
+ sep->se_wait = pid;
+ remove_fd_from_set(sep->se_fd);
+ }
+ if (new_udp_fd >= 0) {
+ /* udp nowait: child connected the socket,
+ * we created and will use new, unconnected one */
+ xmove_fd(new_udp_fd, sep->se_fd);
+ }
+ restore_sigmask(&omask);
+ maybe_close(accepted_fd);
+ continue; /* -> check next fd in fd set */
+ }
+
+ /* we are either child or didn't vfork at all */
+#ifdef INETD_BUILTINS_ENABLED
+ if (sep->se_builtin) {
+ if (pid) { /* "pid" is -1: we did vfork */
+ close(sep->se_fd); /* listening socket */
+ logmode = 0; /* make xwrite etc silent */
+ }
+ restore_sigmask(&omask);
+ if (sep->se_socktype == SOCK_STREAM)
+ sep->se_builtin->bi_stream_fn(ctrl, sep);
+ else
+ sep->se_builtin->bi_dgram_fn(ctrl, sep);
+ if (pid) /* we did vfork */
+ _exit(EXIT_FAILURE);
+ maybe_close(accepted_fd);
+ continue; /* -> check next fd in fd set */
+ }
+#endif
+ /* child */
+ setsid();
+ /* "nowait" udp */
+ if (new_udp_fd >= 0) {
+ len_and_sockaddr *lsa = xzalloc_lsa(sep->se_family);
+ /* peek at the packet and remember peer addr */
+ int r = recvfrom(ctrl, NULL, 0, MSG_PEEK|MSG_DONTWAIT,
+ &lsa->u.sa, &lsa->len);
+ if (r < 0)
+ goto do_exit1;
+ /* make this socket "connected" to peer addr:
+ * only packets from this peer will be recv'ed,
+ * and bare write()/send() will work on it */
+ connect(ctrl, &lsa->u.sa, lsa->len);
+ free(lsa);
+ }
+ /* prepare env and exec program */
+ pwd = getpwnam(sep->se_user);
+ if (pwd == NULL) {
+ bb_error_msg("%s: no such %s", sep->se_user, "user");
+ goto do_exit1;
+ }
+ if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) {
+ bb_error_msg("%s: no such %s", sep->se_group, "group");
+ goto do_exit1;
+ }
+ if (real_uid != 0 && real_uid != pwd->pw_uid) {
+ /* a user running private inetd */
+ bb_error_msg("non-root must run services as himself");
+ goto do_exit1;
+ }
+ if (pwd->pw_uid) {
+ if (sep->se_group)
+ pwd->pw_gid = grp->gr_gid;
+ /* initgroups, setgid, setuid: */
+ change_identity(pwd);
+ } else if (sep->se_group) {
+ xsetgid(grp->gr_gid);
+ setgroups(1, &grp->gr_gid);
+ }
+ if (rlim_ofile.rlim_cur != rlim_ofile_cur)
+ if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0)
+ bb_perror_msg("setrlimit");
+ closelog();
+ xmove_fd(ctrl, 0);
+ xdup2(0, 1);
+ xdup2(0, 2);
+ /* NB: among others, this loop closes listening socket
+ * for nowait stream children */
+ for (sep2 = serv_list; sep2; sep2 = sep2->se_next)
+ maybe_close(sep2->se_fd);
+ sigaction_set(SIGPIPE, &saved_pipe_handler);
+ restore_sigmask(&omask);
+ BB_EXECVP(sep->se_program, sep->se_argv);
+ bb_perror_msg("exec %s", sep->se_program);
+ do_exit1:
+ /* eat packet in udp case */
+ if (sep->se_socktype != SOCK_STREAM)
+ recv(0, line, LINE_SIZE, MSG_DONTWAIT);
+ _exit(EXIT_FAILURE);
+ } /* for (sep = servtab...) */
+ } /* for (;;) */
+}
+
+#if !BB_MMU
+static const char *const cat_args[] = { "cat", NULL };
+#endif
+
+/*
+ * Internet services provided internally by inetd:
+ */
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_ECHO
+/* Echo service -- echo data back. */
+/* ARGSUSED */
+static void echo_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+#if BB_MMU
+ while (1) {
+ ssize_t sz = safe_read(s, line, LINE_SIZE);
+ if (sz <= 0)
+ break;
+ xwrite(s, line, sz);
+ }
+#else
+ /* We are after vfork here! */
+ /* move network socket to stdin/stdout */
+ xmove_fd(s, STDIN_FILENO);
+ xdup2(STDIN_FILENO, STDOUT_FILENO);
+ /* no error messages please... */
+ close(STDERR_FILENO);
+ xopen(bb_dev_null, O_WRONLY);
+ BB_EXECVP("cat", (char**)cat_args);
+ /* on failure we return to main, which does exit(EXIT_FAILURE) */
+#endif
+}
+static void echo_dg(int s, servtab_t *sep)
+{
+ enum { BUFSIZE = 12*1024 }; /* for jumbo sized packets! :) */
+ char *buf = xmalloc(BUFSIZE); /* too big for stack */
+ int sz;
+ len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+ lsa->len = sep->se_lsa->len;
+ /* dgram builtins are non-forking - DONT BLOCK! */
+ sz = recvfrom(s, buf, BUFSIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len);
+ if (sz > 0)
+ sendto(s, buf, sz, 0, &lsa->u.sa, lsa->len);
+ free(buf);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_ECHO */
+
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD
+/* Discard service -- ignore data. */
+/* ARGSUSED */
+static void discard_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+#if BB_MMU
+ while (safe_read(s, line, LINE_SIZE) > 0)
+ continue;
+#else
+ /* We are after vfork here! */
+ /* move network socket to stdin */
+ xmove_fd(s, STDIN_FILENO);
+ /* discard output */
+ close(STDOUT_FILENO);
+ xopen(bb_dev_null, O_WRONLY);
+ /* no error messages please... */
+ xdup2(STDOUT_FILENO, STDERR_FILENO);
+ BB_EXECVP("cat", (char**)cat_args);
+ /* on failure we return to main, which does exit(EXIT_FAILURE) */
+#endif
+}
+/* ARGSUSED */
+static void discard_dg(int s, servtab_t *sep UNUSED_PARAM)
+{
+ /* dgram builtins are non-forking - DONT BLOCK! */
+ recv(s, line, LINE_SIZE, MSG_DONTWAIT);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DISCARD */
+
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN
+#define LINESIZ 72
+static void init_ring(void)
+{
+ int i;
+
+ end_ring = ring;
+ for (i = 0; i <= 128; ++i)
+ if (isprint(i))
+ *end_ring++ = i;
+}
+/* Character generator. MMU arches only. */
+/* ARGSUSED */
+static void chargen_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+ char *rs;
+ int len;
+ char text[LINESIZ + 2];
+
+ if (!end_ring) {
+ init_ring();
+ rs = ring;
+ }
+
+ text[LINESIZ] = '\r';
+ text[LINESIZ + 1] = '\n';
+ rs = ring;
+ for (;;) {
+ len = end_ring - rs;
+ if (len >= LINESIZ)
+ memmove(text, rs, LINESIZ);
+ else {
+ memmove(text, rs, len);
+ memmove(text + len, ring, LINESIZ - len);
+ }
+ if (++rs == end_ring)
+ rs = ring;
+ xwrite(s, text, sizeof(text));
+ }
+}
+/* ARGSUSED */
+static void chargen_dg(int s, servtab_t *sep)
+{
+ int len;
+ char text[LINESIZ + 2];
+ len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+ /* Eat UDP packet which started it all */
+ /* dgram builtins are non-forking - DONT BLOCK! */
+ lsa->len = sep->se_lsa->len;
+ if (recvfrom(s, text, sizeof(text), MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
+ return;
+
+ if (!end_ring) {
+ init_ring();
+ ring_pos = ring;
+ }
+
+ len = end_ring - ring_pos;
+ if (len >= LINESIZ)
+ memmove(text, ring_pos, LINESIZ);
+ else {
+ memmove(text, ring_pos, len);
+ memmove(text + len, ring, LINESIZ - len);
+ }
+ if (++ring_pos == end_ring)
+ ring_pos = ring;
+ text[LINESIZ] = '\r';
+ text[LINESIZ + 1] = '\n';
+ sendto(s, text, sizeof(text), 0, &lsa->u.sa, lsa->len);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */
+
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_TIME
+/*
+ * Return a machine readable date and time, in the form of the
+ * number of seconds since midnight, Jan 1, 1900. Since gettimeofday
+ * returns the number of seconds since midnight, Jan 1, 1970,
+ * we must add 2208988800 seconds to this figure to make up for
+ * some seventy years Bell Labs was asleep.
+ */
+static uint32_t machtime(void)
+{
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ return htonl((uint32_t)(tv.tv_sec + 2208988800));
+}
+/* ARGSUSED */
+static void machtime_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+ uint32_t result;
+
+ result = machtime();
+ full_write(s, &result, sizeof(result));
+}
+static void machtime_dg(int s, servtab_t *sep)
+{
+ uint32_t result;
+ len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+ lsa->len = sep->se_lsa->len;
+ if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
+ return;
+
+ result = machtime();
+ sendto(s, &result, sizeof(result), 0, &lsa->u.sa, lsa->len);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_TIME */
+
+
+#if ENABLE_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME
+/* Return human-readable time of day */
+/* ARGSUSED */
+static void daytime_stream(int s, servtab_t *sep UNUSED_PARAM)
+{
+ time_t t;
+
+ t = time(NULL);
+ fdprintf(s, "%.24s\r\n", ctime(&t));
+}
+static void daytime_dg(int s, servtab_t *sep)
+{
+ time_t t;
+ len_and_sockaddr *lsa = alloca(LSA_LEN_SIZE + sep->se_lsa->len);
+
+ lsa->len = sep->se_lsa->len;
+ if (recvfrom(s, line, LINE_SIZE, MSG_DONTWAIT, &lsa->u.sa, &lsa->len) < 0)
+ return;
+
+ t = time(NULL);
+ sprintf(line, "%.24s\r\n", ctime(&t));
+ sendto(s, line, strlen(line), 0, &lsa->u.sa, lsa->len);
+}
+#endif /* FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME */
diff --git a/networking/interface.c b/networking/interface.c
new file mode 100644
index 0000000..7861b9f
--- /dev/null
+++ b/networking/interface.c
@@ -0,0 +1,1282 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * stolen from net-tools-1.59 and stripped down for busybox by
+ * Erik Andersen <andersen@codepoet.org>
+ *
+ * Heavily modified by Manuel Novoa III Mar 12, 2001
+ *
+ * Added print_bytes_scaled function to reduce code size.
+ * Added some (potentially) missing defines.
+ * Improved display support for -a and for a named interface.
+ *
+ * -----------------------------------------------------------
+ *
+ * ifconfig This file contains an implementation of the command
+ * that either displays or sets the characteristics of
+ * one or more of the system's networking interfaces.
+ *
+ *
+ * Author: Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ * and others. Copyright 1993 MicroWalt Corporation
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Patched to support 'add' and 'del' keywords for INET(4) addresses
+ * by Mrs. Brisby <mrs.brisby@nimh.org>
+ *
+ * {1.34} - 19980630 - Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * - gettext instead of catgets for i18n
+ * 10/1998 - Andi Kleen. Use interface list primitives.
+ * 20001008 - Bernd Eckenfels, Patch from RH for setting mtu
+ * (default AF was wrong)
+ */
+
+#include <net/if.h>
+#include <net/if_arp.h>
+#include "inet_common.h"
+#include "libbb.h"
+
+
+#if ENABLE_FEATURE_HWIB
+/* #include <linux/if_infiniband.h> */
+#undef INFINIBAND_ALEN
+#define INFINIBAND_ALEN 20
+#endif
+
+#if ENABLE_FEATURE_IPV6
+# define HAVE_AFINET6 1
+#else
+# undef HAVE_AFINET6
+#endif
+
+#define _PATH_PROCNET_DEV "/proc/net/dev"
+#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6"
+
+#ifdef HAVE_AFINET6
+
+#ifndef _LINUX_IN6_H
+/*
+ * This is in linux/include/net/ipv6.h.
+ */
+
+struct in6_ifreq {
+ struct in6_addr ifr6_addr;
+ uint32_t ifr6_prefixlen;
+ unsigned int ifr6_ifindex;
+};
+
+#endif
+
+#endif /* HAVE_AFINET6 */
+
+/* Defines for glibc2.0 users. */
+#ifndef SIOCSIFTXQLEN
+#define SIOCSIFTXQLEN 0x8943
+#define SIOCGIFTXQLEN 0x8942
+#endif
+
+/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */
+#ifndef ifr_qlen
+#define ifr_qlen ifr_ifru.ifru_mtu
+#endif
+
+#ifndef HAVE_TXQUEUELEN
+#define HAVE_TXQUEUELEN 1
+#endif
+
+#ifndef IFF_DYNAMIC
+#define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */
+#endif
+
+/* Display an Internet socket address. */
+static const char* FAST_FUNC INET_sprint(struct sockaddr *sap, int numeric)
+{
+ static char *buff;
+
+ free(buff);
+ if (sap->sa_family == 0xFFFF || sap->sa_family == 0)
+ return "[NONE SET]";
+ buff = INET_rresolve((struct sockaddr_in *) sap, numeric, 0xffffff00);
+ return buff;
+}
+
+#ifdef UNUSED_AND_BUGGY
+static int INET_getsock(char *bufp, struct sockaddr *sap)
+{
+ char *sp = bufp, *bp;
+ unsigned int i;
+ unsigned val;
+ struct sockaddr_in *sock_in;
+
+ sock_in = (struct sockaddr_in *) sap;
+ sock_in->sin_family = AF_INET;
+ sock_in->sin_port = 0;
+
+ val = 0;
+ bp = (char *) &val;
+ for (i = 0; i < sizeof(sock_in->sin_addr.s_addr); i++) {
+ *sp = toupper(*sp);
+
+ if ((unsigned)(*sp - 'A') <= 5)
+ bp[i] |= (int) (*sp - ('A' - 10));
+ else if (isdigit(*sp))
+ bp[i] |= (int) (*sp - '0');
+ else
+ return -1;
+
+ bp[i] <<= 4;
+ sp++;
+ *sp = toupper(*sp);
+
+ if ((unsigned)(*sp - 'A') <= 5)
+ bp[i] |= (int) (*sp - ('A' - 10));
+ else if (isdigit(*sp))
+ bp[i] |= (int) (*sp - '0');
+ else
+ return -1;
+
+ sp++;
+ }
+ sock_in->sin_addr.s_addr = htonl(val);
+
+ return (sp - bufp);
+}
+#endif
+
+static int FAST_FUNC INET_input(/*int type,*/ const char *bufp, struct sockaddr *sap)
+{
+ return INET_resolve(bufp, (struct sockaddr_in *) sap, 0);
+/*
+ switch (type) {
+ case 1:
+ return (INET_getsock(bufp, sap));
+ case 256:
+ return (INET_resolve(bufp, (struct sockaddr_in *) sap, 1));
+ default:
+ return (INET_resolve(bufp, (struct sockaddr_in *) sap, 0));
+ }
+*/
+}
+
+static const struct aftype inet_aftype = {
+ .name = "inet",
+ .title = "DARPA Internet",
+ .af = AF_INET,
+ .alen = 4,
+ .sprint = INET_sprint,
+ .input = INET_input,
+};
+
+#ifdef HAVE_AFINET6
+
+/* Display an Internet socket address. */
+/* dirty! struct sockaddr usually doesn't suffer for inet6 addresses, fst. */
+static const char* FAST_FUNC INET6_sprint(struct sockaddr *sap, int numeric)
+{
+ static char *buff;
+
+ free(buff);
+ if (sap->sa_family == 0xFFFF || sap->sa_family == 0)
+ return "[NONE SET]";
+ buff = INET6_rresolve((struct sockaddr_in6 *) sap, numeric);
+ return buff;
+}
+
+#ifdef UNUSED
+static int INET6_getsock(char *bufp, struct sockaddr *sap)
+{
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *) sap;
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = 0;
+
+ if (inet_pton(AF_INET6, bufp, sin6->sin6_addr.s6_addr) <= 0)
+ return -1;
+
+ return 16; /* ?;) */
+}
+#endif
+
+static int FAST_FUNC INET6_input(/*int type,*/ const char *bufp, struct sockaddr *sap)
+{
+ return INET6_resolve(bufp, (struct sockaddr_in6 *) sap);
+/*
+ switch (type) {
+ case 1:
+ return (INET6_getsock(bufp, sap));
+ default:
+ return (INET6_resolve(bufp, (struct sockaddr_in6 *) sap));
+ }
+*/
+}
+
+static const struct aftype inet6_aftype = {
+ .name = "inet6",
+ .title = "IPv6",
+ .af = AF_INET6,
+ .alen = sizeof(struct in6_addr),
+ .sprint = INET6_sprint,
+ .input = INET6_input,
+};
+
+#endif /* HAVE_AFINET6 */
+
+/* Display an UNSPEC address. */
+static char* FAST_FUNC UNSPEC_print(unsigned char *ptr)
+{
+ static char *buff;
+
+ char *pos;
+ unsigned int i;
+
+ if (!buff)
+ buff = xmalloc(sizeof(struct sockaddr) * 3 + 1);
+ pos = buff;
+ for (i = 0; i < sizeof(struct sockaddr); i++) {
+ /* careful -- not every libc's sprintf returns # bytes written */
+ sprintf(pos, "%02X-", (*ptr++ & 0377));
+ pos += 3;
+ }
+ /* Erase trailing "-". Works as long as sizeof(struct sockaddr) != 0 */
+ *--pos = '\0';
+ return buff;
+}
+
+/* Display an UNSPEC socket address. */
+static const char* FAST_FUNC UNSPEC_sprint(struct sockaddr *sap, int numeric UNUSED_PARAM)
+{
+ if (sap->sa_family == 0xFFFF || sap->sa_family == 0)
+ return "[NONE SET]";
+ return UNSPEC_print((unsigned char *)sap->sa_data);
+}
+
+static const struct aftype unspec_aftype = {
+ .name = "unspec",
+ .title = "UNSPEC",
+ .af = AF_UNSPEC,
+ .alen = 0,
+ .print = UNSPEC_print,
+ .sprint = UNSPEC_sprint,
+};
+
+static const struct aftype *const aftypes[] = {
+ &inet_aftype,
+#ifdef HAVE_AFINET6
+ &inet6_aftype,
+#endif
+ &unspec_aftype,
+ NULL
+};
+
+/* Check our protocol family table for this family. */
+const struct aftype* FAST_FUNC get_aftype(const char *name)
+{
+ const struct aftype *const *afp;
+
+ afp = aftypes;
+ while (*afp != NULL) {
+ if (!strcmp((*afp)->name, name))
+ return (*afp);
+ afp++;
+ }
+ return NULL;
+}
+
+/* Check our protocol family table for this family. */
+static const struct aftype *get_afntype(int af)
+{
+ const struct aftype *const *afp;
+
+ afp = aftypes;
+ while (*afp != NULL) {
+ if ((*afp)->af == af)
+ return *afp;
+ afp++;
+ }
+ return NULL;
+}
+
+struct user_net_device_stats {
+ unsigned long long rx_packets; /* total packets received */
+ unsigned long long tx_packets; /* total packets transmitted */
+ unsigned long long rx_bytes; /* total bytes received */
+ unsigned long long tx_bytes; /* total bytes transmitted */
+ unsigned long rx_errors; /* bad packets received */
+ unsigned long tx_errors; /* packet transmit problems */
+ unsigned long rx_dropped; /* no space in linux buffers */
+ unsigned long tx_dropped; /* no space available in linux */
+ unsigned long rx_multicast; /* multicast packets received */
+ unsigned long rx_compressed;
+ unsigned long tx_compressed;
+ unsigned long collisions;
+
+ /* detailed rx_errors: */
+ unsigned long rx_length_errors;
+ unsigned long rx_over_errors; /* receiver ring buff overflow */
+ unsigned long rx_crc_errors; /* recved pkt with crc error */
+ unsigned long rx_frame_errors; /* recv'd frame alignment error */
+ unsigned long rx_fifo_errors; /* recv'r fifo overrun */
+ unsigned long rx_missed_errors; /* receiver missed packet */
+ /* detailed tx_errors */
+ unsigned long tx_aborted_errors;
+ unsigned long tx_carrier_errors;
+ unsigned long tx_fifo_errors;
+ unsigned long tx_heartbeat_errors;
+ unsigned long tx_window_errors;
+};
+
+struct interface {
+ struct interface *next, *prev;
+ char name[IFNAMSIZ]; /* interface name */
+ short type; /* if type */
+ short flags; /* various flags */
+ int metric; /* routing metric */
+ int mtu; /* MTU value */
+ int tx_queue_len; /* transmit queue length */
+ struct ifmap map; /* hardware setup */
+ struct sockaddr addr; /* IP address */
+ struct sockaddr dstaddr; /* P-P IP address */
+ struct sockaddr broadaddr; /* IP broadcast address */
+ struct sockaddr netmask; /* IP network mask */
+ int has_ip;
+ char hwaddr[32]; /* HW address */
+ int statistics_valid;
+ struct user_net_device_stats stats; /* statistics */
+ int keepalive; /* keepalive value for SLIP */
+ int outfill; /* outfill value for SLIP */
+};
+
+
+smallint interface_opt_a; /* show all interfaces */
+
+static struct interface *int_list, *int_last;
+
+
+#if 0
+/* like strcmp(), but knows about numbers */
+except that the freshly added calls to xatoul() brf on ethernet aliases with
+uClibc with e.g.: ife->name='lo' name='eth0:1'
+static int nstrcmp(const char *a, const char *b)
+{
+ const char *a_ptr = a;
+ const char *b_ptr = b;
+
+ while (*a == *b) {
+ if (*a == '\0') {
+ return 0;
+ }
+ if (!isdigit(*a) && isdigit(*(a+1))) {
+ a_ptr = a+1;
+ b_ptr = b+1;
+ }
+ a++;
+ b++;
+ }
+
+ if (isdigit(*a) && isdigit(*b)) {
+ return xatoul(a_ptr) > xatoul(b_ptr) ? 1 : -1;
+ }
+ return *a - *b;
+}
+#endif
+
+static struct interface *add_interface(char *name)
+{
+ struct interface *ife, **nextp, *new;
+
+ for (ife = int_last; ife; ife = ife->prev) {
+ int n = /*n*/strcmp(ife->name, name);
+
+ if (n == 0)
+ return ife;
+ if (n < 0)
+ break;
+ }
+
+ new = xzalloc(sizeof(*new));
+ strncpy(new->name, name, IFNAMSIZ);
+ nextp = ife ? &ife->next : &int_list;
+ new->prev = ife;
+ new->next = *nextp;
+ if (new->next)
+ new->next->prev = new;
+ else
+ int_last = new;
+ *nextp = new;
+ return new;
+}
+
+static char *get_name(char *name, char *p)
+{
+ /* Extract <name> from nul-terminated p where p matches
+ <name>: after leading whitespace.
+ If match is not made, set name empty and return unchanged p */
+ int namestart = 0, nameend = 0;
+
+ while (isspace(p[namestart]))
+ namestart++;
+ nameend = namestart;
+ while (p[nameend] && p[nameend] != ':' && !isspace(p[nameend]))
+ nameend++;
+ if (p[nameend] == ':') {
+ if ((nameend - namestart) < IFNAMSIZ) {
+ memcpy(name, &p[namestart], nameend - namestart);
+ name[nameend - namestart] = '\0';
+ p = &p[nameend];
+ } else {
+ /* Interface name too large */
+ name[0] = '\0';
+ }
+ } else {
+ /* trailing ':' not found - return empty */
+ name[0] = '\0';
+ }
+ return p + 1;
+}
+
+/* If scanf supports size qualifiers for %n conversions, then we can
+ * use a modified fmt that simply stores the position in the fields
+ * having no associated fields in the proc string. Of course, we need
+ * to zero them again when we're done. But that is smaller than the
+ * old approach of multiple scanf occurrences with large numbers of
+ * args. */
+
+/* static const char *const ss_fmt[] = { */
+/* "%lln%llu%lu%lu%lu%lu%ln%ln%lln%llu%lu%lu%lu%lu%lu", */
+/* "%llu%llu%lu%lu%lu%lu%ln%ln%llu%llu%lu%lu%lu%lu%lu", */
+/* "%llu%llu%lu%lu%lu%lu%lu%lu%llu%llu%lu%lu%lu%lu%lu%lu" */
+/* }; */
+
+ /* Lie about the size of the int pointed to for %n. */
+#if INT_MAX == LONG_MAX
+static const char *const ss_fmt[] = {
+ "%n%llu%u%u%u%u%n%n%n%llu%u%u%u%u%u",
+ "%llu%llu%u%u%u%u%n%n%llu%llu%u%u%u%u%u",
+ "%llu%llu%u%u%u%u%u%u%llu%llu%u%u%u%u%u%u"
+};
+#else
+static const char *const ss_fmt[] = {
+ "%n%llu%lu%lu%lu%lu%n%n%n%llu%lu%lu%lu%lu%lu",
+ "%llu%llu%lu%lu%lu%lu%n%n%llu%llu%lu%lu%lu%lu%lu",
+ "%llu%llu%lu%lu%lu%lu%lu%lu%llu%llu%lu%lu%lu%lu%lu%lu"
+};
+
+#endif
+
+static void get_dev_fields(char *bp, struct interface *ife, int procnetdev_vsn)
+{
+ memset(&ife->stats, 0, sizeof(struct user_net_device_stats));
+
+ sscanf(bp, ss_fmt[procnetdev_vsn],
+ &ife->stats.rx_bytes, /* missing for 0 */
+ &ife->stats.rx_packets,
+ &ife->stats.rx_errors,
+ &ife->stats.rx_dropped,
+ &ife->stats.rx_fifo_errors,
+ &ife->stats.rx_frame_errors,
+ &ife->stats.rx_compressed, /* missing for <= 1 */
+ &ife->stats.rx_multicast, /* missing for <= 1 */
+ &ife->stats.tx_bytes, /* missing for 0 */
+ &ife->stats.tx_packets,
+ &ife->stats.tx_errors,
+ &ife->stats.tx_dropped,
+ &ife->stats.tx_fifo_errors,
+ &ife->stats.collisions,
+ &ife->stats.tx_carrier_errors,
+ &ife->stats.tx_compressed /* missing for <= 1 */
+ );
+
+ if (procnetdev_vsn <= 1) {
+ if (procnetdev_vsn == 0) {
+ ife->stats.rx_bytes = 0;
+ ife->stats.tx_bytes = 0;
+ }
+ ife->stats.rx_multicast = 0;
+ ife->stats.rx_compressed = 0;
+ ife->stats.tx_compressed = 0;
+ }
+}
+
+static int procnetdev_version(char *buf)
+{
+ if (strstr(buf, "compressed"))
+ return 2;
+ if (strstr(buf, "bytes"))
+ return 1;
+ return 0;
+}
+
+static int if_readconf(void)
+{
+ int numreqs = 30;
+ struct ifconf ifc;
+ struct ifreq *ifr;
+ int n, err = -1;
+ int skfd;
+
+ ifc.ifc_buf = NULL;
+
+ /* SIOCGIFCONF currently seems to only work properly on AF_INET sockets
+ (as of 2.1.128) */
+ skfd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (skfd < 0) {
+ bb_perror_msg("error: no inet socket available");
+ return -1;
+ }
+
+ for (;;) {
+ ifc.ifc_len = sizeof(struct ifreq) * numreqs;
+ ifc.ifc_buf = xrealloc(ifc.ifc_buf, ifc.ifc_len);
+
+ if (ioctl_or_warn(skfd, SIOCGIFCONF, &ifc) < 0) {
+ goto out;
+ }
+ if (ifc.ifc_len == (int)(sizeof(struct ifreq) * numreqs)) {
+ /* assume it overflowed and try again */
+ numreqs += 10;
+ continue;
+ }
+ break;
+ }
+
+ ifr = ifc.ifc_req;
+ for (n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq)) {
+ add_interface(ifr->ifr_name);
+ ifr++;
+ }
+ err = 0;
+
+ out:
+ close(skfd);
+ free(ifc.ifc_buf);
+ return err;
+}
+
+static int if_readlist_proc(char *target)
+{
+ static smallint proc_read;
+
+ FILE *fh;
+ char buf[512];
+ struct interface *ife;
+ int err, procnetdev_vsn;
+
+ if (proc_read)
+ return 0;
+ if (!target)
+ proc_read = 1;
+
+ fh = fopen_or_warn(_PATH_PROCNET_DEV, "r");
+ if (!fh) {
+ return if_readconf();
+ }
+ fgets(buf, sizeof buf, fh); /* eat line */
+ fgets(buf, sizeof buf, fh);
+
+ procnetdev_vsn = procnetdev_version(buf);
+
+ err = 0;
+ while (fgets(buf, sizeof buf, fh)) {
+ char *s, name[128];
+
+ s = get_name(name, buf);
+ ife = add_interface(name);
+ get_dev_fields(s, ife, procnetdev_vsn);
+ ife->statistics_valid = 1;
+ if (target && !strcmp(target, name))
+ break;
+ }
+ if (ferror(fh)) {
+ bb_perror_msg(_PATH_PROCNET_DEV);
+ err = -1;
+ proc_read = 0;
+ }
+ fclose(fh);
+ return err;
+}
+
+static int if_readlist(void)
+{
+ int err = if_readlist_proc(NULL);
+ /* Needed in order to get ethN:M aliases */
+ if (!err)
+ err = if_readconf();
+ return err;
+}
+
+/* Fetch the interface configuration from the kernel. */
+static int if_fetch(struct interface *ife)
+{
+ struct ifreq ifr;
+ char *ifname = ife->name;
+ int skfd;
+
+ skfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) {
+ close(skfd);
+ return -1;
+ }
+ ife->flags = ifr.ifr_flags;
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ memset(ife->hwaddr, 0, 32);
+ if (ioctl(skfd, SIOCGIFHWADDR, &ifr) >= 0)
+ memcpy(ife->hwaddr, ifr.ifr_hwaddr.sa_data, 8);
+
+ ife->type = ifr.ifr_hwaddr.sa_family;
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ife->metric = 0;
+ if (ioctl(skfd, SIOCGIFMETRIC, &ifr) >= 0)
+ ife->metric = ifr.ifr_metric;
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ife->mtu = 0;
+ if (ioctl(skfd, SIOCGIFMTU, &ifr) >= 0)
+ ife->mtu = ifr.ifr_mtu;
+
+ memset(&ife->map, 0, sizeof(struct ifmap));
+#ifdef SIOCGIFMAP
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0)
+ ife->map = ifr.ifr_map;
+#endif
+
+#ifdef HAVE_TXQUEUELEN
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ife->tx_queue_len = -1; /* unknown value */
+ if (ioctl(skfd, SIOCGIFTXQLEN, &ifr) >= 0)
+ ife->tx_queue_len = ifr.ifr_qlen;
+#else
+ ife->tx_queue_len = -1; /* unknown value */
+#endif
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ifr.ifr_addr.sa_family = AF_INET;
+ memset(&ife->addr, 0, sizeof(struct sockaddr));
+ if (ioctl(skfd, SIOCGIFADDR, &ifr) == 0) {
+ ife->has_ip = 1;
+ ife->addr = ifr.ifr_addr;
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ memset(&ife->dstaddr, 0, sizeof(struct sockaddr));
+ if (ioctl(skfd, SIOCGIFDSTADDR, &ifr) >= 0)
+ ife->dstaddr = ifr.ifr_dstaddr;
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ memset(&ife->broadaddr, 0, sizeof(struct sockaddr));
+ if (ioctl(skfd, SIOCGIFBRDADDR, &ifr) >= 0)
+ ife->broadaddr = ifr.ifr_broadaddr;
+
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ memset(&ife->netmask, 0, sizeof(struct sockaddr));
+ if (ioctl(skfd, SIOCGIFNETMASK, &ifr) >= 0)
+ ife->netmask = ifr.ifr_netmask;
+ }
+
+ close(skfd);
+ return 0;
+}
+
+static int do_if_fetch(struct interface *ife)
+{
+ if (if_fetch(ife) < 0) {
+ const char *errmsg;
+
+ if (errno == ENODEV) {
+ /* Give better error message for this case. */
+ errmsg = "Device not found";
+ } else {
+ errmsg = strerror(errno);
+ }
+ bb_error_msg("%s: error fetching interface information: %s",
+ ife->name, errmsg);
+ return -1;
+ }
+ return 0;
+}
+
+static const struct hwtype unspec_hwtype = {
+ .name = "unspec",
+ .title = "UNSPEC",
+ .type = -1,
+ .print = UNSPEC_print
+};
+
+static const struct hwtype loop_hwtype = {
+ .name = "loop",
+ .title = "Local Loopback",
+ .type = ARPHRD_LOOPBACK
+};
+
+#include <net/if_arp.h>
+
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION)
+#include <net/ethernet.h>
+#else
+#include <linux/if_ether.h>
+#endif
+
+/* Display an Ethernet address in readable format. */
+static char* FAST_FUNC ether_print(unsigned char *ptr)
+{
+ static char *buff;
+
+ free(buff);
+ buff = xasprintf("%02X:%02X:%02X:%02X:%02X:%02X",
+ (ptr[0] & 0377), (ptr[1] & 0377), (ptr[2] & 0377),
+ (ptr[3] & 0377), (ptr[4] & 0377), (ptr[5] & 0377)
+ );
+ return buff;
+}
+
+static int FAST_FUNC ether_input(const char *bufp, struct sockaddr *sap);
+
+static const struct hwtype ether_hwtype = {
+ .name = "ether",
+ .title = "Ethernet",
+ .type = ARPHRD_ETHER,
+ .alen = ETH_ALEN,
+ .print = ether_print,
+ .input = ether_input
+};
+
+static unsigned hexchar2int(char c)
+{
+ if (isdigit(c))
+ return c - '0';
+ c &= ~0x20; /* a -> A */
+ if ((unsigned)(c - 'A') <= 5)
+ return c - ('A' - 10);
+ return ~0U;
+}
+
+/* Input an Ethernet address and convert to binary. */
+static int FAST_FUNC ether_input(const char *bufp, struct sockaddr *sap)
+{
+ unsigned char *ptr;
+ char c;
+ int i;
+ unsigned val;
+
+ sap->sa_family = ether_hwtype.type;
+ ptr = (unsigned char*) sap->sa_data;
+
+ i = 0;
+ while ((*bufp != '\0') && (i < ETH_ALEN)) {
+ val = hexchar2int(*bufp++) * 0x10;
+ if (val > 0xff) {
+ errno = EINVAL;
+ return -1;
+ }
+ c = *bufp;
+ if (c == ':' || c == 0)
+ val >>= 4;
+ else {
+ val |= hexchar2int(c);
+ if (val > 0xff) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+ if (c != 0)
+ bufp++;
+ *ptr++ = (unsigned char) val;
+ i++;
+
+ /* We might get a semicolon here - not required. */
+ if (*bufp == ':') {
+ bufp++;
+ }
+ }
+ return 0;
+}
+
+#include <net/if_arp.h>
+
+static const struct hwtype ppp_hwtype = {
+ .name = "ppp",
+ .title = "Point-to-Point Protocol",
+ .type = ARPHRD_PPP
+};
+
+#if ENABLE_FEATURE_IPV6
+static const struct hwtype sit_hwtype = {
+ .name = "sit",
+ .title = "IPv6-in-IPv4",
+ .type = ARPHRD_SIT,
+ .print = UNSPEC_print,
+ .suppress_null_addr = 1
+};
+#endif
+#if ENABLE_FEATURE_HWIB
+static const struct hwtype ib_hwtype = {
+ .name = "infiniband",
+ .title = "InfiniBand",
+ .type = ARPHRD_INFINIBAND,
+ .alen = INFINIBAND_ALEN,
+ .print = UNSPEC_print,
+ .input = in_ib,
+};
+#endif
+
+
+static const struct hwtype *const hwtypes[] = {
+ &loop_hwtype,
+ &ether_hwtype,
+ &ppp_hwtype,
+ &unspec_hwtype,
+#if ENABLE_FEATURE_IPV6
+ &sit_hwtype,
+#endif
+#if ENABLE_FEATURE_HWIB
+ &ib_hwtype,
+#endif
+ NULL
+};
+
+#ifdef IFF_PORTSEL
+static const char *const if_port_text[] = {
+ /* Keep in step with <linux/netdevice.h> */
+ "unknown",
+ "10base2",
+ "10baseT",
+ "AUI",
+ "100baseT",
+ "100baseTX",
+ "100baseFX",
+ NULL
+};
+#endif
+
+/* Check our hardware type table for this type. */
+const struct hwtype* FAST_FUNC get_hwtype(const char *name)
+{
+ const struct hwtype *const *hwp;
+
+ hwp = hwtypes;
+ while (*hwp != NULL) {
+ if (!strcmp((*hwp)->name, name))
+ return (*hwp);
+ hwp++;
+ }
+ return NULL;
+}
+
+/* Check our hardware type table for this type. */
+const struct hwtype* FAST_FUNC get_hwntype(int type)
+{
+ const struct hwtype *const *hwp;
+
+ hwp = hwtypes;
+ while (*hwp != NULL) {
+ if ((*hwp)->type == type)
+ return *hwp;
+ hwp++;
+ }
+ return NULL;
+}
+
+/* return 1 if address is all zeros */
+static int hw_null_address(const struct hwtype *hw, void *ap)
+{
+ int i;
+ unsigned char *address = (unsigned char *) ap;
+
+ for (i = 0; i < hw->alen; i++)
+ if (address[i])
+ return 0;
+ return 1;
+}
+
+static const char TRext[] ALIGN1 = "\0\0\0Ki\0Mi\0Gi\0Ti";
+
+static void print_bytes_scaled(unsigned long long ull, const char *end)
+{
+ unsigned long long int_part;
+ const char *ext;
+ unsigned int frac_part;
+ int i;
+
+ frac_part = 0;
+ ext = TRext;
+ int_part = ull;
+ i = 4;
+ do {
+ if (int_part >= 1024) {
+ frac_part = ((((unsigned int) int_part) & (1024-1)) * 10) / 1024;
+ int_part /= 1024;
+ ext += 3; /* KiB, MiB, GiB, TiB */
+ }
+ --i;
+ } while (i);
+
+ printf("X bytes:%llu (%llu.%u %sB)%s", ull, int_part, frac_part, ext, end);
+}
+
+static void ife_print(struct interface *ptr)
+{
+ const struct aftype *ap;
+ const struct hwtype *hw;
+ int hf;
+ int can_compress = 0;
+
+#ifdef HAVE_AFINET6
+ FILE *f;
+ char addr6[40], devname[20];
+ struct sockaddr_in6 sap;
+ int plen, scope, dad_status, if_idx;
+ char addr6p[8][5];
+#endif
+
+ ap = get_afntype(ptr->addr.sa_family);
+ if (ap == NULL)
+ ap = get_afntype(0);
+
+ hf = ptr->type;
+
+ if (hf == ARPHRD_CSLIP || hf == ARPHRD_CSLIP6)
+ can_compress = 1;
+
+ hw = get_hwntype(hf);
+ if (hw == NULL)
+ hw = get_hwntype(-1);
+
+ printf("%-9.9s Link encap:%s ", ptr->name, hw->title);
+ /* For some hardware types (eg Ash, ATM) we don't print the
+ hardware address if it's null. */
+ if (hw->print != NULL && (!(hw_null_address(hw, ptr->hwaddr) &&
+ hw->suppress_null_addr)))
+ printf("HWaddr %s ", hw->print((unsigned char *)ptr->hwaddr));
+#ifdef IFF_PORTSEL
+ if (ptr->flags & IFF_PORTSEL) {
+ printf("Media:%s", if_port_text[ptr->map.port] /* [0] */);
+ if (ptr->flags & IFF_AUTOMEDIA)
+ printf("(auto)");
+ }
+#endif
+ bb_putchar('\n');
+
+ if (ptr->has_ip) {
+ printf(" %s addr:%s ", ap->name,
+ ap->sprint(&ptr->addr, 1));
+ if (ptr->flags & IFF_POINTOPOINT) {
+ printf(" P-t-P:%s ", ap->sprint(&ptr->dstaddr, 1));
+ }
+ if (ptr->flags & IFF_BROADCAST) {
+ printf(" Bcast:%s ", ap->sprint(&ptr->broadaddr, 1));
+ }
+ printf(" Mask:%s\n", ap->sprint(&ptr->netmask, 1));
+ }
+
+#ifdef HAVE_AFINET6
+
+#define IPV6_ADDR_ANY 0x0000U
+
+#define IPV6_ADDR_UNICAST 0x0001U
+#define IPV6_ADDR_MULTICAST 0x0002U
+#define IPV6_ADDR_ANYCAST 0x0004U
+
+#define IPV6_ADDR_LOOPBACK 0x0010U
+#define IPV6_ADDR_LINKLOCAL 0x0020U
+#define IPV6_ADDR_SITELOCAL 0x0040U
+
+#define IPV6_ADDR_COMPATv4 0x0080U
+
+#define IPV6_ADDR_SCOPE_MASK 0x00f0U
+
+#define IPV6_ADDR_MAPPED 0x1000U
+#define IPV6_ADDR_RESERVED 0x2000U /* reserved address space */
+
+ f = fopen_for_read(_PATH_PROCNET_IFINET6);
+ if (f != NULL) {
+ while (fscanf
+ (f, "%4s%4s%4s%4s%4s%4s%4s%4s %08x %02x %02x %02x %20s\n",
+ addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4],
+ addr6p[5], addr6p[6], addr6p[7], &if_idx, &plen, &scope,
+ &dad_status, devname) != EOF
+ ) {
+ if (!strcmp(devname, ptr->name)) {
+ sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s",
+ addr6p[0], addr6p[1], addr6p[2], addr6p[3],
+ addr6p[4], addr6p[5], addr6p[6], addr6p[7]);
+ inet_pton(AF_INET6, addr6,
+ (struct sockaddr *) &sap.sin6_addr);
+ sap.sin6_family = AF_INET6;
+ printf(" inet6 addr: %s/%d",
+ INET6_sprint((struct sockaddr *) &sap, 1),
+ plen);
+ printf(" Scope:");
+ switch (scope & IPV6_ADDR_SCOPE_MASK) {
+ case 0:
+ puts("Global");
+ break;
+ case IPV6_ADDR_LINKLOCAL:
+ puts("Link");
+ break;
+ case IPV6_ADDR_SITELOCAL:
+ puts("Site");
+ break;
+ case IPV6_ADDR_COMPATv4:
+ puts("Compat");
+ break;
+ case IPV6_ADDR_LOOPBACK:
+ puts("Host");
+ break;
+ default:
+ puts("Unknown");
+ }
+ }
+ }
+ fclose(f);
+ }
+#endif
+
+ printf(" ");
+ /* DONT FORGET TO ADD THE FLAGS IN ife_print_short, too */
+
+ if (ptr->flags == 0) {
+ printf("[NO FLAGS] ");
+ } else {
+ static const char ife_print_flags_strs[] ALIGN1 =
+ "UP\0"
+ "BROADCAST\0"
+ "DEBUG\0"
+ "LOOPBACK\0"
+ "POINTOPOINT\0"
+ "NOTRAILERS\0"
+ "RUNNING\0"
+ "NOARP\0"
+ "PROMISC\0"
+ "ALLMULTI\0"
+ "SLAVE\0"
+ "MASTER\0"
+ "MULTICAST\0"
+#ifdef HAVE_DYNAMIC
+ "DYNAMIC\0"
+#endif
+ ;
+ static const unsigned short ife_print_flags_mask[] ALIGN2 = {
+ IFF_UP,
+ IFF_BROADCAST,
+ IFF_DEBUG,
+ IFF_LOOPBACK,
+ IFF_POINTOPOINT,
+ IFF_NOTRAILERS,
+ IFF_RUNNING,
+ IFF_NOARP,
+ IFF_PROMISC,
+ IFF_ALLMULTI,
+ IFF_SLAVE,
+ IFF_MASTER,
+ IFF_MULTICAST
+#ifdef HAVE_DYNAMIC
+ ,IFF_DYNAMIC
+#endif
+ };
+ const unsigned short *mask = ife_print_flags_mask;
+ const char *str = ife_print_flags_strs;
+ do {
+ if (ptr->flags & *mask) {
+ printf("%s ", str);
+ }
+ mask++;
+ str += strlen(str) + 1;
+ } while (*str);
+ }
+
+ /* DONT FORGET TO ADD THE FLAGS IN ife_print_short */
+ printf(" MTU:%d Metric:%d", ptr->mtu, ptr->metric ? ptr->metric : 1);
+#ifdef SIOCSKEEPALIVE
+ if (ptr->outfill || ptr->keepalive)
+ printf(" Outfill:%d Keepalive:%d", ptr->outfill, ptr->keepalive);
+#endif
+ bb_putchar('\n');
+
+ /* If needed, display the interface statistics. */
+
+ if (ptr->statistics_valid) {
+ /* XXX: statistics are currently only printed for the primary address,
+ * not for the aliases, although strictly speaking they're shared
+ * by all addresses.
+ */
+ printf(" ");
+
+ printf("RX packets:%llu errors:%lu dropped:%lu overruns:%lu frame:%lu\n",
+ ptr->stats.rx_packets, ptr->stats.rx_errors,
+ ptr->stats.rx_dropped, ptr->stats.rx_fifo_errors,
+ ptr->stats.rx_frame_errors);
+ if (can_compress)
+ printf(" compressed:%lu\n",
+ ptr->stats.rx_compressed);
+ printf(" ");
+ printf("TX packets:%llu errors:%lu dropped:%lu overruns:%lu carrier:%lu\n",
+ ptr->stats.tx_packets, ptr->stats.tx_errors,
+ ptr->stats.tx_dropped, ptr->stats.tx_fifo_errors,
+ ptr->stats.tx_carrier_errors);
+ printf(" collisions:%lu ", ptr->stats.collisions);
+ if (can_compress)
+ printf("compressed:%lu ", ptr->stats.tx_compressed);
+ if (ptr->tx_queue_len != -1)
+ printf("txqueuelen:%d ", ptr->tx_queue_len);
+ printf("\n R");
+ print_bytes_scaled(ptr->stats.rx_bytes, " T");
+ print_bytes_scaled(ptr->stats.tx_bytes, "\n");
+
+ }
+
+ if ((ptr->map.irq || ptr->map.mem_start || ptr->map.dma ||
+ ptr->map.base_addr)) {
+ printf(" ");
+ if (ptr->map.irq)
+ printf("Interrupt:%d ", ptr->map.irq);
+ if (ptr->map.base_addr >= 0x100) /* Only print devices using it for
+ I/O maps */
+ printf("Base address:0x%lx ",
+ (unsigned long) ptr->map.base_addr);
+ if (ptr->map.mem_start) {
+ printf("Memory:%lx-%lx ", ptr->map.mem_start,
+ ptr->map.mem_end);
+ }
+ if (ptr->map.dma)
+ printf("DMA chan:%x ", ptr->map.dma);
+ bb_putchar('\n');
+ }
+ bb_putchar('\n');
+}
+
+
+static int do_if_print(struct interface *ife) /*, int *opt_a)*/
+{
+ int res;
+
+ res = do_if_fetch(ife);
+ if (res >= 0) {
+ if ((ife->flags & IFF_UP) || interface_opt_a)
+ ife_print(ife);
+ }
+ return res;
+}
+
+static struct interface *lookup_interface(char *name)
+{
+ struct interface *ife = NULL;
+
+ if (if_readlist_proc(name) < 0)
+ return NULL;
+ ife = add_interface(name);
+ return ife;
+}
+
+#ifdef UNUSED
+static int for_all_interfaces(int (*doit) (struct interface *, void *),
+ void *cookie)
+{
+ struct interface *ife;
+
+ if (!int_list && (if_readlist() < 0))
+ return -1;
+ for (ife = int_list; ife; ife = ife->next) {
+ int err = doit(ife, cookie);
+
+ if (err)
+ return err;
+ }
+ return 0;
+}
+#endif
+
+/* for ipv4 add/del modes */
+static int if_print(char *ifname)
+{
+ struct interface *ife;
+ int res;
+
+ if (!ifname) {
+ /*res = for_all_interfaces(do_if_print, &interface_opt_a);*/
+ if (!int_list && (if_readlist() < 0))
+ return -1;
+ for (ife = int_list; ife; ife = ife->next) {
+ int err = do_if_print(ife); /*, &interface_opt_a);*/
+ if (err)
+ return err;
+ }
+ return 0;
+ }
+ ife = lookup_interface(ifname);
+ res = do_if_fetch(ife);
+ if (res >= 0)
+ ife_print(ife);
+ return res;
+}
+
+#if ENABLE_FEATURE_HWIB
+/* Input an Infiniband address and convert to binary. */
+int FAST_FUNC in_ib(const char *bufp, struct sockaddr *sap)
+{
+ unsigned char *ptr;
+ char c;
+ const char *orig;
+ int i;
+ unsigned val;
+
+ sap->sa_family = ib_hwtype.type;
+ ptr = (unsigned char *) sap->sa_data;
+
+ i = 0;
+ orig = bufp;
+ while ((*bufp != '\0') && (i < INFINIBAND_ALEN)) {
+ val = 0;
+ c = *bufp++;
+ if (isdigit(c))
+ val = c - '0';
+ else if (c >= 'a' && c <= 'f')
+ val = c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ val = c - 'A' + 10;
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+ val <<= 4;
+ c = *bufp;
+ if (isdigit(c))
+ val |= c - '0';
+ else if (c >= 'a' && c <= 'f')
+ val |= c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ val |= c - 'A' + 10;
+ else if (c == ':' || c == 0)
+ val >>= 4;
+ else {
+ errno = EINVAL;
+ return -1;
+ }
+ if (c != 0)
+ bufp++;
+ *ptr++ = (unsigned char) (val & 0377);
+ i++;
+
+ /* We might get a semicolon here - not required. */
+ if (*bufp == ':') {
+ bufp++;
+ }
+ }
+#ifdef DEBUG
+ fprintf(stderr, "in_ib(%s): %s\n", orig, UNSPEC_print(sap->sa_data));
+#endif
+ return 0;
+}
+#endif
+
+
+int FAST_FUNC display_interfaces(char *ifname)
+{
+ int status;
+
+ status = if_print(ifname);
+
+ return (status < 0); /* status < 0 == 1 -- error */
+}
diff --git a/networking/ip.c b/networking/ip.c
new file mode 100644
index 0000000..9903c68
--- /dev/null
+++ b/networking/ip.c
@@ -0,0 +1,123 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ip.c "ip" utility frontend.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
+ * Bernhard Reutner-Fischer rewrote to use index_in_substr_array
+ */
+
+#include "libbb.h"
+
+#include "libiproute/utils.h"
+#include "libiproute/ip_common.h"
+
+#if ENABLE_FEATURE_IP_ADDRESS \
+ || ENABLE_FEATURE_IP_ROUTE \
+ || ENABLE_FEATURE_IP_LINK \
+ || ENABLE_FEATURE_IP_TUNNEL \
+ || ENABLE_FEATURE_IP_RULE
+
+static int NORETURN ip_print_help(char **argv UNUSED_PARAM)
+{
+ bb_show_usage();
+}
+
+static int ip_do(int (*ip_func)(char **argv), char **argv)
+{
+ argv = ip_parse_common_args(argv + 1);
+ return ip_func(argv);
+}
+
+#if ENABLE_FEATURE_IP_ADDRESS
+int ipaddr_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipaddr_main(int argc UNUSED_PARAM, char **argv)
+{
+ return ip_do(do_ipaddr, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_LINK
+int iplink_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iplink_main(int argc UNUSED_PARAM, char **argv)
+{
+ return ip_do(do_iplink, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_ROUTE
+int iproute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iproute_main(int argc UNUSED_PARAM, char **argv)
+{
+ return ip_do(do_iproute, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_RULE
+int iprule_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iprule_main(int argc UNUSED_PARAM, char **argv)
+{
+ return ip_do(do_iprule, argv);
+}
+#endif
+#if ENABLE_FEATURE_IP_TUNNEL
+int iptunnel_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int iptunnel_main(int argc UNUSED_PARAM, char **argv)
+{
+ return ip_do(do_iptunnel, argv);
+}
+#endif
+
+
+int ip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ip_main(int argc UNUSED_PARAM, char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ USE_FEATURE_IP_ADDRESS("address\0")
+ USE_FEATURE_IP_ROUTE("route\0")
+ USE_FEATURE_IP_LINK("link\0")
+ USE_FEATURE_IP_TUNNEL("tunnel\0" "tunl\0")
+ USE_FEATURE_IP_RULE("rule\0")
+ ;
+ enum {
+ USE_FEATURE_IP_ADDRESS(IP_addr,)
+ USE_FEATURE_IP_ROUTE(IP_route,)
+ USE_FEATURE_IP_LINK(IP_link,)
+ USE_FEATURE_IP_TUNNEL(IP_tunnel, IP_tunl,)
+ USE_FEATURE_IP_RULE(IP_rule,)
+ IP_none
+ };
+ int (*ip_func)(char**) = ip_print_help;
+
+ argv = ip_parse_common_args(argv + 1);
+ if (*argv) {
+ int key = index_in_substrings(keywords, *argv);
+ argv++;
+#if ENABLE_FEATURE_IP_ADDRESS
+ if (key == IP_addr)
+ ip_func = do_ipaddr;
+#endif
+#if ENABLE_FEATURE_IP_ROUTE
+ if (key == IP_route)
+ ip_func = do_iproute;
+#endif
+#if ENABLE_FEATURE_IP_LINK
+ if (key == IP_link)
+ ip_func = do_iplink;
+#endif
+#if ENABLE_FEATURE_IP_TUNNEL
+ if (key == IP_tunnel || key == IP_tunl)
+ ip_func = do_iptunnel;
+#endif
+#if ENABLE_FEATURE_IP_RULE
+ if (key == IP_rule)
+ ip_func = do_iprule;
+#endif
+ }
+ return ip_func(argv);
+}
+
+#endif /* any of ENABLE_FEATURE_IP_xxx is 1 */
diff --git a/networking/ipcalc.c b/networking/ipcalc.c
new file mode 100644
index 0000000..d8fa5f3
--- /dev/null
+++ b/networking/ipcalc.c
@@ -0,0 +1,190 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini ipcalc implementation for busybox
+ *
+ * By Jordan Crouse <jordan@cosmicpenguin.net>
+ * Stephan Linz <linz@li-pro.net>
+ *
+ * This is a complete reimplementation of the ipcalc program
+ * from Red Hat. I didn't look at their source code, but there
+ * is no denying that this is a loving reimplementation
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include "libbb.h"
+
+#define CLASS_A_NETMASK ntohl(0xFF000000)
+#define CLASS_B_NETMASK ntohl(0xFFFF0000)
+#define CLASS_C_NETMASK ntohl(0xFFFFFF00)
+
+static unsigned long get_netmask(unsigned long ipaddr)
+{
+ ipaddr = htonl(ipaddr);
+
+ if ((ipaddr & 0xC0000000) == 0xC0000000)
+ return CLASS_C_NETMASK;
+ else if ((ipaddr & 0x80000000) == 0x80000000)
+ return CLASS_B_NETMASK;
+ else if ((ipaddr & 0x80000000) == 0)
+ return CLASS_A_NETMASK;
+ else
+ return 0;
+}
+
+#if ENABLE_FEATURE_IPCALC_FANCY
+static int get_prefix(unsigned long netmask)
+{
+ unsigned long msk = 0x80000000;
+ int ret = 0;
+
+ netmask = htonl(netmask);
+ while (msk) {
+ if (netmask & msk)
+ ret++;
+ msk >>= 1;
+ }
+ return ret;
+}
+#else
+int get_prefix(unsigned long netmask);
+#endif
+
+
+#define NETMASK 0x01
+#define BROADCAST 0x02
+#define NETWORK 0x04
+#define NETPREFIX 0x08
+#define HOSTNAME 0x10
+#define SILENT 0x20
+
+#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS
+ static const char ipcalc_longopts[] ALIGN1 =
+ "netmask\0" No_argument "m"
+ "broadcast\0" No_argument "b"
+ "network\0" No_argument "n"
+# if ENABLE_FEATURE_IPCALC_FANCY
+ "prefix\0" No_argument "p"
+ "hostname\0" No_argument "h"
+ "silent\0" No_argument "s"
+# endif
+ ;
+#endif
+
+int ipcalc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ipcalc_main(int argc, char **argv)
+{
+ unsigned opt;
+ int have_netmask = 0;
+ in_addr_t netmask, broadcast, network, ipaddr;
+ struct in_addr a;
+ char *ipstr;
+
+#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS
+ applet_long_options = ipcalc_longopts;
+#endif
+ opt = getopt32(argv, "mbn" USE_FEATURE_IPCALC_FANCY("phs"));
+ argc -= optind;
+ argv += optind;
+ if (opt & (BROADCAST | NETWORK | NETPREFIX)) {
+ if (argc > 2 || argc <= 0)
+ bb_show_usage();
+ } else {
+ if (argc != 1)
+ bb_show_usage();
+ }
+ if (opt & SILENT)
+ logmode = LOGMODE_NONE; /* Suppress error_msg() output */
+
+ ipstr = argv[0];
+ if (ENABLE_FEATURE_IPCALC_FANCY) {
+ unsigned long netprefix = 0;
+ char *prefixstr;
+
+ prefixstr = ipstr;
+
+ while (*prefixstr) {
+ if (*prefixstr == '/') {
+ *prefixstr = (char)0;
+ prefixstr++;
+ if (*prefixstr) {
+ unsigned msk;
+ netprefix = xatoul_range(prefixstr, 0, 32);
+ netmask = 0;
+ msk = 0x80000000;
+ while (netprefix > 0) {
+ netmask |= msk;
+ msk >>= 1;
+ netprefix--;
+ }
+ netmask = htonl(netmask);
+ /* Even if it was 0, we will signify that we have a netmask. This allows */
+ /* for specification of default routes, etc which have a 0 netmask/prefix */
+ have_netmask = 1;
+ }
+ break;
+ }
+ prefixstr++;
+ }
+ }
+ ipaddr = inet_aton(ipstr, &a);
+
+ if (ipaddr == 0) {
+ bb_error_msg_and_die("bad IP address: %s", argv[0]);
+ }
+ ipaddr = a.s_addr;
+
+ if (argc == 2) {
+ if (ENABLE_FEATURE_IPCALC_FANCY && have_netmask) {
+ bb_error_msg_and_die("use prefix or netmask, not both");
+ }
+
+ netmask = inet_aton(argv[1], &a);
+ if (netmask == 0) {
+ bb_error_msg_and_die("bad netmask: %s", argv[1]);
+ }
+ netmask = a.s_addr;
+ } else {
+
+ /* JHC - If the netmask wasn't provided then calculate it */
+ if (!ENABLE_FEATURE_IPCALC_FANCY || !have_netmask)
+ netmask = get_netmask(ipaddr);
+ }
+
+ if (opt & NETMASK) {
+ printf("NETMASK=%s\n", inet_ntoa((*(struct in_addr *) &netmask)));
+ }
+
+ if (opt & BROADCAST) {
+ broadcast = (ipaddr & netmask) | ~netmask;
+ printf("BROADCAST=%s\n", inet_ntoa((*(struct in_addr *) &broadcast)));
+ }
+
+ if (opt & NETWORK) {
+ network = ipaddr & netmask;
+ printf("NETWORK=%s\n", inet_ntoa((*(struct in_addr *) &network)));
+ }
+
+ if (ENABLE_FEATURE_IPCALC_FANCY) {
+ if (opt & NETPREFIX) {
+ printf("PREFIX=%i\n", get_prefix(netmask));
+ }
+
+ if (opt & HOSTNAME) {
+ struct hostent *hostinfo;
+
+ hostinfo = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET);
+ if (!hostinfo) {
+ bb_herror_msg_and_die("cannot find hostname for %s", argv[0]);
+ }
+ str_tolower(hostinfo->h_name);
+
+ printf("HOSTNAME=%s\n", hostinfo->h_name);
+ }
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/networking/isrv.c b/networking/isrv.c
new file mode 100644
index 0000000..66bb371
--- /dev/null
+++ b/networking/isrv.c
@@ -0,0 +1,338 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Generic non-forking server infrastructure.
+ * Intended to make writing telnetd-type servers easier.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "isrv.h"
+
+#define DEBUG 0
+
+#if DEBUG
+#define DPRINTF(args...) bb_error_msg(args)
+#else
+#define DPRINTF(args...) ((void)0)
+#endif
+
+/* Helpers */
+
+/* Opaque structure */
+
+struct isrv_state_t {
+ short *fd2peer; /* one per registered fd */
+ void **param_tbl; /* one per registered peer */
+ /* one per registered peer; doesn't exist if !timeout */
+ time_t *timeo_tbl;
+ int (*new_peer)(isrv_state_t *state, int fd);
+ time_t curtime;
+ int timeout;
+ int fd_count;
+ int peer_count;
+ int wr_count;
+ fd_set rd;
+ fd_set wr;
+};
+#define FD2PEER (state->fd2peer)
+#define PARAM_TBL (state->param_tbl)
+#define TIMEO_TBL (state->timeo_tbl)
+#define CURTIME (state->curtime)
+#define TIMEOUT (state->timeout)
+#define FD_COUNT (state->fd_count)
+#define PEER_COUNT (state->peer_count)
+#define WR_COUNT (state->wr_count)
+
+/* callback */
+void isrv_want_rd(isrv_state_t *state, int fd)
+{
+ FD_SET(fd, &state->rd);
+}
+
+/* callback */
+void isrv_want_wr(isrv_state_t *state, int fd)
+{
+ if (!FD_ISSET(fd, &state->wr)) {
+ WR_COUNT++;
+ FD_SET(fd, &state->wr);
+ }
+}
+
+/* callback */
+void isrv_dont_want_rd(isrv_state_t *state, int fd)
+{
+ FD_CLR(fd, &state->rd);
+}
+
+/* callback */
+void isrv_dont_want_wr(isrv_state_t *state, int fd)
+{
+ if (FD_ISSET(fd, &state->wr)) {
+ WR_COUNT--;
+ FD_CLR(fd, &state->wr);
+ }
+}
+
+/* callback */
+int isrv_register_fd(isrv_state_t *state, int peer, int fd)
+{
+ int n;
+
+ DPRINTF("register_fd(peer:%d,fd:%d)", peer, fd);
+
+ if (FD_COUNT >= FD_SETSIZE) return -1;
+ if (FD_COUNT <= fd) {
+ n = FD_COUNT;
+ FD_COUNT = fd + 1;
+
+ DPRINTF("register_fd: FD_COUNT %d", FD_COUNT);
+
+ FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
+ while (n < fd) FD2PEER[n++] = -1;
+ }
+
+ DPRINTF("register_fd: FD2PEER[%d] = %d", fd, peer);
+
+ FD2PEER[fd] = peer;
+ return 0;
+}
+
+/* callback */
+void isrv_close_fd(isrv_state_t *state, int fd)
+{
+ DPRINTF("close_fd(%d)", fd);
+
+ close(fd);
+ isrv_dont_want_rd(state, fd);
+ if (WR_COUNT) isrv_dont_want_wr(state, fd);
+
+ FD2PEER[fd] = -1;
+ if (fd == FD_COUNT-1) {
+ do fd--; while (fd >= 0 && FD2PEER[fd] == -1);
+ FD_COUNT = fd + 1;
+
+ DPRINTF("close_fd: FD_COUNT %d", FD_COUNT);
+
+ FD2PEER = xrealloc(FD2PEER, FD_COUNT * sizeof(FD2PEER[0]));
+ }
+}
+
+/* callback */
+int isrv_register_peer(isrv_state_t *state, void *param)
+{
+ int n;
+
+ if (PEER_COUNT >= FD_SETSIZE) return -1;
+ n = PEER_COUNT++;
+
+ DPRINTF("register_peer: PEER_COUNT %d", PEER_COUNT);
+
+ PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
+ PARAM_TBL[n] = param;
+ if (TIMEOUT) {
+ TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
+ TIMEO_TBL[n] = CURTIME;
+ }
+ return n;
+}
+
+static void remove_peer(isrv_state_t *state, int peer)
+{
+ int movesize;
+ int fd;
+
+ DPRINTF("remove_peer(%d)", peer);
+
+ fd = FD_COUNT - 1;
+ while (fd >= 0) {
+ if (FD2PEER[fd] == peer) {
+ isrv_close_fd(state, fd);
+ fd--;
+ continue;
+ }
+ if (FD2PEER[fd] > peer)
+ FD2PEER[fd]--;
+ fd--;
+ }
+
+ PEER_COUNT--;
+ DPRINTF("remove_peer: PEER_COUNT %d", PEER_COUNT);
+
+ movesize = (PEER_COUNT - peer) * sizeof(void*);
+ if (movesize > 0) {
+ memcpy(&PARAM_TBL[peer], &PARAM_TBL[peer+1], movesize);
+ if (TIMEOUT)
+ memcpy(&TIMEO_TBL[peer], &TIMEO_TBL[peer+1], movesize);
+ }
+ PARAM_TBL = xrealloc(PARAM_TBL, PEER_COUNT * sizeof(PARAM_TBL[0]));
+ if (TIMEOUT)
+ TIMEO_TBL = xrealloc(TIMEO_TBL, PEER_COUNT * sizeof(TIMEO_TBL[0]));
+}
+
+static void handle_accept(isrv_state_t *state, int fd)
+{
+ int n, newfd;
+
+ /* suppress gcc warning "cast from ptr to int of different size" */
+ fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]) | O_NONBLOCK);
+ newfd = accept(fd, NULL, 0);
+ fcntl(fd, F_SETFL, (int)(ptrdiff_t)(PARAM_TBL[0]));
+ if (newfd < 0) {
+ if (errno == EAGAIN) return;
+ /* Most probably someone gave us wrong fd type
+ * (for example, non-socket). Don't want
+ * to loop forever. */
+ bb_perror_msg_and_die("accept");
+ }
+
+ DPRINTF("new_peer(%d)", newfd);
+ n = state->new_peer(state, newfd);
+ if (n)
+ remove_peer(state, n); /* unsuccesful peer start */
+}
+
+void BUG_sizeof_fd_set_is_strange(void);
+static void handle_fd_set(isrv_state_t *state, fd_set *fds, int (*h)(int, void **))
+{
+ enum { LONG_CNT = sizeof(fd_set) / sizeof(long) };
+ int fds_pos;
+ int fd, peer;
+ /* need to know value at _the beginning_ of this routine */
+ int fd_cnt = FD_COUNT;
+
+ if (LONG_CNT * sizeof(long) != sizeof(fd_set))
+ BUG_sizeof_fd_set_is_strange();
+
+ fds_pos = 0;
+ while (1) {
+ /* Find next nonzero bit */
+ while (fds_pos < LONG_CNT) {
+ if (((long*)fds)[fds_pos] == 0) {
+ fds_pos++;
+ continue;
+ }
+ /* Found non-zero word */
+ fd = fds_pos * sizeof(long)*8; /* word# -> bit# */
+ while (1) {
+ if (FD_ISSET(fd, fds)) {
+ FD_CLR(fd, fds);
+ goto found_fd;
+ }
+ fd++;
+ }
+ }
+ break; /* all words are zero */
+ found_fd:
+ if (fd >= fd_cnt) { /* paranoia */
+ DPRINTF("handle_fd_set: fd > fd_cnt?? (%d > %d)",
+ fd, fd_cnt);
+ break;
+ }
+ DPRINTF("handle_fd_set: fd %d is active", fd);
+ peer = FD2PEER[fd];
+ if (peer < 0)
+ continue; /* peer is already gone */
+ if (peer == 0) {
+ handle_accept(state, fd);
+ continue;
+ }
+ DPRINTF("h(fd:%d)", fd);
+ if (h(fd, &PARAM_TBL[peer])) {
+ /* this peer is gone */
+ remove_peer(state, peer);
+ } else if (TIMEOUT) {
+ TIMEO_TBL[peer] = monotonic_sec();
+ }
+ }
+}
+
+static void handle_timeout(isrv_state_t *state, int (*do_timeout)(void **))
+{
+ int n, peer;
+ peer = PEER_COUNT-1;
+ /* peer 0 is not checked */
+ while (peer > 0) {
+ DPRINTF("peer %d: time diff %d", peer,
+ (int)(CURTIME - TIMEO_TBL[peer]));
+ if ((CURTIME - TIMEO_TBL[peer]) >= TIMEOUT) {
+ DPRINTF("peer %d: do_timeout()", peer);
+ n = do_timeout(&PARAM_TBL[peer]);
+ if (n)
+ remove_peer(state, peer);
+ }
+ peer--;
+ }
+}
+
+/* Driver */
+void isrv_run(
+ int listen_fd,
+ int (*new_peer)(isrv_state_t *state, int fd),
+ int (*do_rd)(int fd, void **),
+ int (*do_wr)(int fd, void **),
+ int (*do_timeout)(void **),
+ int timeout,
+ int linger_timeout)
+{
+ isrv_state_t *state = xzalloc(sizeof(*state));
+ state->new_peer = new_peer;
+ state->timeout = timeout;
+
+ /* register "peer" #0 - it will accept new connections */
+ isrv_register_peer(state, NULL);
+ isrv_register_fd(state, /*peer:*/ 0, listen_fd);
+ isrv_want_rd(state, listen_fd);
+ /* remember flags to make blocking<->nonblocking switch faster */
+ /* (suppress gcc warning "cast from ptr to int of different size") */
+ PARAM_TBL[0] = (void*)(ptrdiff_t)(fcntl(listen_fd, F_GETFL));
+
+ while (1) {
+ struct timeval tv;
+ fd_set rd;
+ fd_set wr;
+ fd_set *wrp = NULL;
+ int n;
+
+ tv.tv_sec = timeout;
+ if (PEER_COUNT <= 1)
+ tv.tv_sec = linger_timeout;
+ tv.tv_usec = 0;
+ rd = state->rd;
+ if (WR_COUNT) {
+ wr = state->wr;
+ wrp = &wr;
+ }
+
+ DPRINTF("run: select(FD_COUNT:%d,timeout:%d)...",
+ FD_COUNT, (int)tv.tv_sec);
+ n = select(FD_COUNT, &rd, wrp, NULL, tv.tv_sec ? &tv : NULL);
+ DPRINTF("run: ...select:%d", n);
+
+ if (n < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("select");
+ continue;
+ }
+
+ if (n == 0 && linger_timeout && PEER_COUNT <= 1)
+ break;
+
+ if (timeout) {
+ time_t t = monotonic_sec();
+ if (t != CURTIME) {
+ CURTIME = t;
+ handle_timeout(state, do_timeout);
+ }
+ }
+ if (n > 0) {
+ handle_fd_set(state, &rd, do_rd);
+ if (wrp)
+ handle_fd_set(state, wrp, do_wr);
+ }
+ }
+ DPRINTF("run: bailout");
+ /* NB: accept socket is not closed. Caller is to decide what to do */
+}
diff --git a/networking/isrv.h b/networking/isrv.h
new file mode 100644
index 0000000..c0158a3
--- /dev/null
+++ b/networking/isrv.h
@@ -0,0 +1,41 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Generic non-forking server infrastructure.
+ * Intended to make writing telnetd-type servers easier.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+/* opaque structure */
+struct isrv_state_t;
+typedef struct isrv_state_t isrv_state_t;
+
+/* callbacks */
+void isrv_want_rd(isrv_state_t *state, int fd);
+void isrv_want_wr(isrv_state_t *state, int fd);
+void isrv_dont_want_rd(isrv_state_t *state, int fd);
+void isrv_dont_want_wr(isrv_state_t *state, int fd);
+int isrv_register_fd(isrv_state_t *state, int peer, int fd);
+void isrv_close_fd(isrv_state_t *state, int fd);
+int isrv_register_peer(isrv_state_t *state, void *param);
+
+/* driver */
+void isrv_run(
+ int listen_fd,
+ int (*new_peer)(isrv_state_t *state, int fd),
+ int (*do_rd)(int fd, void **),
+ int (*do_wr)(int fd, void **),
+ int (*do_timeout)(void **),
+ int timeout,
+ int linger_timeout
+);
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
diff --git a/networking/isrv_identd.c b/networking/isrv_identd.c
new file mode 100644
index 0000000..e08ebd4
--- /dev/null
+++ b/networking/isrv_identd.c
@@ -0,0 +1,147 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Fake identd server.
+ *
+ * Copyright (C) 2007 Denys Vlasenko
+ *
+ * Licensed under GPL version 2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include "isrv.h"
+
+enum { TIMEOUT = 20 };
+
+typedef struct identd_buf_t {
+ int pos;
+ int fd_flag;
+ char buf[64 - 2*sizeof(int)];
+} identd_buf_t;
+
+#define bogouser bb_common_bufsiz1
+
+static int new_peer(isrv_state_t *state, int fd)
+{
+ int peer;
+ identd_buf_t *buf = xzalloc(sizeof(*buf));
+
+ peer = isrv_register_peer(state, buf);
+ if (peer < 0)
+ return 0; /* failure */
+ if (isrv_register_fd(state, peer, fd) < 0)
+ return peer; /* failure, unregister peer */
+
+ buf->fd_flag = fcntl(fd, F_GETFL) | O_NONBLOCK;
+ isrv_want_rd(state, fd);
+ return 0;
+}
+
+static int do_rd(int fd, void **paramp)
+{
+ identd_buf_t *buf = *paramp;
+ char *cur, *p;
+ int retval = 0; /* session is ok (so far) */
+ int sz;
+
+ cur = buf->buf + buf->pos;
+
+ if (buf->fd_flag & O_NONBLOCK)
+ fcntl(fd, F_SETFL, buf->fd_flag);
+ sz = safe_read(fd, cur, sizeof(buf->buf) - buf->pos);
+
+ if (sz < 0) {
+ if (errno != EAGAIN)
+ goto term; /* terminate this session if !EAGAIN */
+ goto ok;
+ }
+
+ buf->pos += sz;
+ buf->buf[buf->pos] = '\0';
+ p = strpbrk(cur, "\r\n");
+ if (p)
+ *p = '\0';
+ if (!p && sz && buf->pos <= (int)sizeof(buf->buf))
+ goto ok;
+ /* Terminate session. If we are in server mode, then
+ * fd is still in nonblocking mode - we never block here */
+ if (fd == 0) fd++; /* inetd mode? then write to fd 1 */
+ fdprintf(fd, "%s : USERID : UNIX : %s\r\n", buf->buf, bogouser);
+ term:
+ free(buf);
+ retval = 1; /* terminate */
+ ok:
+ if (buf->fd_flag & O_NONBLOCK)
+ fcntl(fd, F_SETFL, buf->fd_flag & ~O_NONBLOCK);
+ return retval;
+}
+
+static int do_timeout(void **paramp UNUSED_PARAM)
+{
+ return 1; /* terminate session */
+}
+
+static void inetd_mode(void)
+{
+ identd_buf_t *buf = xzalloc(sizeof(*buf));
+ /* buf->pos = 0; - xzalloc did it */
+ /* We do NOT want nonblocking I/O here! */
+ /* buf->fd_flag = 0; - xzalloc did it */
+ do
+ alarm(TIMEOUT);
+ while (do_rd(0, (void*)&buf) == 0);
+}
+
+int fakeidentd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int fakeidentd_main(int argc UNUSED_PARAM, char **argv)
+{
+ enum {
+ OPT_foreground = 0x1,
+ OPT_inetd = 0x2,
+ OPT_inetdwait = 0x4,
+ OPT_fiw = 0x7,
+ OPT_bindaddr = 0x8,
+ };
+
+ const char *bind_address = NULL;
+ unsigned opt;
+ int fd;
+
+ opt = getopt32(argv, "fiwb:", &bind_address);
+ strcpy(bogouser, "nobody");
+ if (argv[optind])
+ strncpy(bogouser, argv[optind], sizeof(bogouser));
+
+ /* Daemonize if no -f and no -i and no -w */
+ if (!(opt & OPT_fiw))
+ bb_daemonize_or_rexec(0, argv);
+
+ /* Where to log in inetd modes? "Classic" inetd
+ * probably has its stderr /dev/null'ed (we need log to syslog?),
+ * but daemontools-like utilities usually expect that children
+ * log to stderr. I like daemontools more. Go their way.
+ * (Or maybe we need yet another option "log to syslog") */
+ if (!(opt & OPT_fiw) /* || (opt & OPT_syslog) */) {
+ openlog(applet_name, 0, LOG_DAEMON);
+ logmode = LOGMODE_SYSLOG;
+ }
+
+ if (opt & OPT_inetd) {
+ inetd_mode();
+ return 0;
+ }
+
+ /* Ignore closed connections when writing */
+ signal(SIGPIPE, SIG_IGN);
+
+ fd = 0;
+ if (!(opt & OPT_inetdwait)) {
+ fd = create_and_bind_stream_or_die(bind_address,
+ bb_lookup_port("identd", "tcp", 113));
+ xlisten(fd, 5);
+ }
+
+ isrv_run(fd, new_peer, do_rd, /*do_wr:*/ NULL, do_timeout,
+ TIMEOUT, (opt & OPT_inetdwait) ? TIMEOUT : 0);
+ return 0;
+}
diff --git a/networking/libiproute/Kbuild b/networking/libiproute/Kbuild
new file mode 100644
index 0000000..5f9dd32
--- /dev/null
+++ b/networking/libiproute/Kbuild
@@ -0,0 +1,64 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+#
+
+lib-y:=
+
+lib-$(CONFIG_SLATTACH) += \
+ utils.o
+
+lib-$(CONFIG_IP) += \
+ ip_parse_common_args.o \
+ libnetlink.o \
+ ll_addr.o \
+ ll_map.o \
+ ll_proto.o \
+ ll_types.o \
+ rt_names.o \
+ rtm_map.o \
+ utils.o
+
+lib-$(CONFIG_FEATURE_IP_ADDRESS) += \
+ ip_parse_common_args.o \
+ ipaddress.o \
+ libnetlink.o \
+ ll_addr.o \
+ ll_map.o \
+ ll_types.o \
+ rt_names.o \
+ utils.o
+
+lib-$(CONFIG_FEATURE_IP_LINK) += \
+ ip_parse_common_args.o \
+ ipaddress.o \
+ iplink.o \
+ libnetlink.o \
+ ll_addr.o \
+ ll_map.o \
+ ll_types.o \
+ rt_names.o \
+ utils.o
+
+lib-$(CONFIG_FEATURE_IP_ROUTE) += \
+ ip_parse_common_args.o \
+ iproute.o \
+ libnetlink.o \
+ ll_map.o \
+ rt_names.o \
+ rtm_map.o \
+ utils.o
+
+lib-$(CONFIG_FEATURE_IP_TUNNEL) += \
+ ip_parse_common_args.o \
+ iptunnel.o \
+ rt_names.o \
+ utils.o
+
+lib-$(CONFIG_FEATURE_IP_RULE) += \
+ ip_parse_common_args.o \
+ iprule.o \
+ rt_names.o \
+ utils.o
diff --git a/networking/libiproute/ip_common.h b/networking/libiproute/ip_common.h
new file mode 100644
index 0000000..305b491
--- /dev/null
+++ b/networking/libiproute/ip_common.h
@@ -0,0 +1,41 @@
+/* vi: set sw=4 ts=4: */
+#ifndef _IP_COMMON_H
+#define _IP_COMMON_H 1
+
+#include "libbb.h"
+#include <asm/types.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#if !defined IFA_RTA
+#include <linux/if_addr.h>
+#endif
+#if !defined IFLA_RTA
+#include <linux/if_link.h>
+#endif
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+extern char **ip_parse_common_args(char **argv);
+extern int print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+extern int ipaddr_list_or_flush(char **argv, int flush);
+extern int iproute_monitor(char **argv);
+extern void iplink_usage(void) NORETURN;
+extern void ipneigh_reset_filter(void);
+
+extern int do_ipaddr(char **argv);
+extern int do_iproute(char **argv);
+extern int do_iprule(char **argv);
+extern int do_ipneigh(char **argv);
+extern int do_iptunnel(char **argv);
+extern int do_iplink(char **argv);
+extern int do_ipmonitor(char **argv);
+extern int do_multiaddr(char **argv);
+extern int do_multiroute(char **argv);
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif /* ip_common.h */
diff --git a/networking/libiproute/ip_parse_common_args.c b/networking/libiproute/ip_parse_common_args.c
new file mode 100644
index 0000000..5e4012b
--- /dev/null
+++ b/networking/libiproute/ip_parse_common_args.c
@@ -0,0 +1,84 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ip.c "ip" utility frontend.
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
+ */
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "utils.h"
+
+family_t preferred_family = AF_UNSPEC;
+smallint oneline;
+char _SL_;
+
+char **ip_parse_common_args(char **argv)
+{
+ static const char ip_common_commands[] ALIGN1 =
+ "oneline" "\0"
+ "family" "\0"
+ "4" "\0"
+ "6" "\0"
+ "0" "\0"
+ ;
+ enum {
+ ARG_oneline,
+ ARG_family,
+ ARG_IPv4,
+ ARG_IPv6,
+ ARG_packet,
+ };
+ static const family_t af_numbers[] = { AF_INET, AF_INET6, AF_PACKET };
+ int arg;
+
+ while (*argv) {
+ char *opt = *argv;
+
+ if (opt[0] != '-')
+ break;
+ opt++;
+ if (opt[0] == '-') {
+ opt++;
+ if (!opt[0]) { /* "--" */
+ argv++;
+ break;
+ }
+ }
+ arg = index_in_substrings(ip_common_commands, opt);
+ if (arg < 0)
+ bb_show_usage();
+ if (arg == ARG_oneline) {
+ oneline = 1;
+ argv++;
+ continue;
+ }
+ if (arg == ARG_family) {
+ static const char families[] ALIGN1 =
+ "inet" "\0" "inet6" "\0" "link" "\0";
+ argv++;
+ if (!*argv)
+ bb_show_usage();
+ arg = index_in_strings(families, *argv);
+ if (arg < 0)
+ invarg(*argv, "protocol family");
+ /* now arg == 0, 1 or 2 */
+ } else {
+ arg -= ARG_IPv4;
+ /* now arg == 0, 1 or 2 */
+ }
+ preferred_family = af_numbers[arg];
+ argv++;
+ }
+ _SL_ = oneline ? '\\' : '\n';
+ return argv;
+}
diff --git a/networking/libiproute/ipaddress.c b/networking/libiproute/ipaddress.c
new file mode 100644
index 0000000..288dcca
--- /dev/null
+++ b/networking/libiproute/ipaddress.c
@@ -0,0 +1,784 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ipaddress.c "ip address".
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ * Laszlo Valko <valko@linux.karinthy.hu> 990223: address label must be zero terminated
+ */
+
+#include <fnmatch.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+#ifndef IFF_LOWER_UP
+/* from linux/if.h */
+#define IFF_LOWER_UP 0x10000 /* driver signals L1 up*/
+#endif
+
+typedef struct filter_t {
+ char *label;
+ char *flushb;
+ struct rtnl_handle *rth;
+ int scope, scopemask;
+ int flags, flagmask;
+ int flushp;
+ int flushe;
+ int ifindex;
+ family_t family;
+ smallint showqueue;
+ smallint oneline;
+ smallint up;
+ smallint flushed;
+ inet_prefix pfx;
+} filter_t;
+
+#define filter (*(filter_t*)&bb_common_bufsiz1)
+
+
+static void print_link_flags(unsigned flags, unsigned mdown)
+{
+ static const int flag_masks[] = {
+ IFF_LOOPBACK, IFF_BROADCAST, IFF_POINTOPOINT,
+ IFF_MULTICAST, IFF_NOARP, IFF_UP, IFF_LOWER_UP };
+ static const char flag_labels[] ALIGN1 =
+ "LOOPBACK\0""BROADCAST\0""POINTOPOINT\0"
+ "MULTICAST\0""NOARP\0""UP\0""LOWER_UP\0";
+
+ bb_putchar('<');
+ flags &= ~IFF_RUNNING;
+#if 0
+ _PF(ALLMULTI);
+ _PF(PROMISC);
+ _PF(MASTER);
+ _PF(SLAVE);
+ _PF(DEBUG);
+ _PF(DYNAMIC);
+ _PF(AUTOMEDIA);
+ _PF(PORTSEL);
+ _PF(NOTRAILERS);
+#endif
+ flags = print_flags_separated(flag_masks, flag_labels, flags, ",");
+ if (flags)
+ printf("%x", flags);
+ if (mdown)
+ printf(",M-DOWN");
+ printf("> ");
+}
+
+static void print_queuelen(char *name)
+{
+ struct ifreq ifr;
+ int s;
+
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s < 0)
+ return;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ if (ioctl_or_warn(s, SIOCGIFTXQLEN, &ifr) < 0) {
+ close(s);
+ return;
+ }
+ close(s);
+
+ if (ifr.ifr_qlen)
+ printf("qlen %d", ifr.ifr_qlen);
+}
+
+static int print_linkinfo(const struct nlmsghdr *n)
+{
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct rtattr * tb[IFLA_MAX+1];
+ int len = n->nlmsg_len;
+ unsigned m_flag = 0;
+
+ if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*ifi));
+ if (len < 0)
+ return -1;
+
+ if (filter.ifindex && ifi->ifi_index != filter.ifindex)
+ return 0;
+ if (filter.up && !(ifi->ifi_flags & IFF_UP))
+ return 0;
+
+ memset(tb, 0, sizeof(tb));
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len);
+ if (tb[IFLA_IFNAME] == NULL) {
+ bb_error_msg("nil ifname");
+ return -1;
+ }
+ if (filter.label
+ && (!filter.family || filter.family == AF_PACKET)
+ && fnmatch(filter.label, RTA_DATA(tb[IFLA_IFNAME]), 0)
+ ) {
+ return 0;
+ }
+
+ if (n->nlmsg_type == RTM_DELLINK)
+ printf("Deleted ");
+
+ printf("%d: %s", ifi->ifi_index,
+ tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : "<nil>");
+
+ if (tb[IFLA_LINK]) {
+ SPRINT_BUF(b1);
+ int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]);
+ if (iflink == 0)
+ printf("@NONE: ");
+ else {
+ printf("@%s: ", ll_idx_n2a(iflink, b1));
+ m_flag = ll_index_to_flags(iflink);
+ m_flag = !(m_flag & IFF_UP);
+ }
+ } else {
+ printf(": ");
+ }
+ print_link_flags(ifi->ifi_flags, m_flag);
+
+ if (tb[IFLA_MTU])
+ printf("mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU]));
+ if (tb[IFLA_QDISC])
+ printf("qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC]));
+#ifdef IFLA_MASTER
+ if (tb[IFLA_MASTER]) {
+ SPRINT_BUF(b1);
+ printf("master %s ", ll_idx_n2a(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1));
+ }
+#endif
+ if (filter.showqueue)
+ print_queuelen((char*)RTA_DATA(tb[IFLA_IFNAME]));
+
+ if (!filter.family || filter.family == AF_PACKET) {
+ SPRINT_BUF(b1);
+ printf("%c link/%s ", _SL_, ll_type_n2a(ifi->ifi_type, b1, sizeof(b1)));
+
+ if (tb[IFLA_ADDRESS]) {
+ fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]),
+ RTA_PAYLOAD(tb[IFLA_ADDRESS]),
+ ifi->ifi_type,
+ b1, sizeof(b1)), stdout);
+ }
+ if (tb[IFLA_BROADCAST]) {
+ if (ifi->ifi_flags & IFF_POINTOPOINT)
+ printf(" peer ");
+ else
+ printf(" brd ");
+ fputs(ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]),
+ RTA_PAYLOAD(tb[IFLA_BROADCAST]),
+ ifi->ifi_type,
+ b1, sizeof(b1)), stdout);
+ }
+ }
+ bb_putchar('\n');
+ /*fflush(stdout);*/
+ return 0;
+}
+
+static int flush_update(void)
+{
+ if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) {
+ bb_perror_msg("failed to send flush request");
+ return -1;
+ }
+ filter.flushp = 0;
+ return 0;
+}
+
+static int print_addrinfo(const struct sockaddr_nl *who UNUSED_PARAM,
+ struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+ struct ifaddrmsg *ifa = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr * rta_tb[IFA_MAX+1];
+ char abuf[256];
+ SPRINT_BUF(b1);
+
+ if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR)
+ return 0;
+ len -= NLMSG_LENGTH(sizeof(*ifa));
+ if (len < 0) {
+ bb_error_msg("wrong nlmsg len %d", len);
+ return -1;
+ }
+
+ if (filter.flushb && n->nlmsg_type != RTM_NEWADDR)
+ return 0;
+
+ memset(rta_tb, 0, sizeof(rta_tb));
+ parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
+
+ if (!rta_tb[IFA_LOCAL])
+ rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
+ if (!rta_tb[IFA_ADDRESS])
+ rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
+
+ if (filter.ifindex && filter.ifindex != ifa->ifa_index)
+ return 0;
+ if ((filter.scope ^ ifa->ifa_scope) & filter.scopemask)
+ return 0;
+ if ((filter.flags ^ ifa->ifa_flags) & filter.flagmask)
+ return 0;
+ if (filter.label) {
+ const char *label;
+ if (rta_tb[IFA_LABEL])
+ label = RTA_DATA(rta_tb[IFA_LABEL]);
+ else
+ label = ll_idx_n2a(ifa->ifa_index, b1);
+ if (fnmatch(filter.label, label, 0) != 0)
+ return 0;
+ }
+ if (filter.pfx.family) {
+ if (rta_tb[IFA_LOCAL]) {
+ inet_prefix dst;
+ memset(&dst, 0, sizeof(dst));
+ dst.family = ifa->ifa_family;
+ memcpy(&dst.data, RTA_DATA(rta_tb[IFA_LOCAL]), RTA_PAYLOAD(rta_tb[IFA_LOCAL]));
+ if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen))
+ return 0;
+ }
+ }
+
+ if (filter.flushb) {
+ struct nlmsghdr *fn;
+ if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+ if (flush_update())
+ return -1;
+ }
+ fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+ memcpy(fn, n, n->nlmsg_len);
+ fn->nlmsg_type = RTM_DELADDR;
+ fn->nlmsg_flags = NLM_F_REQUEST;
+ fn->nlmsg_seq = ++filter.rth->seq;
+ filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb;
+ filter.flushed = 1;
+ return 0;
+ }
+
+ if (n->nlmsg_type == RTM_DELADDR)
+ printf("Deleted ");
+
+ if (filter.oneline)
+ printf("%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index));
+ if (ifa->ifa_family == AF_INET)
+ printf(" inet ");
+ else if (ifa->ifa_family == AF_INET6)
+ printf(" inet6 ");
+ else
+ printf(" family %d ", ifa->ifa_family);
+
+ if (rta_tb[IFA_LOCAL]) {
+ fputs(rt_addr_n2a(ifa->ifa_family,
+ RTA_PAYLOAD(rta_tb[IFA_LOCAL]),
+ RTA_DATA(rta_tb[IFA_LOCAL]),
+ abuf, sizeof(abuf)), stdout);
+
+ if (rta_tb[IFA_ADDRESS] == NULL ||
+ memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0) {
+ printf("/%d ", ifa->ifa_prefixlen);
+ } else {
+ printf(" peer %s/%d ",
+ rt_addr_n2a(ifa->ifa_family,
+ RTA_PAYLOAD(rta_tb[IFA_ADDRESS]),
+ RTA_DATA(rta_tb[IFA_ADDRESS]),
+ abuf, sizeof(abuf)),
+ ifa->ifa_prefixlen);
+ }
+ }
+
+ if (rta_tb[IFA_BROADCAST]) {
+ printf("brd %s ",
+ rt_addr_n2a(ifa->ifa_family,
+ RTA_PAYLOAD(rta_tb[IFA_BROADCAST]),
+ RTA_DATA(rta_tb[IFA_BROADCAST]),
+ abuf, sizeof(abuf)));
+ }
+ if (rta_tb[IFA_ANYCAST]) {
+ printf("any %s ",
+ rt_addr_n2a(ifa->ifa_family,
+ RTA_PAYLOAD(rta_tb[IFA_ANYCAST]),
+ RTA_DATA(rta_tb[IFA_ANYCAST]),
+ abuf, sizeof(abuf)));
+ }
+ printf("scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1)));
+ if (ifa->ifa_flags & IFA_F_SECONDARY) {
+ ifa->ifa_flags &= ~IFA_F_SECONDARY;
+ printf("secondary ");
+ }
+ if (ifa->ifa_flags & IFA_F_TENTATIVE) {
+ ifa->ifa_flags &= ~IFA_F_TENTATIVE;
+ printf("tentative ");
+ }
+ if (ifa->ifa_flags & IFA_F_DEPRECATED) {
+ ifa->ifa_flags &= ~IFA_F_DEPRECATED;
+ printf("deprecated ");
+ }
+ if (!(ifa->ifa_flags & IFA_F_PERMANENT)) {
+ printf("dynamic ");
+ } else
+ ifa->ifa_flags &= ~IFA_F_PERMANENT;
+ if (ifa->ifa_flags)
+ printf("flags %02x ", ifa->ifa_flags);
+ if (rta_tb[IFA_LABEL])
+ fputs((char*)RTA_DATA(rta_tb[IFA_LABEL]), stdout);
+ if (rta_tb[IFA_CACHEINFO]) {
+ struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]);
+ char buf[128];
+ bb_putchar(_SL_);
+ if (ci->ifa_valid == 0xFFFFFFFFU)
+ sprintf(buf, "valid_lft forever");
+ else
+ sprintf(buf, "valid_lft %dsec", ci->ifa_valid);
+ if (ci->ifa_prefered == 0xFFFFFFFFU)
+ sprintf(buf+strlen(buf), " preferred_lft forever");
+ else
+ sprintf(buf+strlen(buf), " preferred_lft %dsec", ci->ifa_prefered);
+ printf(" %s", buf);
+ }
+ bb_putchar('\n');
+ /*fflush(stdout);*/
+ return 0;
+}
+
+
+struct nlmsg_list
+{
+ struct nlmsg_list *next;
+ struct nlmsghdr h;
+};
+
+static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo)
+{
+ for (; ainfo; ainfo = ainfo->next) {
+ struct nlmsghdr *n = &ainfo->h;
+ struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+ if (n->nlmsg_type != RTM_NEWADDR)
+ continue;
+
+ if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifa)))
+ return -1;
+
+ if (ifa->ifa_index != ifindex ||
+ (filter.family && filter.family != ifa->ifa_family))
+ continue;
+
+ print_addrinfo(NULL, n, NULL);
+ }
+ return 0;
+}
+
+
+static int store_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg)
+{
+ struct nlmsg_list **linfo = (struct nlmsg_list**)arg;
+ struct nlmsg_list *h;
+ struct nlmsg_list **lp;
+
+ h = malloc(n->nlmsg_len+sizeof(void*));
+ if (h == NULL)
+ return -1;
+
+ memcpy(&h->h, n, n->nlmsg_len);
+ h->next = NULL;
+
+ for (lp = linfo; *lp; lp = &(*lp)->next)
+ continue;
+ *lp = h;
+
+ ll_remember_index(who, n, NULL);
+ return 0;
+}
+
+static void ipaddr_reset_filter(int _oneline)
+{
+ memset(&filter, 0, sizeof(filter));
+ filter.oneline = _oneline;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int ipaddr_list_or_flush(char **argv, int flush)
+{
+ static const char option[] ALIGN1 = "to\0""scope\0""up\0""label\0""dev\0";
+
+ struct nlmsg_list *linfo = NULL;
+ struct nlmsg_list *ainfo = NULL;
+ struct nlmsg_list *l;
+ struct rtnl_handle rth;
+ char *filter_dev = NULL;
+ int no_link = 0;
+
+ ipaddr_reset_filter(oneline);
+ filter.showqueue = 1;
+
+ if (filter.family == AF_UNSPEC)
+ filter.family = preferred_family;
+
+ if (flush) {
+ if (!*argv) {
+ bb_error_msg_and_die(bb_msg_requires_arg, "flush");
+ }
+ if (filter.family == AF_PACKET) {
+ bb_error_msg_and_die("cannot flush link addresses");
+ }
+ }
+
+ while (*argv) {
+ const int option_num = index_in_strings(option, *argv);
+ switch (option_num) {
+ case 0: /* to */
+ NEXT_ARG();
+ get_prefix(&filter.pfx, *argv, filter.family);
+ if (filter.family == AF_UNSPEC) {
+ filter.family = filter.pfx.family;
+ }
+ break;
+ case 1: /* scope */
+ {
+ uint32_t scope = 0;
+ NEXT_ARG();
+ filter.scopemask = -1;
+ if (rtnl_rtscope_a2n(&scope, *argv)) {
+ if (strcmp(*argv, "all") != 0) {
+ invarg(*argv, "scope");
+ }
+ scope = RT_SCOPE_NOWHERE;
+ filter.scopemask = 0;
+ }
+ filter.scope = scope;
+ break;
+ }
+ case 2: /* up */
+ filter.up = 1;
+ break;
+ case 3: /* label */
+ NEXT_ARG();
+ filter.label = *argv;
+ break;
+ case 4: /* dev */
+ NEXT_ARG();
+ default:
+ if (filter_dev) {
+ duparg2("dev", *argv);
+ }
+ filter_dev = *argv;
+ }
+ argv++;
+ }
+
+ xrtnl_open(&rth);
+
+ xrtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK);
+ xrtnl_dump_filter(&rth, store_nlmsg, &linfo);
+
+ if (filter_dev) {
+ filter.ifindex = xll_name_to_index(filter_dev);
+ }
+
+ if (flush) {
+ char flushb[4096-512];
+
+ filter.flushb = flushb;
+ filter.flushp = 0;
+ filter.flushe = sizeof(flushb);
+ filter.rth = &rth;
+
+ for (;;) {
+ xrtnl_wilddump_request(&rth, filter.family, RTM_GETADDR);
+ filter.flushed = 0;
+ xrtnl_dump_filter(&rth, print_addrinfo, NULL);
+ if (filter.flushed == 0) {
+ return 0;
+ }
+ if (flush_update() < 0)
+ return 1;
+ }
+ }
+
+ if (filter.family != AF_PACKET) {
+ xrtnl_wilddump_request(&rth, filter.family, RTM_GETADDR);
+ xrtnl_dump_filter(&rth, store_nlmsg, &ainfo);
+ }
+
+
+ if (filter.family && filter.family != AF_PACKET) {
+ struct nlmsg_list **lp;
+ lp = &linfo;
+
+ if (filter.oneline)
+ no_link = 1;
+
+ while ((l = *lp) != NULL) {
+ int ok = 0;
+ struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+ struct nlmsg_list *a;
+
+ for (a = ainfo; a; a = a->next) {
+ struct nlmsghdr *n = &a->h;
+ struct ifaddrmsg *ifa = NLMSG_DATA(n);
+
+ if (ifa->ifa_index != ifi->ifi_index ||
+ (filter.family && filter.family != ifa->ifa_family))
+ continue;
+ if ((filter.scope ^ ifa->ifa_scope) & filter.scopemask)
+ continue;
+ if ((filter.flags ^ ifa->ifa_flags) & filter.flagmask)
+ continue;
+ if (filter.pfx.family || filter.label) {
+ struct rtattr *tb[IFA_MAX+1];
+ memset(tb, 0, sizeof(tb));
+ parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n));
+ if (!tb[IFA_LOCAL])
+ tb[IFA_LOCAL] = tb[IFA_ADDRESS];
+
+ if (filter.pfx.family && tb[IFA_LOCAL]) {
+ inet_prefix dst;
+ memset(&dst, 0, sizeof(dst));
+ dst.family = ifa->ifa_family;
+ memcpy(&dst.data, RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_LOCAL]));
+ if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen))
+ continue;
+ }
+ if (filter.label) {
+ SPRINT_BUF(b1);
+ const char *label;
+ if (tb[IFA_LABEL])
+ label = RTA_DATA(tb[IFA_LABEL]);
+ else
+ label = ll_idx_n2a(ifa->ifa_index, b1);
+ if (fnmatch(filter.label, label, 0) != 0)
+ continue;
+ }
+ }
+
+ ok = 1;
+ break;
+ }
+ if (!ok)
+ *lp = l->next;
+ else
+ lp = &l->next;
+ }
+ }
+
+ for (l = linfo; l; l = l->next) {
+ if (no_link || print_linkinfo(&l->h) == 0) {
+ struct ifinfomsg *ifi = NLMSG_DATA(&l->h);
+ if (filter.family != AF_PACKET)
+ print_selected_addrinfo(ifi->ifi_index, ainfo);
+ }
+ }
+
+ return 0;
+}
+
+static int default_scope(inet_prefix *lcl)
+{
+ if (lcl->family == AF_INET) {
+ if (lcl->bytelen >= 1 && *(uint8_t*)&lcl->data == 127)
+ return RT_SCOPE_HOST;
+ }
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int ipaddr_modify(int cmd, char **argv)
+{
+ static const char option[] ALIGN1 =
+ "peer\0""remote\0""broadcast\0""brd\0"
+ "anycast\0""scope\0""dev\0""label\0""local\0";
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct ifaddrmsg ifa;
+ char buf[256];
+ } req;
+ char *d = NULL;
+ char *l = NULL;
+ inet_prefix lcl;
+ inet_prefix peer;
+ int local_len = 0;
+ int peer_len = 0;
+ int brd_len = 0;
+ int any_len = 0;
+ bool scoped = 0;
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = cmd;
+ req.ifa.ifa_family = preferred_family;
+
+ while (*argv) {
+ const int option_num = index_in_strings(option, *argv);
+ switch (option_num) {
+ case 0: /* peer */
+ case 1: /* remote */
+ NEXT_ARG();
+
+ if (peer_len) {
+ duparg("peer", *argv);
+ }
+ get_prefix(&peer, *argv, req.ifa.ifa_family);
+ peer_len = peer.bytelen;
+ if (req.ifa.ifa_family == AF_UNSPEC) {
+ req.ifa.ifa_family = peer.family;
+ }
+ addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &peer.data, peer.bytelen);
+ req.ifa.ifa_prefixlen = peer.bitlen;
+ break;
+ case 2: /* broadcast */
+ case 3: /* brd */
+ {
+ inet_prefix addr;
+ NEXT_ARG();
+ if (brd_len) {
+ duparg("broadcast", *argv);
+ }
+ if (LONE_CHAR(*argv, '+')) {
+ brd_len = -1;
+ } else if (LONE_DASH(*argv)) {
+ brd_len = -2;
+ } else {
+ get_addr(&addr, *argv, req.ifa.ifa_family);
+ if (req.ifa.ifa_family == AF_UNSPEC)
+ req.ifa.ifa_family = addr.family;
+ addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &addr.data, addr.bytelen);
+ brd_len = addr.bytelen;
+ }
+ break;
+ }
+ case 4: /* anycast */
+ {
+ inet_prefix addr;
+ NEXT_ARG();
+ if (any_len) {
+ duparg("anycast", *argv);
+ }
+ get_addr(&addr, *argv, req.ifa.ifa_family);
+ if (req.ifa.ifa_family == AF_UNSPEC) {
+ req.ifa.ifa_family = addr.family;
+ }
+ addattr_l(&req.n, sizeof(req), IFA_ANYCAST, &addr.data, addr.bytelen);
+ any_len = addr.bytelen;
+ break;
+ }
+ case 5: /* scope */
+ {
+ uint32_t scope = 0;
+ NEXT_ARG();
+ if (rtnl_rtscope_a2n(&scope, *argv)) {
+ invarg(*argv, "scope");
+ }
+ req.ifa.ifa_scope = scope;
+ scoped = 1;
+ break;
+ }
+ case 6: /* dev */
+ NEXT_ARG();
+ d = *argv;
+ break;
+ case 7: /* label */
+ NEXT_ARG();
+ l = *argv;
+ addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l)+1);
+ break;
+ case 8: /* local */
+ NEXT_ARG();
+ default:
+ if (local_len) {
+ duparg2("local", *argv);
+ }
+ get_prefix(&lcl, *argv, req.ifa.ifa_family);
+ if (req.ifa.ifa_family == AF_UNSPEC) {
+ req.ifa.ifa_family = lcl.family;
+ }
+ addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen);
+ local_len = lcl.bytelen;
+ }
+ argv++;
+ }
+
+ if (d == NULL) {
+ bb_error_msg(bb_msg_requires_arg, "\"dev\"");
+ return -1;
+ }
+ if (l && strncmp(d, l, strlen(d)) != 0) {
+ bb_error_msg_and_die("\"dev\" (%s) must match \"label\" (%s)", d, l);
+ }
+
+ if (peer_len == 0 && local_len && cmd != RTM_DELADDR) {
+ peer = lcl;
+ addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &lcl.data, lcl.bytelen);
+ }
+ if (req.ifa.ifa_prefixlen == 0)
+ req.ifa.ifa_prefixlen = lcl.bitlen;
+
+ if (brd_len < 0 && cmd != RTM_DELADDR) {
+ inet_prefix brd;
+ int i;
+ if (req.ifa.ifa_family != AF_INET) {
+ bb_error_msg_and_die("broadcast can be set only for IPv4 addresses");
+ }
+ brd = peer;
+ if (brd.bitlen <= 30) {
+ for (i=31; i>=brd.bitlen; i--) {
+ if (brd_len == -1)
+ brd.data[0] |= htonl(1<<(31-i));
+ else
+ brd.data[0] &= ~htonl(1<<(31-i));
+ }
+ addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &brd.data, brd.bytelen);
+ brd_len = brd.bytelen;
+ }
+ }
+ if (!scoped && cmd != RTM_DELADDR)
+ req.ifa.ifa_scope = default_scope(&lcl);
+
+ xrtnl_open(&rth);
+
+ ll_init_map(&rth);
+
+ req.ifa.ifa_index = xll_name_to_index(d);
+
+ if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
+ return 2;
+
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_ipaddr(char **argv)
+{
+ static const char commands[] ALIGN1 =
+ "add\0""delete\0""list\0""show\0""lst\0""flush\0";
+
+ int command_num = 2; /* default command is list */
+
+ if (*argv) {
+ command_num = index_in_substrings(commands, *argv);
+ if (command_num < 0 || command_num > 5)
+ bb_error_msg_and_die("unknown command %s", *argv);
+ argv++;
+ }
+ if (command_num == 0) /* add */
+ return ipaddr_modify(RTM_NEWADDR, argv);
+ if (command_num == 1) /* delete */
+ return ipaddr_modify(RTM_DELADDR, argv);
+ if (command_num == 5) /* flush */
+ return ipaddr_list_or_flush(argv, 1);
+ /* 2 == list, 3 == show, 4 == lst */
+ return ipaddr_list_or_flush(argv, 0);
+}
diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c
new file mode 100644
index 0000000..8de17bf
--- /dev/null
+++ b/networking/libiproute/iplink.c
@@ -0,0 +1,306 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * iplink.c "ip link".
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+//#include <sys/ioctl.h>
+//#include <sys/socket.h>
+#include <net/if.h>
+#include <net/if_packet.h>
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+/* taken from linux/sockios.h */
+#define SIOCSIFNAME 0x8923 /* set interface name */
+
+/* Exits on error */
+static int get_ctl_fd(void)
+{
+ int fd;
+
+ fd = socket(PF_INET, SOCK_DGRAM, 0);
+ if (fd >= 0)
+ return fd;
+ fd = socket(PF_PACKET, SOCK_DGRAM, 0);
+ if (fd >= 0)
+ return fd;
+ return xsocket(PF_INET6, SOCK_DGRAM, 0);
+}
+
+/* Exits on error */
+static void do_chflags(char *dev, uint32_t flags, uint32_t mask)
+{
+ struct ifreq ifr;
+ int fd;
+
+ strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+ fd = get_ctl_fd();
+ xioctl(fd, SIOCGIFFLAGS, &ifr);
+ if ((ifr.ifr_flags ^ flags) & mask) {
+ ifr.ifr_flags &= ~mask;
+ ifr.ifr_flags |= mask & flags;
+ xioctl(fd, SIOCSIFFLAGS, &ifr);
+ }
+ close(fd);
+}
+
+/* Exits on error */
+static void do_changename(char *dev, char *newdev)
+{
+ struct ifreq ifr;
+ int fd;
+
+ strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+ strncpy(ifr.ifr_newname, newdev, sizeof(ifr.ifr_newname));
+ fd = get_ctl_fd();
+ xioctl(fd, SIOCSIFNAME, &ifr);
+ close(fd);
+}
+
+/* Exits on error */
+static void set_qlen(char *dev, int qlen)
+{
+ struct ifreq ifr;
+ int s;
+
+ s = get_ctl_fd();
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+ ifr.ifr_qlen = qlen;
+ xioctl(s, SIOCSIFTXQLEN, &ifr);
+ close(s);
+}
+
+/* Exits on error */
+static void set_mtu(char *dev, int mtu)
+{
+ struct ifreq ifr;
+ int s;
+
+ s = get_ctl_fd();
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+ ifr.ifr_mtu = mtu;
+ xioctl(s, SIOCSIFMTU, &ifr);
+ close(s);
+}
+
+/* Exits on error */
+static int get_address(char *dev, int *htype)
+{
+ struct ifreq ifr;
+ struct sockaddr_ll me;
+ socklen_t alen;
+ int s;
+
+ s = xsocket(PF_PACKET, SOCK_DGRAM, 0);
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+ xioctl(s, SIOCGIFINDEX, &ifr);
+
+ memset(&me, 0, sizeof(me));
+ me.sll_family = AF_PACKET;
+ me.sll_ifindex = ifr.ifr_ifindex;
+ me.sll_protocol = htons(ETH_P_LOOP);
+ xbind(s, (struct sockaddr*)&me, sizeof(me));
+
+ alen = sizeof(me);
+ if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) {
+ bb_perror_msg_and_die("getsockname");
+ }
+ close(s);
+ *htype = me.sll_hatype;
+ return me.sll_halen;
+}
+
+/* Exits on error */
+static void parse_address(char *dev, int hatype, int halen, char *lla, struct ifreq *ifr)
+{
+ int alen;
+
+ memset(ifr, 0, sizeof(*ifr));
+ strncpy(ifr->ifr_name, dev, sizeof(ifr->ifr_name));
+ ifr->ifr_hwaddr.sa_family = hatype;
+
+ alen = hatype == 1/*ARPHRD_ETHER*/ ? 14/*ETH_HLEN*/ : 19/*INFINIBAND_HLEN*/;
+ alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), alen, lla);
+ if (alen < 0)
+ exit(EXIT_FAILURE);
+ if (alen != halen) {
+ bb_error_msg_and_die("wrong address (%s) length: expected %d bytes", lla, halen);
+ }
+}
+
+/* Exits on error */
+static void set_address(struct ifreq *ifr, int brd)
+{
+ int s;
+
+ s = get_ctl_fd();
+ if (brd)
+ xioctl(s, SIOCSIFHWBROADCAST, ifr);
+ else
+ xioctl(s, SIOCSIFHWADDR, ifr);
+ close(s);
+}
+
+
+static void die_must_be_on_off(const char *msg) NORETURN;
+static void die_must_be_on_off(const char *msg)
+{
+ bb_error_msg_and_die("argument of \"%s\" must be \"on\" or \"off\"", msg);
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_set(char **argv)
+{
+ char *dev = NULL;
+ uint32_t mask = 0;
+ uint32_t flags = 0;
+ int qlen = -1;
+ int mtu = -1;
+ char *newaddr = NULL;
+ char *newbrd = NULL;
+ struct ifreq ifr0, ifr1;
+ char *newname = NULL;
+ int htype, halen;
+ static const char keywords[] ALIGN1 =
+ "up\0""down\0""name\0""mtu\0""multicast\0"
+ "arp\0""address\0""dev\0";
+ enum { ARG_up = 0, ARG_down, ARG_name, ARG_mtu, ARG_multicast,
+ ARG_arp, ARG_addr, ARG_dev };
+ static const char str_on_off[] ALIGN1 = "on\0""off\0";
+ enum { PARM_on = 0, PARM_off };
+ smalluint key;
+
+ while (*argv) {
+ /* substring search ensures that e.g. "addr" and "address"
+ * are both accepted */
+ key = index_in_substrings(keywords, *argv);
+ if (key == ARG_up) {
+ mask |= IFF_UP;
+ flags |= IFF_UP;
+ }
+ if (key == ARG_down) {
+ mask |= IFF_UP;
+ flags &= ~IFF_UP;
+ }
+ if (key == ARG_name) {
+ NEXT_ARG();
+ newname = *argv;
+ }
+ if (key == ARG_mtu) {
+ NEXT_ARG();
+ if (mtu != -1)
+ duparg("mtu", *argv);
+ if (get_integer(&mtu, *argv, 0))
+ invarg(*argv, "mtu");
+ }
+ if (key == ARG_multicast) {
+ int param;
+ NEXT_ARG();
+ mask |= IFF_MULTICAST;
+ param = index_in_strings(str_on_off, *argv);
+ if (param < 0)
+ die_must_be_on_off("multicast");
+ if (param == PARM_on)
+ flags |= IFF_MULTICAST;
+ else
+ flags &= ~IFF_MULTICAST;
+ }
+ if (key == ARG_arp) {
+ int param;
+ NEXT_ARG();
+ mask |= IFF_NOARP;
+ param = index_in_strings(str_on_off, *argv);
+ if (param < 0)
+ die_must_be_on_off("arp");
+ if (param == PARM_on)
+ flags &= ~IFF_NOARP;
+ else
+ flags |= IFF_NOARP;
+ }
+ if (key == ARG_addr) {
+ NEXT_ARG();
+ newaddr = *argv;
+ }
+ if (key >= ARG_dev) {
+ if (key == ARG_dev) {
+ NEXT_ARG();
+ }
+ if (dev)
+ duparg2("dev", *argv);
+ dev = *argv;
+ }
+ argv++;
+ }
+
+ if (!dev) {
+ bb_error_msg_and_die(bb_msg_requires_arg, "\"dev\"");
+ }
+
+ if (newaddr || newbrd) {
+ halen = get_address(dev, &htype);
+ if (newaddr) {
+ parse_address(dev, htype, halen, newaddr, &ifr0);
+ }
+ if (newbrd) {
+ parse_address(dev, htype, halen, newbrd, &ifr1);
+ }
+ }
+
+ if (newname && strcmp(dev, newname)) {
+ do_changename(dev, newname);
+ dev = newname;
+ }
+ if (qlen != -1) {
+ set_qlen(dev, qlen);
+ }
+ if (mtu != -1) {
+ set_mtu(dev, mtu);
+ }
+ if (newaddr || newbrd) {
+ if (newbrd) {
+ set_address(&ifr1, 1);
+ }
+ if (newaddr) {
+ set_address(&ifr0, 0);
+ }
+ }
+ if (mask)
+ do_chflags(dev, flags, mask);
+ return 0;
+}
+
+static int ipaddr_list_link(char **argv)
+{
+ preferred_family = AF_PACKET;
+ return ipaddr_list_or_flush(argv, 0);
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iplink(char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ "set\0""show\0""lst\0""list\0";
+ int key;
+ if (!*argv)
+ return ipaddr_list_link(argv);
+ key = index_in_substrings(keywords, *argv);
+ if (key < 0)
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ argv++;
+ if (key == 0) /* set */
+ return do_set(argv);
+ /* show, lst, list */
+ return ipaddr_list_link(argv);
+}
diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c
new file mode 100644
index 0000000..a7ec66c
--- /dev/null
+++ b/networking/libiproute/iproute.c
@@ -0,0 +1,907 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * iproute.c "ip route".
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
+ * Kunihiro Ishiguro <kunihiro@zebra.org> 001102: rtnh_ifindex was not initialized
+ */
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+#ifndef RTAX_RTTVAR
+#define RTAX_RTTVAR RTAX_HOPS
+#endif
+
+
+typedef struct filter_t {
+ int tb;
+ smallint flushed;
+ char *flushb;
+ int flushp;
+ int flushe;
+ struct rtnl_handle *rth;
+ int protocol, protocolmask;
+ int scope, scopemask;
+ int type, typemask;
+ int tos, tosmask;
+ int iif, iifmask;
+ int oif, oifmask;
+ int realm, realmmask;
+ inet_prefix rprefsrc;
+ inet_prefix rvia;
+ inet_prefix rdst;
+ inet_prefix mdst;
+ inet_prefix rsrc;
+ inet_prefix msrc;
+} filter_t;
+
+#define filter (*(filter_t*)&bb_common_bufsiz1)
+
+static int flush_update(void)
+{
+ if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) {
+ bb_perror_msg("failed to send flush request");
+ return -1;
+ }
+ filter.flushp = 0;
+ return 0;
+}
+
+static unsigned get_hz(void)
+{
+ static unsigned hz_internal;
+ FILE *fp;
+
+ if (hz_internal)
+ return hz_internal;
+
+ fp = fopen_for_read("/proc/net/psched");
+ if (fp) {
+ unsigned nom, denom;
+
+ if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2)
+ if (nom == 1000000)
+ hz_internal = denom;
+ fclose(fp);
+ }
+ if (!hz_internal)
+ hz_internal = sysconf(_SC_CLK_TCK);
+ return hz_internal;
+}
+
+static int print_route(const struct sockaddr_nl *who UNUSED_PARAM,
+ struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+ struct rtmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ struct rtattr * tb[RTA_MAX+1];
+ char abuf[256];
+ inet_prefix dst;
+ inet_prefix src;
+ int host_len = -1;
+ SPRINT_BUF(b1);
+
+ if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) {
+ fprintf(stderr, "Not a route: %08x %08x %08x\n",
+ n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+ return 0;
+ }
+ if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE)
+ return 0;
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0)
+ bb_error_msg_and_die("wrong nlmsg len %d", len);
+
+ if (r->rtm_family == AF_INET6)
+ host_len = 128;
+ else if (r->rtm_family == AF_INET)
+ host_len = 32;
+
+ if (r->rtm_family == AF_INET6) {
+ if (filter.tb) {
+ if (filter.tb < 0) {
+ if (!(r->rtm_flags & RTM_F_CLONED)) {
+ return 0;
+ }
+ } else {
+ if (r->rtm_flags & RTM_F_CLONED) {
+ return 0;
+ }
+ if (filter.tb == RT_TABLE_LOCAL) {
+ if (r->rtm_type != RTN_LOCAL) {
+ return 0;
+ }
+ } else if (filter.tb == RT_TABLE_MAIN) {
+ if (r->rtm_type == RTN_LOCAL) {
+ return 0;
+ }
+ } else {
+ return 0;
+ }
+ }
+ }
+ } else {
+ if (filter.tb > 0 && filter.tb != r->rtm_table) {
+ return 0;
+ }
+ }
+ if (filter.rdst.family &&
+ (r->rtm_family != filter.rdst.family || filter.rdst.bitlen > r->rtm_dst_len)) {
+ return 0;
+ }
+ if (filter.mdst.family &&
+ (r->rtm_family != filter.mdst.family ||
+ (filter.mdst.bitlen >= 0 && filter.mdst.bitlen < r->rtm_dst_len))) {
+ return 0;
+ }
+ if (filter.rsrc.family &&
+ (r->rtm_family != filter.rsrc.family || filter.rsrc.bitlen > r->rtm_src_len)) {
+ return 0;
+ }
+ if (filter.msrc.family &&
+ (r->rtm_family != filter.msrc.family ||
+ (filter.msrc.bitlen >= 0 && filter.msrc.bitlen < r->rtm_src_len))) {
+ return 0;
+ }
+
+ memset(tb, 0, sizeof(tb));
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+ if (filter.rdst.family && inet_addr_match(&dst, &filter.rdst, filter.rdst.bitlen))
+ return 0;
+ if (filter.mdst.family && filter.mdst.bitlen >= 0 &&
+ inet_addr_match(&dst, &filter.mdst, r->rtm_dst_len))
+ return 0;
+
+ if (filter.rsrc.family && inet_addr_match(&src, &filter.rsrc, filter.rsrc.bitlen))
+ return 0;
+ if (filter.msrc.family && filter.msrc.bitlen >= 0 &&
+ inet_addr_match(&src, &filter.msrc, r->rtm_src_len))
+ return 0;
+
+ if (filter.flushb &&
+ r->rtm_family == AF_INET6 &&
+ r->rtm_dst_len == 0 &&
+ r->rtm_type == RTN_UNREACHABLE &&
+ tb[RTA_PRIORITY] &&
+ *(int*)RTA_DATA(tb[RTA_PRIORITY]) == -1)
+ return 0;
+
+ if (filter.flushb) {
+ struct nlmsghdr *fn;
+ if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) {
+ if (flush_update())
+ bb_error_msg_and_die("flush");
+ }
+ fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp));
+ memcpy(fn, n, n->nlmsg_len);
+ fn->nlmsg_type = RTM_DELROUTE;
+ fn->nlmsg_flags = NLM_F_REQUEST;
+ fn->nlmsg_seq = ++filter.rth->seq;
+ filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb;
+ filter.flushed = 1;
+ return 0;
+ }
+
+ if (n->nlmsg_type == RTM_DELROUTE) {
+ printf("Deleted ");
+ }
+ if (r->rtm_type != RTN_UNICAST && !filter.type) {
+ printf("%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)));
+ }
+
+ if (tb[RTA_DST]) {
+ if (r->rtm_dst_len != host_len) {
+ printf("%s/%u ", rt_addr_n2a(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_DST]),
+ RTA_DATA(tb[RTA_DST]),
+ abuf, sizeof(abuf)),
+ r->rtm_dst_len
+ );
+ } else {
+ printf("%s ", format_host(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_DST]),
+ RTA_DATA(tb[RTA_DST]),
+ abuf, sizeof(abuf))
+ );
+ }
+ } else if (r->rtm_dst_len) {
+ printf("0/%d ", r->rtm_dst_len);
+ } else {
+ printf("default ");
+ }
+ if (tb[RTA_SRC]) {
+ if (r->rtm_src_len != host_len) {
+ printf("from %s/%u ", rt_addr_n2a(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_SRC]),
+ RTA_DATA(tb[RTA_SRC]),
+ abuf, sizeof(abuf)),
+ r->rtm_src_len
+ );
+ } else {
+ printf("from %s ", format_host(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_SRC]),
+ RTA_DATA(tb[RTA_SRC]),
+ abuf, sizeof(abuf))
+ );
+ }
+ } else if (r->rtm_src_len) {
+ printf("from 0/%u ", r->rtm_src_len);
+ }
+ if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) {
+ printf("via %s ", format_host(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_GATEWAY]),
+ RTA_DATA(tb[RTA_GATEWAY]),
+ abuf, sizeof(abuf)));
+ }
+ if (tb[RTA_OIF] && filter.oifmask != -1) {
+ printf("dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF])));
+ }
+
+ if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) {
+ /* Do not use format_host(). It is our local addr
+ and symbolic name will not be useful.
+ */
+ printf(" src %s ", rt_addr_n2a(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_PREFSRC]),
+ RTA_DATA(tb[RTA_PREFSRC]),
+ abuf, sizeof(abuf)));
+ }
+ if (tb[RTA_PRIORITY]) {
+ printf(" metric %d ", *(uint32_t*)RTA_DATA(tb[RTA_PRIORITY]));
+ }
+ if (r->rtm_family == AF_INET6) {
+ struct rta_cacheinfo *ci = NULL;
+ if (tb[RTA_CACHEINFO]) {
+ ci = RTA_DATA(tb[RTA_CACHEINFO]);
+ }
+ if ((r->rtm_flags & RTM_F_CLONED) || (ci && ci->rta_expires)) {
+ if (r->rtm_flags & RTM_F_CLONED) {
+ printf("%c cache ", _SL_);
+ }
+ if (ci->rta_expires) {
+ printf(" expires %dsec", ci->rta_expires / get_hz());
+ }
+ if (ci->rta_error != 0) {
+ printf(" error %d", ci->rta_error);
+ }
+ } else if (ci) {
+ if (ci->rta_error != 0)
+ printf(" error %d", ci->rta_error);
+ }
+ }
+ if (tb[RTA_IIF] && filter.iifmask != -1) {
+ printf(" iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF])));
+ }
+ bb_putchar('\n');
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iproute_modify(int cmd, unsigned flags, char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ "src\0""via\0""mtu\0""lock\0""protocol\0"USE_FEATURE_IP_RULE("table\0")
+ "dev\0""oif\0""to\0""metric\0";
+ enum {
+ ARG_src,
+ ARG_via,
+ ARG_mtu, PARM_lock,
+ ARG_protocol,
+USE_FEATURE_IP_RULE(ARG_table,)
+ ARG_dev,
+ ARG_oif,
+ ARG_to,
+ ARG_metric,
+ };
+ enum {
+ gw_ok = 1 << 0,
+ dst_ok = 1 << 1,
+ proto_ok = 1 << 2,
+ type_ok = 1 << 3
+ };
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+ char mxbuf[256];
+ struct rtattr * mxrta = (void*)mxbuf;
+ unsigned mxlock = 0;
+ char *d = NULL;
+ smalluint ok = 0;
+ int arg;
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST | flags;
+ req.n.nlmsg_type = cmd;
+ req.r.rtm_family = preferred_family;
+ if (RT_TABLE_MAIN) /* if it is zero, memset already did it */
+ req.r.rtm_table = RT_TABLE_MAIN;
+ if (RT_SCOPE_NOWHERE)
+ req.r.rtm_scope = RT_SCOPE_NOWHERE;
+
+ if (cmd != RTM_DELROUTE) {
+ req.r.rtm_protocol = RTPROT_BOOT;
+ req.r.rtm_scope = RT_SCOPE_UNIVERSE;
+ req.r.rtm_type = RTN_UNICAST;
+ }
+
+ mxrta->rta_type = RTA_METRICS;
+ mxrta->rta_len = RTA_LENGTH(0);
+
+ while (*argv) {
+ arg = index_in_substrings(keywords, *argv);
+ if (arg == ARG_src) {
+ inet_prefix addr;
+ NEXT_ARG();
+ get_addr(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = addr.family;
+ addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &addr.data, addr.bytelen);
+ } else if (arg == ARG_via) {
+ inet_prefix addr;
+ ok |= gw_ok;
+ NEXT_ARG();
+ get_addr(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC) {
+ req.r.rtm_family = addr.family;
+ }
+ addattr_l(&req.n, sizeof(req), RTA_GATEWAY, &addr.data, addr.bytelen);
+ } else if (arg == ARG_mtu) {
+ unsigned mtu;
+ NEXT_ARG();
+ if (index_in_strings(keywords, *argv) == PARM_lock) {
+ mxlock |= (1 << RTAX_MTU);
+ NEXT_ARG();
+ }
+ if (get_unsigned(&mtu, *argv, 0))
+ invarg(*argv, "mtu");
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_MTU, mtu);
+ } else if (arg == ARG_protocol) {
+ uint32_t prot;
+ NEXT_ARG();
+ if (rtnl_rtprot_a2n(&prot, *argv))
+ invarg(*argv, "protocol");
+ req.r.rtm_protocol = prot;
+ ok |= proto_ok;
+#if ENABLE_FEATURE_IP_RULE
+ } else if (arg == ARG_table) {
+ uint32_t tid;
+ NEXT_ARG();
+ if (rtnl_rttable_a2n(&tid, *argv))
+ invarg(*argv, "table");
+ req.r.rtm_table = tid;
+#endif
+ } else if (arg == ARG_dev || arg == ARG_oif) {
+ NEXT_ARG();
+ d = *argv;
+ } else if (arg == ARG_metric) {
+ uint32_t metric;
+ NEXT_ARG();
+ if (get_u32(&metric, *argv, 0))
+ invarg(*argv, "metric");
+ addattr32(&req.n, sizeof(req), RTA_PRIORITY, metric);
+ } else {
+ int type;
+ inet_prefix dst;
+
+ if (arg == ARG_to) {
+ NEXT_ARG();
+ }
+ if ((**argv < '0' || **argv > '9')
+ && rtnl_rtntype_a2n(&type, *argv) == 0) {
+ NEXT_ARG();
+ req.r.rtm_type = type;
+ ok |= type_ok;
+ }
+
+ if (ok & dst_ok) {
+ duparg2("to", *argv);
+ }
+ get_prefix(&dst, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC) {
+ req.r.rtm_family = dst.family;
+ }
+ req.r.rtm_dst_len = dst.bitlen;
+ ok |= dst_ok;
+ if (dst.bytelen) {
+ addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen);
+ }
+ }
+ argv++;
+ }
+
+ xrtnl_open(&rth);
+
+ if (d) {
+ int idx;
+
+ ll_init_map(&rth);
+
+ if (d) {
+ idx = xll_name_to_index(d);
+ addattr32(&req.n, sizeof(req), RTA_OIF, idx);
+ }
+ }
+
+ if (mxrta->rta_len > RTA_LENGTH(0)) {
+ if (mxlock) {
+ rta_addattr32(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock);
+ }
+ addattr_l(&req.n, sizeof(req), RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta));
+ }
+
+ if (req.r.rtm_type == RTN_LOCAL || req.r.rtm_type == RTN_NAT)
+ req.r.rtm_scope = RT_SCOPE_HOST;
+ else if (req.r.rtm_type == RTN_BROADCAST ||
+ req.r.rtm_type == RTN_MULTICAST ||
+ req.r.rtm_type == RTN_ANYCAST)
+ req.r.rtm_scope = RT_SCOPE_LINK;
+ else if (req.r.rtm_type == RTN_UNICAST || req.r.rtm_type == RTN_UNSPEC) {
+ if (cmd == RTM_DELROUTE)
+ req.r.rtm_scope = RT_SCOPE_NOWHERE;
+ else if (!(ok & gw_ok))
+ req.r.rtm_scope = RT_SCOPE_LINK;
+ }
+
+ if (req.r.rtm_family == AF_UNSPEC) {
+ req.r.rtm_family = AF_INET;
+ }
+
+ if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) {
+ return 2;
+ }
+
+ return 0;
+}
+
+static int rtnl_rtcache_request(struct rtnl_handle *rth, int family)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtmsg rtm;
+ } req;
+ struct sockaddr_nl nladdr;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ memset(&req, 0, sizeof(req));
+ nladdr.nl_family = AF_NETLINK;
+
+ req.nlh.nlmsg_len = sizeof(req);
+ if (RTM_GETROUTE)
+ req.nlh.nlmsg_type = RTM_GETROUTE;
+ if (NLM_F_ROOT | NLM_F_REQUEST)
+ req.nlh.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST;
+ /*req.nlh.nlmsg_pid = 0; - memset did it already */
+ req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
+ req.rtm.rtm_family = family;
+ if (RTM_F_CLONED)
+ req.rtm.rtm_flags = RTM_F_CLONED;
+
+ return xsendto(rth->fd, (void*)&req, sizeof(req), (struct sockaddr*)&nladdr, sizeof(nladdr));
+}
+
+static void iproute_flush_cache(void)
+{
+ static const char fn[] ALIGN1 = "/proc/sys/net/ipv4/route/flush";
+ int flush_fd = open_or_warn(fn, O_WRONLY);
+
+ if (flush_fd < 0) {
+ return;
+ }
+
+ if (write(flush_fd, "-1", 2) < 2) {
+ bb_perror_msg("cannot flush routing cache");
+ return;
+ }
+ close(flush_fd);
+}
+
+static void iproute_reset_filter(void)
+{
+ memset(&filter, 0, sizeof(filter));
+ filter.mdst.bitlen = -1;
+ filter.msrc.bitlen = -1;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iproute_list_or_flush(char **argv, int flush)
+{
+ int do_ipv6 = preferred_family;
+ struct rtnl_handle rth;
+ char *id = NULL;
+ char *od = NULL;
+ static const char keywords[] ALIGN1 =
+ /* "ip route list/flush" parameters: */
+ "protocol\0" "dev\0" "oif\0" "iif\0"
+ "via\0" "table\0" "cache\0"
+ "from\0" "to\0"
+ /* and possible further keywords */
+ "all\0"
+ "root\0"
+ "match\0"
+ "exact\0"
+ "main\0"
+ ;
+ enum {
+ KW_proto, KW_dev, KW_oif, KW_iif,
+ KW_via, KW_table, KW_cache,
+ KW_from, KW_to,
+ /* */
+ KW_all,
+ KW_root,
+ KW_match,
+ KW_exact,
+ KW_main,
+ };
+ int arg, parm;
+
+ iproute_reset_filter();
+ filter.tb = RT_TABLE_MAIN;
+
+ if (flush && !*argv)
+ bb_error_msg_and_die(bb_msg_requires_arg, "\"ip route flush\"");
+
+ while (*argv) {
+ arg = index_in_substrings(keywords, *argv);
+ if (arg == KW_proto) {
+ uint32_t prot = 0;
+ NEXT_ARG();
+ filter.protocolmask = -1;
+ if (rtnl_rtprot_a2n(&prot, *argv)) {
+ if (index_in_strings(keywords, *argv) != KW_all)
+ invarg(*argv, "protocol");
+ prot = 0;
+ filter.protocolmask = 0;
+ }
+ filter.protocol = prot;
+ } else if (arg == KW_dev || arg == KW_oif) {
+ NEXT_ARG();
+ od = *argv;
+ } else if (arg == KW_iif) {
+ NEXT_ARG();
+ id = *argv;
+ } else if (arg == KW_via) {
+ NEXT_ARG();
+ get_prefix(&filter.rvia, *argv, do_ipv6);
+ } else if (arg == KW_table) { /* table all/cache/main */
+ NEXT_ARG();
+ parm = index_in_substrings(keywords, *argv);
+ if (parm == KW_cache)
+ filter.tb = -1;
+ else if (parm == KW_all)
+ filter.tb = 0;
+ else if (parm != KW_main) {
+#if ENABLE_FEATURE_IP_RULE
+ uint32_t tid;
+ if (rtnl_rttable_a2n(&tid, *argv))
+ invarg(*argv, "table");
+ filter.tb = tid;
+#else
+ invarg(*argv, "table");
+#endif
+ }
+ } else if (arg == KW_cache) {
+ /* The command 'ip route flush cache' is used by OpenSWAN.
+ * Assuming it's a synonym for 'ip route flush table cache' */
+ filter.tb = -1;
+ } else if (arg == KW_from) {
+ NEXT_ARG();
+ parm = index_in_substrings(keywords, *argv);
+ if (parm == KW_root) {
+ NEXT_ARG();
+ get_prefix(&filter.rsrc, *argv, do_ipv6);
+ } else if (parm == KW_match) {
+ NEXT_ARG();
+ get_prefix(&filter.msrc, *argv, do_ipv6);
+ } else {
+ if (parm == KW_exact)
+ NEXT_ARG();
+ get_prefix(&filter.msrc, *argv, do_ipv6);
+ filter.rsrc = filter.msrc;
+ }
+ } else { /* "to" is the default parameter */
+ if (arg == KW_to) {
+ NEXT_ARG();
+ arg = index_in_substrings(keywords, *argv);
+ }
+ /* parm = arg; - would be more plausible, but we reuse 'arg' here */
+ if (arg == KW_root) {
+ NEXT_ARG();
+ get_prefix(&filter.rdst, *argv, do_ipv6);
+ } else if (arg == KW_match) {
+ NEXT_ARG();
+ get_prefix(&filter.mdst, *argv, do_ipv6);
+ } else { /* "to exact" is the default */
+ if (arg == KW_exact)
+ NEXT_ARG();
+ get_prefix(&filter.mdst, *argv, do_ipv6);
+ filter.rdst = filter.mdst;
+ }
+ }
+ argv++;
+ }
+
+ if (do_ipv6 == AF_UNSPEC && filter.tb) {
+ do_ipv6 = AF_INET;
+ }
+
+ xrtnl_open(&rth);
+ ll_init_map(&rth);
+
+ if (id || od) {
+ int idx;
+
+ if (id) {
+ idx = xll_name_to_index(id);
+ filter.iif = idx;
+ filter.iifmask = -1;
+ }
+ if (od) {
+ idx = xll_name_to_index(od);
+ filter.oif = idx;
+ filter.oifmask = -1;
+ }
+ }
+
+ if (flush) {
+ char flushb[4096-512];
+
+ if (filter.tb == -1) { /* "flush table cache" */
+ if (do_ipv6 != AF_INET6)
+ iproute_flush_cache();
+ if (do_ipv6 == AF_INET)
+ return 0;
+ }
+
+ filter.flushb = flushb;
+ filter.flushp = 0;
+ filter.flushe = sizeof(flushb);
+ filter.rth = &rth;
+
+ for (;;) {
+ xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE);
+ filter.flushed = 0;
+ xrtnl_dump_filter(&rth, print_route, NULL);
+ if (filter.flushed == 0)
+ return 0;
+ if (flush_update())
+ return 1;
+ }
+ }
+
+ if (filter.tb != -1) {
+ xrtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE);
+ } else if (rtnl_rtcache_request(&rth, do_ipv6) < 0) {
+ bb_perror_msg_and_die("cannot send dump request");
+ }
+ xrtnl_dump_filter(&rth, print_route, NULL);
+
+ return 0;
+}
+
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iproute_get(char **argv)
+{
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+ char *idev = NULL;
+ char *odev = NULL;
+ bool connected = 0;
+ bool from_ok = 0;
+ static const char options[] ALIGN1 =
+ "from\0""iif\0""oif\0""dev\0""notify\0""connected\0""to\0";
+
+ memset(&req, 0, sizeof(req));
+
+ iproute_reset_filter();
+
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ if (NLM_F_REQUEST)
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ if (RTM_GETROUTE)
+ req.n.nlmsg_type = RTM_GETROUTE;
+ req.r.rtm_family = preferred_family;
+ /*req.r.rtm_table = 0; - memset did this already */
+ /*req.r.rtm_protocol = 0;*/
+ /*req.r.rtm_scope = 0;*/
+ /*req.r.rtm_type = 0;*/
+ /*req.r.rtm_src_len = 0;*/
+ /*req.r.rtm_dst_len = 0;*/
+ /*req.r.rtm_tos = 0;*/
+
+ while (*argv) {
+ switch (index_in_strings(options, *argv)) {
+ case 0: /* from */
+ {
+ inet_prefix addr;
+ NEXT_ARG();
+ from_ok = 1;
+ get_prefix(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC) {
+ req.r.rtm_family = addr.family;
+ }
+ if (addr.bytelen) {
+ addattr_l(&req.n, sizeof(req), RTA_SRC, &addr.data, addr.bytelen);
+ }
+ req.r.rtm_src_len = addr.bitlen;
+ break;
+ }
+ case 1: /* iif */
+ NEXT_ARG();
+ idev = *argv;
+ break;
+ case 2: /* oif */
+ case 3: /* dev */
+ NEXT_ARG();
+ odev = *argv;
+ break;
+ case 4: /* notify */
+ req.r.rtm_flags |= RTM_F_NOTIFY;
+ break;
+ case 5: /* connected */
+ connected = 1;
+ break;
+ case 6: /* to */
+ NEXT_ARG();
+ default:
+ {
+ inet_prefix addr;
+ get_prefix(&addr, *argv, req.r.rtm_family);
+ if (req.r.rtm_family == AF_UNSPEC) {
+ req.r.rtm_family = addr.family;
+ }
+ if (addr.bytelen) {
+ addattr_l(&req.n, sizeof(req), RTA_DST, &addr.data, addr.bytelen);
+ }
+ req.r.rtm_dst_len = addr.bitlen;
+ }
+ argv++;
+ }
+ }
+
+ if (req.r.rtm_dst_len == 0) {
+ bb_error_msg_and_die("need at least destination address");
+ }
+
+ xrtnl_open(&rth);
+
+ ll_init_map(&rth);
+
+ if (idev || odev) {
+ int idx;
+
+ if (idev) {
+ idx = xll_name_to_index(idev);
+ addattr32(&req.n, sizeof(req), RTA_IIF, idx);
+ }
+ if (odev) {
+ idx = xll_name_to_index(odev);
+ addattr32(&req.n, sizeof(req), RTA_OIF, idx);
+ }
+ }
+
+ if (req.r.rtm_family == AF_UNSPEC) {
+ req.r.rtm_family = AF_INET;
+ }
+
+ if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) {
+ return 2;
+ }
+
+ if (connected && !from_ok) {
+ struct rtmsg *r = NLMSG_DATA(&req.n);
+ int len = req.n.nlmsg_len;
+ struct rtattr * tb[RTA_MAX+1];
+
+ print_route(NULL, &req.n, NULL);
+
+ if (req.n.nlmsg_type != RTM_NEWROUTE) {
+ bb_error_msg_and_die("not a route?");
+ }
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0) {
+ bb_error_msg_and_die("wrong len %d", len);
+ }
+
+ memset(tb, 0, sizeof(tb));
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+ if (tb[RTA_PREFSRC]) {
+ tb[RTA_PREFSRC]->rta_type = RTA_SRC;
+ r->rtm_src_len = 8*RTA_PAYLOAD(tb[RTA_PREFSRC]);
+ } else if (!tb[RTA_SRC]) {
+ bb_error_msg_and_die("failed to connect the route");
+ }
+ if (!odev && tb[RTA_OIF]) {
+ tb[RTA_OIF]->rta_type = 0;
+ }
+ if (tb[RTA_GATEWAY]) {
+ tb[RTA_GATEWAY]->rta_type = 0;
+ }
+ if (!idev && tb[RTA_IIF]) {
+ tb[RTA_IIF]->rta_type = 0;
+ }
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.n.nlmsg_type = RTM_GETROUTE;
+
+ if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) {
+ return 2;
+ }
+ }
+ print_route(NULL, &req.n, NULL);
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iproute(char **argv)
+{
+ static const char ip_route_commands[] ALIGN1 =
+ /*0-3*/ "add\0""append\0""change\0""chg\0"
+ /*4-7*/ "delete\0""get\0""list\0""show\0"
+ /*8..*/ "prepend\0""replace\0""test\0""flush\0";
+ int command_num;
+ unsigned flags = 0;
+ int cmd = RTM_NEWROUTE;
+
+ if (!*argv)
+ return iproute_list_or_flush(argv, 0);
+
+ /* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */
+ /* It probably means that it is using "first match" rule */
+ command_num = index_in_substrings(ip_route_commands, *argv);
+
+ switch (command_num) {
+ case 0: /* add */
+ flags = NLM_F_CREATE|NLM_F_EXCL;
+ break;
+ case 1: /* append */
+ flags = NLM_F_CREATE|NLM_F_APPEND;
+ break;
+ case 2: /* change */
+ case 3: /* chg */
+ flags = NLM_F_REPLACE;
+ break;
+ case 4: /* delete */
+ cmd = RTM_DELROUTE;
+ break;
+ case 5: /* get */
+ return iproute_get(argv+1);
+ case 6: /* list */
+ case 7: /* show */
+ return iproute_list_or_flush(argv+1, 0);
+ case 8: /* prepend */
+ flags = NLM_F_CREATE;
+ break;
+ case 9: /* replace */
+ flags = NLM_F_CREATE|NLM_F_REPLACE;
+ break;
+ case 10: /* test */
+ flags = NLM_F_EXCL;
+ break;
+ case 11: /* flush */
+ return iproute_list_or_flush(argv+1, 1);
+ default:
+ bb_error_msg_and_die("unknown command %s", *argv);
+ }
+
+ return iproute_modify(cmd, flags, argv+1);
+}
diff --git a/networking/libiproute/iprule.c b/networking/libiproute/iprule.c
new file mode 100644
index 0000000..ca22546
--- /dev/null
+++ b/networking/libiproute/iprule.c
@@ -0,0 +1,334 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * iprule.c "ip rule".
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
+ * initially integrated into busybox by Bernhard Reutner-Fischer
+ */
+
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+/*
+static void usage(void) __attribute__((noreturn));
+
+static void usage(void)
+{
+ fprintf(stderr, "Usage: ip rule [ list | add | del ] SELECTOR ACTION\n");
+ fprintf(stderr, "SELECTOR := [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK ]\n");
+ fprintf(stderr, " [ dev STRING ] [ pref NUMBER ]\n");
+ fprintf(stderr, "ACTION := [ table TABLE_ID ] [ nat ADDRESS ]\n");
+ fprintf(stderr, " [ prohibit | reject | unreachable ]\n");
+ fprintf(stderr, " [ realms [SRCREALM/]DSTREALM ]\n");
+ fprintf(stderr, "TABLE_ID := [ local | main | default | NUMBER ]\n");
+ exit(-1);
+}
+*/
+
+static int print_rule(const struct sockaddr_nl *who UNUSED_PARAM,
+ struct nlmsghdr *n, void *arg UNUSED_PARAM)
+{
+ struct rtmsg *r = NLMSG_DATA(n);
+ int len = n->nlmsg_len;
+ int host_len = -1;
+ struct rtattr * tb[RTA_MAX+1];
+ char abuf[256];
+ SPRINT_BUF(b1);
+
+ if (n->nlmsg_type != RTM_NEWRULE)
+ return 0;
+
+ len -= NLMSG_LENGTH(sizeof(*r));
+ if (len < 0)
+ return -1;
+
+ memset(tb, 0, sizeof(tb));
+ parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len);
+
+ if (r->rtm_family == AF_INET)
+ host_len = 32;
+ else if (r->rtm_family == AF_INET6)
+ host_len = 128;
+/* else if (r->rtm_family == AF_DECnet)
+ host_len = 16;
+ else if (r->rtm_family == AF_IPX)
+ host_len = 80;
+*/
+ if (tb[RTA_PRIORITY])
+ printf("%u:\t", *(unsigned*)RTA_DATA(tb[RTA_PRIORITY]));
+ else
+ printf("0:\t");
+
+ printf("from ");
+ if (tb[RTA_SRC]) {
+ if (r->rtm_src_len != host_len) {
+ printf("%s/%u", rt_addr_n2a(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_SRC]),
+ RTA_DATA(tb[RTA_SRC]),
+ abuf, sizeof(abuf)),
+ r->rtm_src_len
+ );
+ } else {
+ fputs(format_host(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_SRC]),
+ RTA_DATA(tb[RTA_SRC]),
+ abuf, sizeof(abuf)), stdout);
+ }
+ } else if (r->rtm_src_len) {
+ printf("0/%d", r->rtm_src_len);
+ } else {
+ printf("all");
+ }
+ bb_putchar(' ');
+
+ if (tb[RTA_DST]) {
+ if (r->rtm_dst_len != host_len) {
+ printf("to %s/%u ", rt_addr_n2a(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_DST]),
+ RTA_DATA(tb[RTA_DST]),
+ abuf, sizeof(abuf)),
+ r->rtm_dst_len
+ );
+ } else {
+ printf("to %s ", format_host(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_DST]),
+ RTA_DATA(tb[RTA_DST]),
+ abuf, sizeof(abuf)));
+ }
+ } else if (r->rtm_dst_len) {
+ printf("to 0/%d ", r->rtm_dst_len);
+ }
+
+ if (r->rtm_tos) {
+ printf("tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1)));
+ }
+ if (tb[RTA_PROTOINFO]) {
+ printf("fwmark %#x ", *(uint32_t*)RTA_DATA(tb[RTA_PROTOINFO]));
+ }
+
+ if (tb[RTA_IIF]) {
+ printf("iif %s ", (char*)RTA_DATA(tb[RTA_IIF]));
+ }
+
+ if (r->rtm_table)
+ printf("lookup %s ", rtnl_rttable_n2a(r->rtm_table, b1, sizeof(b1)));
+
+ if (tb[RTA_FLOW]) {
+ uint32_t to = *(uint32_t*)RTA_DATA(tb[RTA_FLOW]);
+ uint32_t from = to>>16;
+ to &= 0xFFFF;
+ if (from) {
+ printf("realms %s/",
+ rtnl_rtrealm_n2a(from, b1, sizeof(b1)));
+ }
+ printf("%s ",
+ rtnl_rtrealm_n2a(to, b1, sizeof(b1)));
+ }
+
+ if (r->rtm_type == RTN_NAT) {
+ if (tb[RTA_GATEWAY]) {
+ printf("map-to %s ",
+ format_host(r->rtm_family,
+ RTA_PAYLOAD(tb[RTA_GATEWAY]),
+ RTA_DATA(tb[RTA_GATEWAY]),
+ abuf, sizeof(abuf)));
+ } else
+ printf("masquerade");
+ } else if (r->rtm_type != RTN_UNICAST)
+ fputs(rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1)), stdout);
+
+ bb_putchar('\n');
+ /*fflush(stdout);*/
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iprule_list(char **argv)
+{
+ struct rtnl_handle rth;
+ int af = preferred_family;
+
+ if (af == AF_UNSPEC)
+ af = AF_INET;
+
+ if (*argv) {
+ //bb_error_msg("\"rule show\" needs no arguments");
+ bb_warn_ignoring_args(1);
+ return -1;
+ }
+
+ xrtnl_open(&rth);
+
+ xrtnl_wilddump_request(&rth, af, RTM_GETRULE);
+ xrtnl_dump_filter(&rth, print_rule, NULL);
+
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int iprule_modify(int cmd, char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ "from\0""to\0""preference\0""order\0""priority\0"
+ "tos\0""fwmark\0""realms\0""table\0""lookup\0""dev\0"
+ "iif\0""nat\0""map-to\0""type\0""help\0";
+ enum {
+ ARG_from = 1, ARG_to, ARG_preference, ARG_order, ARG_priority,
+ ARG_tos, ARG_fwmark, ARG_realms, ARG_table, ARG_lookup, ARG_dev,
+ ARG_iif, ARG_nat, ARG_map_to, ARG_type, ARG_help
+ };
+ bool table_ok = 0;
+ struct rtnl_handle rth;
+ struct {
+ struct nlmsghdr n;
+ struct rtmsg r;
+ char buf[1024];
+ } req;
+ smalluint key;
+
+ memset(&req, 0, sizeof(req));
+
+ req.n.nlmsg_type = cmd;
+ req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ req.n.nlmsg_flags = NLM_F_REQUEST;
+ req.r.rtm_family = preferred_family;
+ req.r.rtm_protocol = RTPROT_BOOT;
+ req.r.rtm_scope = RT_SCOPE_UNIVERSE;
+ req.r.rtm_table = 0;
+ req.r.rtm_type = RTN_UNSPEC;
+
+ if (cmd == RTM_NEWRULE) {
+ req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL;
+ req.r.rtm_type = RTN_UNICAST;
+ }
+
+ while (*argv) {
+ key = index_in_substrings(keywords, *argv) + 1;
+ if (key == 0) /* no match found in keywords array, bail out. */
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ if (key == ARG_from) {
+ inet_prefix dst;
+ NEXT_ARG();
+ get_prefix(&dst, *argv, req.r.rtm_family);
+ req.r.rtm_src_len = dst.bitlen;
+ addattr_l(&req.n, sizeof(req), RTA_SRC, &dst.data, dst.bytelen);
+ } else if (key == ARG_to) {
+ inet_prefix dst;
+ NEXT_ARG();
+ get_prefix(&dst, *argv, req.r.rtm_family);
+ req.r.rtm_dst_len = dst.bitlen;
+ addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen);
+ } else if (key == ARG_preference ||
+ key == ARG_order ||
+ key == ARG_priority) {
+ uint32_t pref;
+ NEXT_ARG();
+ if (get_u32(&pref, *argv, 0))
+ invarg(*argv, "preference");
+ addattr32(&req.n, sizeof(req), RTA_PRIORITY, pref);
+ } else if (key == ARG_tos) {
+ uint32_t tos;
+ NEXT_ARG();
+ if (rtnl_dsfield_a2n(&tos, *argv))
+ invarg(*argv, "TOS");
+ req.r.rtm_tos = tos;
+ } else if (key == ARG_fwmark) {
+ uint32_t fwmark;
+ NEXT_ARG();
+ if (get_u32(&fwmark, *argv, 0))
+ invarg(*argv, "fwmark");
+ addattr32(&req.n, sizeof(req), RTA_PROTOINFO, fwmark);
+ } else if (key == ARG_realms) {
+ uint32_t realm;
+ NEXT_ARG();
+ if (get_rt_realms(&realm, *argv))
+ invarg(*argv, "realms");
+ addattr32(&req.n, sizeof(req), RTA_FLOW, realm);
+ } else if (key == ARG_table ||
+ key == ARG_lookup) {
+ uint32_t tid;
+ NEXT_ARG();
+ if (rtnl_rttable_a2n(&tid, *argv))
+ invarg(*argv, "table ID");
+ req.r.rtm_table = tid;
+ table_ok = 1;
+ } else if (key == ARG_dev ||
+ key == ARG_iif) {
+ NEXT_ARG();
+ addattr_l(&req.n, sizeof(req), RTA_IIF, *argv, strlen(*argv)+1);
+ } else if (key == ARG_nat ||
+ key == ARG_map_to) {
+ NEXT_ARG();
+ addattr32(&req.n, sizeof(req), RTA_GATEWAY, get_addr32(*argv));
+ req.r.rtm_type = RTN_NAT;
+ } else {
+ int type;
+
+ if (key == ARG_type) {
+ NEXT_ARG();
+ }
+ if (key == ARG_help)
+ bb_show_usage();
+ if (rtnl_rtntype_a2n(&type, *argv))
+ invarg(*argv, "type");
+ req.r.rtm_type = type;
+ }
+ argv++;
+ }
+
+ if (req.r.rtm_family == AF_UNSPEC)
+ req.r.rtm_family = AF_INET;
+
+ if (!table_ok && cmd == RTM_NEWRULE)
+ req.r.rtm_table = RT_TABLE_MAIN;
+
+ xrtnl_open(&rth);
+
+ if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
+ return 2;
+
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iprule(char **argv)
+{
+ static const char ip_rule_commands[] ALIGN1 =
+ "add\0""delete\0""list\0""show\0";
+ int cmd = 2; /* list */
+
+ if (!*argv)
+ return iprule_list(argv);
+
+ cmd = index_in_substrings(ip_rule_commands, *argv);
+ switch (cmd) {
+ case 0: /* add */
+ cmd = RTM_NEWRULE;
+ break;
+ case 1: /* delete */
+ cmd = RTM_DELRULE;
+ break;
+ case 2: /* list */
+ case 3: /* show */
+ return iprule_list(argv+1);
+ break;
+ default:
+ bb_error_msg_and_die("unknown command %s", *argv);
+ }
+ return iprule_modify(cmd, argv+1);
+}
diff --git a/networking/libiproute/iptunnel.c b/networking/libiproute/iptunnel.c
new file mode 100644
index 0000000..14fc6bb
--- /dev/null
+++ b/networking/libiproute/iptunnel.c
@@ -0,0 +1,580 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * iptunnel.c "ip tunnel"
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
+ * Rani Assaf <rani@magic.metawire.com> 980930: do not allow key for ipip/sit
+ * Phil Karn <karn@ka9q.ampr.org> 990408: "pmtudisc" flag
+ */
+
+#include <netinet/ip.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <asm/types.h>
+
+#ifndef __constant_htons
+#define __constant_htons htons
+#endif
+
+// FYI: #define SIOCDEVPRIVATE 0x89F0
+
+/* From linux/if_tunnel.h. #including it proved troublesome
+ * (redefiniton errors due to name collisions in linux/ and net[inet]/) */
+#define SIOCGETTUNNEL (SIOCDEVPRIVATE + 0)
+#define SIOCADDTUNNEL (SIOCDEVPRIVATE + 1)
+#define SIOCDELTUNNEL (SIOCDEVPRIVATE + 2)
+#define SIOCCHGTUNNEL (SIOCDEVPRIVATE + 3)
+//#define SIOCGETPRL (SIOCDEVPRIVATE + 4)
+//#define SIOCADDPRL (SIOCDEVPRIVATE + 5)
+//#define SIOCDELPRL (SIOCDEVPRIVATE + 6)
+//#define SIOCCHGPRL (SIOCDEVPRIVATE + 7)
+#define GRE_CSUM __constant_htons(0x8000)
+//#define GRE_ROUTING __constant_htons(0x4000)
+#define GRE_KEY __constant_htons(0x2000)
+#define GRE_SEQ __constant_htons(0x1000)
+//#define GRE_STRICT __constant_htons(0x0800)
+//#define GRE_REC __constant_htons(0x0700)
+//#define GRE_FLAGS __constant_htons(0x00F8)
+//#define GRE_VERSION __constant_htons(0x0007)
+struct ip_tunnel_parm {
+ char name[IFNAMSIZ];
+ int link;
+ uint16_t i_flags;
+ uint16_t o_flags;
+ uint32_t i_key;
+ uint32_t o_key;
+ struct iphdr iph;
+};
+/* SIT-mode i_flags */
+//#define SIT_ISATAP 0x0001
+//struct ip_tunnel_prl {
+// uint32_t addr;
+// uint16_t flags;
+// uint16_t __reserved;
+// uint32_t datalen;
+// uint32_t __reserved2;
+// /* data follows */
+//};
+///* PRL flags */
+//#define PRL_DEFAULT 0x0001
+
+#include "ip_common.h" /* #include "libbb.h" is inside */
+#include "rt_names.h"
+#include "utils.h"
+
+
+/* Dies on error */
+static int do_ioctl_get_ifindex(char *dev)
+{
+ struct ifreq ifr;
+ int fd;
+
+ strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+ fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+ xioctl(fd, SIOCGIFINDEX, &ifr);
+ close(fd);
+ return ifr.ifr_ifindex;
+}
+
+static int do_ioctl_get_iftype(char *dev)
+{
+ struct ifreq ifr;
+ int fd;
+ int err;
+
+ strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name));
+ fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+ err = ioctl_or_warn(fd, SIOCGIFHWADDR, &ifr);
+ close(fd);
+ return err ? -1 : ifr.ifr_addr.sa_family;
+}
+
+static char *do_ioctl_get_ifname(int idx)
+{
+ struct ifreq ifr;
+ int fd;
+ int err;
+
+ ifr.ifr_ifindex = idx;
+ fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+ err = ioctl_or_warn(fd, SIOCGIFNAME, &ifr);
+ close(fd);
+ return err ? NULL : xstrndup(ifr.ifr_name, sizeof(ifr.ifr_name));
+}
+
+static int do_get_ioctl(const char *basedev, struct ip_tunnel_parm *p)
+{
+ struct ifreq ifr;
+ int fd;
+ int err;
+
+ strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name));
+ ifr.ifr_ifru.ifru_data = (void*)p;
+ fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+ err = ioctl_or_warn(fd, SIOCGETTUNNEL, &ifr);
+ close(fd);
+ return err;
+}
+
+/* Dies on error, otherwise returns 0 */
+static int do_add_ioctl(int cmd, const char *basedev, struct ip_tunnel_parm *p)
+{
+ struct ifreq ifr;
+ int fd;
+
+ if (cmd == SIOCCHGTUNNEL && p->name[0]) {
+ strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name));
+ } else {
+ strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name));
+ }
+ ifr.ifr_ifru.ifru_data = (void*)p;
+ fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+#if ENABLE_IOCTL_HEX2STR_ERROR
+ /* #define magic will turn ioctl# into string */
+ if (cmd == SIOCCHGTUNNEL)
+ xioctl(fd, SIOCCHGTUNNEL, &ifr);
+ else
+ xioctl(fd, SIOCADDTUNNEL, &ifr);
+#else
+ xioctl(fd, cmd, &ifr);
+#endif
+ close(fd);
+ return 0;
+}
+
+/* Dies on error, otherwise returns 0 */
+static int do_del_ioctl(const char *basedev, struct ip_tunnel_parm *p)
+{
+ struct ifreq ifr;
+ int fd;
+
+ if (p->name[0]) {
+ strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name));
+ } else {
+ strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name));
+ }
+ ifr.ifr_ifru.ifru_data = (void*)p;
+ fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+ xioctl(fd, SIOCDELTUNNEL, &ifr);
+ close(fd);
+ return 0;
+}
+
+/* Dies on error */
+static void parse_args(char **argv, int cmd, struct ip_tunnel_parm *p)
+{
+ static const char keywords[] ALIGN1 =
+ "mode\0""ipip\0""ip/ip\0""gre\0""gre/ip\0""sit\0""ipv6/ip\0"
+ "key\0""ikey\0""okey\0""seq\0""iseq\0""oseq\0"
+ "csum\0""icsum\0""ocsum\0""nopmtudisc\0""pmtudisc\0"
+ "remote\0""any\0""local\0""dev\0"
+ "ttl\0""inherit\0""tos\0""dsfield\0"
+ "name\0";
+ enum {
+ ARG_mode, ARG_ipip, ARG_ip_ip, ARG_gre, ARG_gre_ip, ARG_sit, ARG_ip6_ip,
+ ARG_key, ARG_ikey, ARG_okey, ARG_seq, ARG_iseq, ARG_oseq,
+ ARG_csum, ARG_icsum, ARG_ocsum, ARG_nopmtudisc, ARG_pmtudisc,
+ ARG_remote, ARG_any, ARG_local, ARG_dev,
+ ARG_ttl, ARG_inherit, ARG_tos, ARG_dsfield,
+ ARG_name
+ };
+ int count = 0;
+ char medium[IFNAMSIZ];
+ int key;
+
+ memset(p, 0, sizeof(*p));
+ memset(&medium, 0, sizeof(medium));
+
+ p->iph.version = 4;
+ p->iph.ihl = 5;
+#ifndef IP_DF
+#define IP_DF 0x4000 /* Flag: "Don't Fragment" */
+#endif
+ p->iph.frag_off = htons(IP_DF);
+
+ while (*argv) {
+ key = index_in_strings(keywords, *argv);
+ if (key == ARG_mode) {
+ NEXT_ARG();
+ key = index_in_strings(keywords, *argv);
+ if (key == ARG_ipip ||
+ key == ARG_ip_ip) {
+ if (p->iph.protocol && p->iph.protocol != IPPROTO_IPIP) {
+ bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one");
+ }
+ p->iph.protocol = IPPROTO_IPIP;
+ } else if (key == ARG_gre ||
+ key == ARG_gre_ip) {
+ if (p->iph.protocol && p->iph.protocol != IPPROTO_GRE) {
+ bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one");
+ }
+ p->iph.protocol = IPPROTO_GRE;
+ } else if (key == ARG_sit ||
+ key == ARG_ip6_ip) {
+ if (p->iph.protocol && p->iph.protocol != IPPROTO_IPV6) {
+ bb_error_msg_and_die("%s tunnel mode", "you managed to ask for more than one");
+ }
+ p->iph.protocol = IPPROTO_IPV6;
+ } else {
+ bb_error_msg_and_die("%s tunnel mode", "cannot guess");
+ }
+ } else if (key == ARG_key) {
+ unsigned uval;
+ NEXT_ARG();
+ p->i_flags |= GRE_KEY;
+ p->o_flags |= GRE_KEY;
+ if (strchr(*argv, '.'))
+ p->i_key = p->o_key = get_addr32(*argv);
+ else {
+ if (get_unsigned(&uval, *argv, 0) < 0) {
+ invarg(*argv, "key");
+ }
+ p->i_key = p->o_key = htonl(uval);
+ }
+ } else if (key == ARG_ikey) {
+ unsigned uval;
+ NEXT_ARG();
+ p->i_flags |= GRE_KEY;
+ if (strchr(*argv, '.'))
+ p->o_key = get_addr32(*argv);
+ else {
+ if (get_unsigned(&uval, *argv, 0) < 0) {
+ invarg(*argv, "ikey");
+ }
+ p->i_key = htonl(uval);
+ }
+ } else if (key == ARG_okey) {
+ unsigned uval;
+ NEXT_ARG();
+ p->o_flags |= GRE_KEY;
+ if (strchr(*argv, '.'))
+ p->o_key = get_addr32(*argv);
+ else {
+ if (get_unsigned(&uval, *argv, 0) < 0) {
+ invarg(*argv, "okey");
+ }
+ p->o_key = htonl(uval);
+ }
+ } else if (key == ARG_seq) {
+ p->i_flags |= GRE_SEQ;
+ p->o_flags |= GRE_SEQ;
+ } else if (key == ARG_iseq) {
+ p->i_flags |= GRE_SEQ;
+ } else if (key == ARG_oseq) {
+ p->o_flags |= GRE_SEQ;
+ } else if (key == ARG_csum) {
+ p->i_flags |= GRE_CSUM;
+ p->o_flags |= GRE_CSUM;
+ } else if (key == ARG_icsum) {
+ p->i_flags |= GRE_CSUM;
+ } else if (key == ARG_ocsum) {
+ p->o_flags |= GRE_CSUM;
+ } else if (key == ARG_nopmtudisc) {
+ p->iph.frag_off = 0;
+ } else if (key == ARG_pmtudisc) {
+ p->iph.frag_off = htons(IP_DF);
+ } else if (key == ARG_remote) {
+ NEXT_ARG();
+ key = index_in_strings(keywords, *argv);
+ if (key != ARG_any)
+ p->iph.daddr = get_addr32(*argv);
+ } else if (key == ARG_local) {
+ NEXT_ARG();
+ key = index_in_strings(keywords, *argv);
+ if (key != ARG_any)
+ p->iph.saddr = get_addr32(*argv);
+ } else if (key == ARG_dev) {
+ NEXT_ARG();
+ strncpy(medium, *argv, IFNAMSIZ-1);
+ } else if (key == ARG_ttl) {
+ unsigned uval;
+ NEXT_ARG();
+ key = index_in_strings(keywords, *argv);
+ if (key != ARG_inherit) {
+ if (get_unsigned(&uval, *argv, 0))
+ invarg(*argv, "TTL");
+ if (uval > 255)
+ invarg(*argv, "TTL must be <=255");
+ p->iph.ttl = uval;
+ }
+ } else if (key == ARG_tos ||
+ key == ARG_dsfield) {
+ uint32_t uval;
+ NEXT_ARG();
+ key = index_in_strings(keywords, *argv);
+ if (key != ARG_inherit) {
+ if (rtnl_dsfield_a2n(&uval, *argv))
+ invarg(*argv, "TOS");
+ p->iph.tos = uval;
+ } else
+ p->iph.tos = 1;
+ } else {
+ if (key == ARG_name) {
+ NEXT_ARG();
+ }
+ if (p->name[0])
+ duparg2("name", *argv);
+ strncpy(p->name, *argv, IFNAMSIZ);
+ if (cmd == SIOCCHGTUNNEL && count == 0) {
+ struct ip_tunnel_parm old_p;
+ memset(&old_p, 0, sizeof(old_p));
+ if (do_get_ioctl(*argv, &old_p))
+ exit(EXIT_FAILURE);
+ *p = old_p;
+ }
+ }
+ count++;
+ argv++;
+ }
+
+ if (p->iph.protocol == 0) {
+ if (memcmp(p->name, "gre", 3) == 0)
+ p->iph.protocol = IPPROTO_GRE;
+ else if (memcmp(p->name, "ipip", 4) == 0)
+ p->iph.protocol = IPPROTO_IPIP;
+ else if (memcmp(p->name, "sit", 3) == 0)
+ p->iph.protocol = IPPROTO_IPV6;
+ }
+
+ if (p->iph.protocol == IPPROTO_IPIP || p->iph.protocol == IPPROTO_IPV6) {
+ if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) {
+ bb_error_msg_and_die("keys are not allowed with ipip and sit");
+ }
+ }
+
+ if (medium[0]) {
+ p->link = do_ioctl_get_ifindex(medium);
+ }
+
+ if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
+ p->i_key = p->iph.daddr;
+ p->i_flags |= GRE_KEY;
+ }
+ if (p->o_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) {
+ p->o_key = p->iph.daddr;
+ p->o_flags |= GRE_KEY;
+ }
+ if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) {
+ bb_error_msg_and_die("broadcast tunnel requires a source address");
+ }
+}
+
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_add(int cmd, char **argv)
+{
+ struct ip_tunnel_parm p;
+
+ parse_args(argv, cmd, &p);
+
+ if (p.iph.ttl && p.iph.frag_off == 0) {
+ bb_error_msg_and_die("ttl != 0 and noptmudisc are incompatible");
+ }
+
+ switch (p.iph.protocol) {
+ case IPPROTO_IPIP:
+ return do_add_ioctl(cmd, "tunl0", &p);
+ case IPPROTO_GRE:
+ return do_add_ioctl(cmd, "gre0", &p);
+ case IPPROTO_IPV6:
+ return do_add_ioctl(cmd, "sit0", &p);
+ default:
+ bb_error_msg_and_die("cannot determine tunnel mode (ipip, gre or sit)");
+ }
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_del(char **argv)
+{
+ struct ip_tunnel_parm p;
+
+ parse_args(argv, SIOCDELTUNNEL, &p);
+
+ switch (p.iph.protocol) {
+ case IPPROTO_IPIP:
+ return do_del_ioctl("tunl0", &p);
+ case IPPROTO_GRE:
+ return do_del_ioctl("gre0", &p);
+ case IPPROTO_IPV6:
+ return do_del_ioctl("sit0", &p);
+ default:
+ return do_del_ioctl(p.name, &p);
+ }
+}
+
+static void print_tunnel(struct ip_tunnel_parm *p)
+{
+ char s1[256];
+ char s2[256];
+ char s3[64];
+ char s4[64];
+
+ format_host(AF_INET, 4, &p->iph.daddr, s1, sizeof(s1));
+ format_host(AF_INET, 4, &p->iph.saddr, s2, sizeof(s2));
+ inet_ntop(AF_INET, &p->i_key, s3, sizeof(s3));
+ inet_ntop(AF_INET, &p->o_key, s4, sizeof(s4));
+
+ printf("%s: %s/ip remote %s local %s ",
+ p->name,
+ p->iph.protocol == IPPROTO_IPIP ? "ip" :
+ (p->iph.protocol == IPPROTO_GRE ? "gre" :
+ (p->iph.protocol == IPPROTO_IPV6 ? "ipv6" : "unknown")),
+ p->iph.daddr ? s1 : "any", p->iph.saddr ? s2 : "any");
+ if (p->link) {
+ char *n = do_ioctl_get_ifname(p->link);
+ if (n) {
+ printf(" dev %s ", n);
+ free(n);
+ }
+ }
+ if (p->iph.ttl)
+ printf(" ttl %d ", p->iph.ttl);
+ else
+ printf(" ttl inherit ");
+ if (p->iph.tos) {
+ SPRINT_BUF(b1);
+ printf(" tos");
+ if (p->iph.tos & 1)
+ printf(" inherit");
+ if (p->iph.tos & ~1)
+ printf("%c%s ", p->iph.tos & 1 ? '/' : ' ',
+ rtnl_dsfield_n2a(p->iph.tos & ~1, b1, sizeof(b1)));
+ }
+ if (!(p->iph.frag_off & htons(IP_DF)))
+ printf(" nopmtudisc");
+
+ if ((p->i_flags & GRE_KEY) && (p->o_flags & GRE_KEY) && p->o_key == p->i_key)
+ printf(" key %s", s3);
+ else if ((p->i_flags | p->o_flags) & GRE_KEY) {
+ if (p->i_flags & GRE_KEY)
+ printf(" ikey %s ", s3);
+ if (p->o_flags & GRE_KEY)
+ printf(" okey %s ", s4);
+ }
+
+ if (p->i_flags & GRE_SEQ)
+ printf("%c Drop packets out of sequence.\n", _SL_);
+ if (p->i_flags & GRE_CSUM)
+ printf("%c Checksum in received packet is required.", _SL_);
+ if (p->o_flags & GRE_SEQ)
+ printf("%c Sequence packets on output.", _SL_);
+ if (p->o_flags & GRE_CSUM)
+ printf("%c Checksum output packets.", _SL_);
+}
+
+static void do_tunnels_list(struct ip_tunnel_parm *p)
+{
+ char name[IFNAMSIZ];
+ unsigned long rx_bytes, rx_packets, rx_errs, rx_drops,
+ rx_fifo, rx_frame,
+ tx_bytes, tx_packets, tx_errs, tx_drops,
+ tx_fifo, tx_colls, tx_carrier, rx_multi;
+ int type;
+ struct ip_tunnel_parm p1;
+ char buf[512];
+ FILE *fp = fopen_or_warn("/proc/net/dev", "r");
+
+ if (fp == NULL) {
+ return;
+ }
+ /* skip headers */
+ fgets(buf, sizeof(buf), fp);
+ fgets(buf, sizeof(buf), fp);
+
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ char *ptr;
+
+ /*buf[sizeof(buf) - 1] = 0; - fgets is safe anyway */
+ ptr = strchr(buf, ':');
+ if (ptr == NULL ||
+ (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) {
+ bb_error_msg("wrong format of /proc/net/dev");
+ return;
+ }
+ if (sscanf(ptr, "%lu%lu%lu%lu%lu%lu%lu%*d%lu%lu%lu%lu%lu%lu%lu",
+ &rx_bytes, &rx_packets, &rx_errs, &rx_drops,
+ &rx_fifo, &rx_frame, &rx_multi,
+ &tx_bytes, &tx_packets, &tx_errs, &tx_drops,
+ &tx_fifo, &tx_colls, &tx_carrier) != 14)
+ continue;
+ if (p->name[0] && strcmp(p->name, name))
+ continue;
+ type = do_ioctl_get_iftype(name);
+ if (type == -1) {
+ bb_error_msg("cannot get type of [%s]", name);
+ continue;
+ }
+ if (type != ARPHRD_TUNNEL && type != ARPHRD_IPGRE && type != ARPHRD_SIT)
+ continue;
+ memset(&p1, 0, sizeof(p1));
+ if (do_get_ioctl(name, &p1))
+ continue;
+ if ((p->link && p1.link != p->link) ||
+ (p->name[0] && strcmp(p1.name, p->name)) ||
+ (p->iph.daddr && p1.iph.daddr != p->iph.daddr) ||
+ (p->iph.saddr && p1.iph.saddr != p->iph.saddr) ||
+ (p->i_key && p1.i_key != p->i_key))
+ continue;
+ print_tunnel(&p1);
+ bb_putchar('\n');
+ }
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+static int do_show(char **argv)
+{
+ int err;
+ struct ip_tunnel_parm p;
+
+ parse_args(argv, SIOCGETTUNNEL, &p);
+
+ switch (p.iph.protocol) {
+ case IPPROTO_IPIP:
+ err = do_get_ioctl(p.name[0] ? p.name : "tunl0", &p);
+ break;
+ case IPPROTO_GRE:
+ err = do_get_ioctl(p.name[0] ? p.name : "gre0", &p);
+ break;
+ case IPPROTO_IPV6:
+ err = do_get_ioctl(p.name[0] ? p.name : "sit0", &p);
+ break;
+ default:
+ do_tunnels_list(&p);
+ return 0;
+ }
+ if (err)
+ return -1;
+
+ print_tunnel(&p);
+ bb_putchar('\n');
+ return 0;
+}
+
+/* Return value becomes exitcode. It's okay to not return at all */
+int do_iptunnel(char **argv)
+{
+ static const char keywords[] ALIGN1 =
+ "add\0""change\0""delete\0""show\0""list\0""lst\0";
+ enum { ARG_add = 0, ARG_change, ARG_del, ARG_show, ARG_list, ARG_lst };
+ int key;
+
+ if (*argv) {
+ key = index_in_substrings(keywords, *argv);
+ if (key < 0)
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ argv++;
+ if (key == ARG_add)
+ return do_add(SIOCADDTUNNEL, argv);
+ if (key == ARG_change)
+ return do_add(SIOCCHGTUNNEL, argv);
+ if (key == ARG_del)
+ return do_del(argv);
+ }
+ return do_show(argv);
+}
diff --git a/networking/libiproute/libnetlink.c b/networking/libiproute/libnetlink.c
new file mode 100644
index 0000000..01454fb
--- /dev/null
+++ b/networking/libiproute/libnetlink.c
@@ -0,0 +1,409 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * libnetlink.c RTnetlink service routines.
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include "libbb.h"
+#include "libnetlink.h"
+
+void FAST_FUNC rtnl_close(struct rtnl_handle *rth)
+{
+ close(rth->fd);
+}
+
+int FAST_FUNC xrtnl_open(struct rtnl_handle *rth/*, unsigned subscriptions*/)
+{
+ socklen_t addr_len;
+
+ memset(rth, 0, sizeof(rth));
+
+ rth->fd = xsocket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+
+ memset(&rth->local, 0, sizeof(rth->local));
+ rth->local.nl_family = AF_NETLINK;
+ /*rth->local.nl_groups = subscriptions;*/
+
+ xbind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local));
+ addr_len = sizeof(rth->local);
+ if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0)
+ bb_perror_msg_and_die("getsockname");
+ if (addr_len != sizeof(rth->local))
+ bb_error_msg_and_die("wrong address length %d", addr_len);
+ if (rth->local.nl_family != AF_NETLINK)
+ bb_error_msg_and_die("wrong address family %d", rth->local.nl_family);
+ rth->seq = time(NULL);
+ return 0;
+}
+
+int FAST_FUNC xrtnl_wilddump_request(struct rtnl_handle *rth, int family, int type)
+{
+ struct {
+ struct nlmsghdr nlh;
+ struct rtgenmsg g;
+ } req;
+ struct sockaddr_nl nladdr;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ req.nlh.nlmsg_len = sizeof(req);
+ req.nlh.nlmsg_type = type;
+ req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
+ req.nlh.nlmsg_pid = 0;
+ req.nlh.nlmsg_seq = rth->dump = ++rth->seq;
+ req.g.rtgen_family = family;
+
+ return xsendto(rth->fd, (void*)&req, sizeof(req),
+ (struct sockaddr*)&nladdr, sizeof(nladdr));
+}
+
+int FAST_FUNC rtnl_send(struct rtnl_handle *rth, char *buf, int len)
+{
+ struct sockaddr_nl nladdr;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ return xsendto(rth->fd, buf, len, (struct sockaddr*)&nladdr, sizeof(nladdr));
+}
+
+int FAST_FUNC rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len)
+{
+ struct nlmsghdr nlh;
+ struct sockaddr_nl nladdr;
+ struct iovec iov[2] = { { &nlh, sizeof(nlh) }, { req, len } };
+ struct msghdr msg = {
+ (void*)&nladdr, sizeof(nladdr),
+ iov, 2,
+ NULL, 0,
+ 0
+ };
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+
+ nlh.nlmsg_len = NLMSG_LENGTH(len);
+ nlh.nlmsg_type = type;
+ nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
+ nlh.nlmsg_pid = 0;
+ nlh.nlmsg_seq = rth->dump = ++rth->seq;
+
+ return sendmsg(rth->fd, &msg, 0);
+}
+
+static int rtnl_dump_filter(struct rtnl_handle *rth,
+ int (*filter)(const struct sockaddr_nl *, struct nlmsghdr *n, void *),
+ void *arg1/*,
+ int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *),
+ void *arg2*/)
+{
+ int retval = -1;
+ char *buf = xmalloc(8*1024); /* avoid big stack buffer */
+ struct sockaddr_nl nladdr;
+ struct iovec iov = { buf, 8*1024 };
+
+ while (1) {
+ int status;
+ struct nlmsghdr *h;
+
+ struct msghdr msg = {
+ (void*)&nladdr, sizeof(nladdr),
+ &iov, 1,
+ NULL, 0,
+ 0
+ };
+
+ status = recvmsg(rth->fd, &msg, 0);
+
+ if (status < 0) {
+ if (errno == EINTR)
+ continue;
+ bb_perror_msg("OVERRUN");
+ continue;
+ }
+ if (status == 0) {
+ bb_error_msg("EOF on netlink");
+ goto ret;
+ }
+ if (msg.msg_namelen != sizeof(nladdr)) {
+ bb_error_msg_and_die("sender address length == %d", msg.msg_namelen);
+ }
+
+ h = (struct nlmsghdr*)buf;
+ while (NLMSG_OK(h, status)) {
+ int err;
+
+ if (nladdr.nl_pid != 0 ||
+ h->nlmsg_pid != rth->local.nl_pid ||
+ h->nlmsg_seq != rth->dump) {
+// if (junk) {
+// err = junk(&nladdr, h, arg2);
+// if (err < 0) {
+// retval = err;
+// goto ret;
+// }
+// }
+ goto skip_it;
+ }
+
+ if (h->nlmsg_type == NLMSG_DONE) {
+ goto ret_0;
+ }
+ if (h->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *l_err = (struct nlmsgerr*)NLMSG_DATA(h);
+ if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
+ bb_error_msg("ERROR truncated");
+ } else {
+ errno = -l_err->error;
+ bb_perror_msg("RTNETLINK answers");
+ }
+ goto ret;
+ }
+ err = filter(&nladdr, h, arg1);
+ if (err < 0) {
+ retval = err;
+ goto ret;
+ }
+
+ skip_it:
+ h = NLMSG_NEXT(h, status);
+ }
+ if (msg.msg_flags & MSG_TRUNC) {
+ bb_error_msg("message truncated");
+ continue;
+ }
+ if (status) {
+ bb_error_msg_and_die("remnant of size %d!", status);
+ }
+ } /* while (1) */
+ ret_0:
+ retval++; /* = 0 */
+ ret:
+ free(buf);
+ return retval;
+}
+
+int FAST_FUNC xrtnl_dump_filter(struct rtnl_handle *rth,
+ int (*filter)(const struct sockaddr_nl *, struct nlmsghdr *, void *),
+ void *arg1)
+{
+ int ret = rtnl_dump_filter(rth, filter, arg1/*, NULL, NULL*/);
+ if (ret < 0)
+ bb_error_msg_and_die("dump terminated");
+ return ret;
+}
+
+int FAST_FUNC rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
+ pid_t peer, unsigned groups,
+ struct nlmsghdr *answer,
+ int (*junk)(struct sockaddr_nl *, struct nlmsghdr *, void *),
+ void *jarg)
+{
+/* bbox doesn't use parameters no. 3, 4, 6, 7, they are stubbed out */
+#define peer 0
+#define groups 0
+#define junk NULL
+#define jarg NULL
+ int retval = -1;
+ int status;
+ unsigned seq;
+ struct nlmsghdr *h;
+ struct sockaddr_nl nladdr;
+ struct iovec iov = { (void*)n, n->nlmsg_len };
+ char *buf = xmalloc(8*1024); /* avoid big stack buffer */
+ struct msghdr msg = {
+ (void*)&nladdr, sizeof(nladdr),
+ &iov, 1,
+ NULL, 0,
+ 0
+ };
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+// nladdr.nl_pid = peer;
+// nladdr.nl_groups = groups;
+
+ n->nlmsg_seq = seq = ++rtnl->seq;
+ if (answer == NULL) {
+ n->nlmsg_flags |= NLM_F_ACK;
+ }
+ status = sendmsg(rtnl->fd, &msg, 0);
+
+ if (status < 0) {
+ bb_perror_msg("cannot talk to rtnetlink");
+ goto ret;
+ }
+
+ iov.iov_base = buf;
+
+ while (1) {
+ iov.iov_len = 8*1024;
+ status = recvmsg(rtnl->fd, &msg, 0);
+
+ if (status < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ bb_perror_msg("OVERRUN");
+ continue;
+ }
+ if (status == 0) {
+ bb_error_msg("EOF on netlink");
+ goto ret;
+ }
+ if (msg.msg_namelen != sizeof(nladdr)) {
+ bb_error_msg_and_die("sender address length == %d", msg.msg_namelen);
+ }
+ for (h = (struct nlmsghdr*)buf; status >= (int)sizeof(*h); ) {
+// int l_err;
+ int len = h->nlmsg_len;
+ int l = len - sizeof(*h);
+
+ if (l < 0 || len > status) {
+ if (msg.msg_flags & MSG_TRUNC) {
+ bb_error_msg("truncated message");
+ goto ret;
+ }
+ bb_error_msg_and_die("malformed message: len=%d!", len);
+ }
+
+ if (nladdr.nl_pid != peer ||
+ h->nlmsg_pid != rtnl->local.nl_pid ||
+ h->nlmsg_seq != seq) {
+// if (junk) {
+// l_err = junk(&nladdr, h, jarg);
+// if (l_err < 0) {
+// retval = l_err;
+// goto ret;
+// }
+// }
+ continue;
+ }
+
+ if (h->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
+ if (l < (int)sizeof(struct nlmsgerr)) {
+ bb_error_msg("ERROR truncated");
+ } else {
+ errno = - err->error;
+ if (errno == 0) {
+ if (answer) {
+ memcpy(answer, h, h->nlmsg_len);
+ }
+ goto ret_0;
+ }
+ bb_perror_msg("RTNETLINK answers");
+ }
+ goto ret;
+ }
+ if (answer) {
+ memcpy(answer, h, h->nlmsg_len);
+ goto ret_0;
+ }
+
+ bb_error_msg("unexpected reply!");
+
+ status -= NLMSG_ALIGN(len);
+ h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len));
+ }
+ if (msg.msg_flags & MSG_TRUNC) {
+ bb_error_msg("message truncated");
+ continue;
+ }
+ if (status) {
+ bb_error_msg_and_die("remnant of size %d!", status);
+ }
+ } /* while (1) */
+ ret_0:
+ retval++; /* = 0 */
+ ret:
+ free(buf);
+ return retval;
+}
+
+int FAST_FUNC addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data)
+{
+ int len = RTA_LENGTH(4);
+ struct rtattr *rta;
+ if ((int)(NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen)
+ return -1;
+ rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
+ rta->rta_type = type;
+ rta->rta_len = len;
+ memcpy(RTA_DATA(rta), &data, 4);
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
+ return 0;
+}
+
+int FAST_FUNC addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
+{
+ int len = RTA_LENGTH(alen);
+ struct rtattr *rta;
+
+ if ((int)(NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen)
+ return -1;
+ rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
+ rta->rta_type = type;
+ rta->rta_len = len;
+ memcpy(RTA_DATA(rta), data, alen);
+ n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
+ return 0;
+}
+
+int FAST_FUNC rta_addattr32(struct rtattr *rta, int maxlen, int type, uint32_t data)
+{
+ int len = RTA_LENGTH(4);
+ struct rtattr *subrta;
+
+ if (RTA_ALIGN(rta->rta_len) + len > maxlen) {
+ return -1;
+ }
+ subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len));
+ subrta->rta_type = type;
+ subrta->rta_len = len;
+ memcpy(RTA_DATA(subrta), &data, 4);
+ rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
+ return 0;
+}
+
+int FAST_FUNC rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen)
+{
+ struct rtattr *subrta;
+ int len = RTA_LENGTH(alen);
+
+ if (RTA_ALIGN(rta->rta_len) + len > maxlen) {
+ return -1;
+ }
+ subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len));
+ subrta->rta_type = type;
+ subrta->rta_len = len;
+ memcpy(RTA_DATA(subrta), data, alen);
+ rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len;
+ return 0;
+}
+
+
+int FAST_FUNC parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
+{
+ while (RTA_OK(rta, len)) {
+ if (rta->rta_type <= max) {
+ tb[rta->rta_type] = rta;
+ }
+ rta = RTA_NEXT(rta,len);
+ }
+ if (len) {
+ bb_error_msg("deficit %d, rta_len=%d!", len, rta->rta_len);
+ }
+ return 0;
+}
diff --git a/networking/libiproute/libnetlink.h b/networking/libiproute/libnetlink.h
new file mode 100644
index 0000000..079153b
--- /dev/null
+++ b/networking/libiproute/libnetlink.h
@@ -0,0 +1,55 @@
+/* vi: set sw=4 ts=4: */
+#ifndef __LIBNETLINK_H__
+#define __LIBNETLINK_H__ 1
+
+#include <linux/types.h>
+/* We need linux/types.h because older kernels use __u32 etc
+ * in linux/[rt]netlink.h. 2.6.19 seems to be ok, though */
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+struct rtnl_handle
+{
+ int fd;
+ struct sockaddr_nl local;
+ struct sockaddr_nl peer;
+ uint32_t seq;
+ uint32_t dump;
+};
+
+extern int xrtnl_open(struct rtnl_handle *rth) FAST_FUNC;
+extern void rtnl_close(struct rtnl_handle *rth) FAST_FUNC;
+extern int xrtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type) FAST_FUNC;
+extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) FAST_FUNC;
+extern int xrtnl_dump_filter(struct rtnl_handle *rth,
+ int (*filter)(const struct sockaddr_nl*, struct nlmsghdr *n, void*),
+ void *arg1) FAST_FUNC;
+
+/* bbox doesn't use parameters no. 3, 4, 6, 7, stub them out */
+#define rtnl_talk(rtnl, n, peer, groups, answer, junk, jarg) \
+ rtnl_talk(rtnl, n, answer)
+extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
+ unsigned groups, struct nlmsghdr *answer,
+ int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *),
+ void *jarg) FAST_FUNC;
+
+extern int rtnl_send(struct rtnl_handle *rth, char *buf, int) FAST_FUNC;
+
+
+extern int addattr32(struct nlmsghdr *n, int maxlen, int type, uint32_t data) FAST_FUNC;
+extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) FAST_FUNC;
+extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, uint32_t data) FAST_FUNC;
+extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen) FAST_FUNC;
+
+extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) FAST_FUNC;
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif /* __LIBNETLINK_H__ */
diff --git a/networking/libiproute/ll_addr.c b/networking/libiproute/ll_addr.c
new file mode 100644
index 0000000..e732efd
--- /dev/null
+++ b/networking/libiproute/ll_addr.c
@@ -0,0 +1,79 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ll_addr.c
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <net/if_arp.h>
+
+#include "libbb.h"
+#include "rt_names.h"
+#include "utils.h"
+
+
+const char *ll_addr_n2a(unsigned char *addr, int alen, int type, char *buf, int blen)
+{
+ int i;
+ int l;
+
+ if (alen == 4 &&
+ (type == ARPHRD_TUNNEL || type == ARPHRD_SIT || type == ARPHRD_IPGRE)) {
+ return inet_ntop(AF_INET, addr, buf, blen);
+ }
+ l = 0;
+ for (i=0; i<alen; i++) {
+ if (i==0) {
+ snprintf(buf+l, blen, ":%02x"+1, addr[i]);
+ blen -= 2;
+ l += 2;
+ } else {
+ snprintf(buf+l, blen, ":%02x", addr[i]);
+ blen -= 3;
+ l += 3;
+ }
+ }
+ return buf;
+}
+
+int ll_addr_a2n(unsigned char *lladdr, int len, char *arg)
+{
+ if (strchr(arg, '.')) {
+ inet_prefix pfx;
+ if (get_addr_1(&pfx, arg, AF_INET)) {
+ bb_error_msg("\"%s\" is invalid lladdr", arg);
+ return -1;
+ }
+ if (len < 4) {
+ return -1;
+ }
+ memcpy(lladdr, pfx.data, 4);
+ return 4;
+ } else {
+ int i;
+
+ for (i=0; i<len; i++) {
+ int temp;
+ char *cp = strchr(arg, ':');
+ if (cp) {
+ *cp = 0;
+ cp++;
+ }
+ if (sscanf(arg, "%x", &temp) != 1 || (temp < 0 || temp > 255)) {
+ bb_error_msg("\"%s\" is invalid lladdr", arg);
+ return -1;
+ }
+ lladdr[i] = temp;
+ if (!cp) {
+ break;
+ }
+ arg = cp;
+ }
+ return i+1;
+ }
+}
diff --git a/networking/libiproute/ll_map.c b/networking/libiproute/ll_map.c
new file mode 100644
index 0000000..eeae4e2
--- /dev/null
+++ b/networking/libiproute/ll_map.c
@@ -0,0 +1,200 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ll_map.c
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <net/if.h> /* struct ifreq and co. */
+
+#include "libbb.h"
+#include "libnetlink.h"
+#include "ll_map.h"
+
+struct idxmap {
+ struct idxmap *next;
+ int index;
+ int type;
+ int alen;
+ unsigned flags;
+ unsigned char addr[8];
+ char name[16];
+};
+
+static struct idxmap *idxmap[16];
+
+static struct idxmap *find_by_index(int idx)
+{
+ struct idxmap *im;
+
+ for (im = idxmap[idx & 0xF]; im; im = im->next)
+ if (im->index == idx)
+ return im;
+ return NULL;
+}
+
+int ll_remember_index(const struct sockaddr_nl *who UNUSED_PARAM,
+ struct nlmsghdr *n,
+ void *arg UNUSED_PARAM)
+{
+ int h;
+ struct ifinfomsg *ifi = NLMSG_DATA(n);
+ struct idxmap *im, **imp;
+ struct rtattr *tb[IFLA_MAX+1];
+
+ if (n->nlmsg_type != RTM_NEWLINK)
+ return 0;
+
+ if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifi)))
+ return -1;
+
+ memset(tb, 0, sizeof(tb));
+ parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n));
+ if (tb[IFLA_IFNAME] == NULL)
+ return 0;
+
+ h = ifi->ifi_index & 0xF;
+
+ for (imp = &idxmap[h]; (im = *imp) != NULL; imp = &im->next)
+ if (im->index == ifi->ifi_index)
+ goto found;
+
+ im = xmalloc(sizeof(*im));
+ im->next = *imp;
+ im->index = ifi->ifi_index;
+ *imp = im;
+ found:
+ im->type = ifi->ifi_type;
+ im->flags = ifi->ifi_flags;
+ if (tb[IFLA_ADDRESS]) {
+ int alen;
+ im->alen = alen = RTA_PAYLOAD(tb[IFLA_ADDRESS]);
+ if (alen > (int)sizeof(im->addr))
+ alen = sizeof(im->addr);
+ memcpy(im->addr, RTA_DATA(tb[IFLA_ADDRESS]), alen);
+ } else {
+ im->alen = 0;
+ memset(im->addr, 0, sizeof(im->addr));
+ }
+ strcpy(im->name, RTA_DATA(tb[IFLA_IFNAME]));
+ return 0;
+}
+
+const char *ll_idx_n2a(int idx, char *buf)
+{
+ struct idxmap *im;
+
+ if (idx == 0)
+ return "*";
+ im = find_by_index(idx);
+ if (im)
+ return im->name;
+ snprintf(buf, 16, "if%d", idx);
+ return buf;
+}
+
+
+const char *ll_index_to_name(int idx)
+{
+ static char nbuf[16];
+
+ return ll_idx_n2a(idx, nbuf);
+}
+
+#ifdef UNUSED
+int ll_index_to_type(int idx)
+{
+ struct idxmap *im;
+
+ if (idx == 0)
+ return -1;
+ im = find_by_index(idx);
+ if (im)
+ return im->type;
+ return -1;
+}
+#endif
+
+unsigned ll_index_to_flags(int idx)
+{
+ struct idxmap *im;
+
+ if (idx == 0)
+ return 0;
+ im = find_by_index(idx);
+ if (im)
+ return im->flags;
+ return 0;
+}
+
+int xll_name_to_index(const char *const name)
+{
+ int ret = 0;
+ int sock_fd;
+
+/* caching is not warranted - no users which repeatedly call it */
+#ifdef UNUSED
+ static char ncache[16];
+ static int icache;
+
+ struct idxmap *im;
+ int i;
+
+ if (name == NULL)
+ goto out;
+ if (icache && strcmp(name, ncache) == 0) {
+ ret = icache;
+ goto out;
+ }
+ for (i = 0; i < 16; i++) {
+ for (im = idxmap[i]; im; im = im->next) {
+ if (strcmp(im->name, name) == 0) {
+ icache = im->index;
+ strcpy(ncache, name);
+ ret = im->index;
+ goto out;
+ }
+ }
+ }
+ /* We have not found the interface in our cache, but the kernel
+ * may still know about it. One reason is that we may be using
+ * module on-demand loading, which means that the kernel will
+ * load the module and make the interface exist only when
+ * we explicitely request it (check for dev_load() in net/core/dev.c).
+ * I can think of other similar scenario, but they are less common...
+ * Jean II */
+#endif
+
+ sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock_fd) {
+ struct ifreq ifr;
+ int tmp;
+
+ strncpy(ifr.ifr_name, name, IFNAMSIZ);
+ ifr.ifr_ifindex = -1;
+ tmp = ioctl(sock_fd, SIOCGIFINDEX, &ifr);
+ close(sock_fd);
+ if (tmp >= 0)
+ /* In theory, we should redump the interface list
+ * to update our cache, this is left as an exercise
+ * to the reader... Jean II */
+ ret = ifr.ifr_ifindex;
+ }
+/* out:*/
+ if (ret <= 0)
+ bb_error_msg_and_die("cannot find device \"%s\"", name);
+ return ret;
+}
+
+int ll_init_map(struct rtnl_handle *rth)
+{
+ xrtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK);
+ xrtnl_dump_filter(rth, ll_remember_index, &idxmap);
+ return 0;
+}
diff --git a/networking/libiproute/ll_map.h b/networking/libiproute/ll_map.h
new file mode 100644
index 0000000..6d64ac1
--- /dev/null
+++ b/networking/libiproute/ll_map.h
@@ -0,0 +1,21 @@
+/* vi: set sw=4 ts=4: */
+#ifndef __LL_MAP_H__
+#define __LL_MAP_H__ 1
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+int ll_remember_index(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg);
+int ll_init_map(struct rtnl_handle *rth);
+int xll_name_to_index(const char *const name);
+const char *ll_index_to_name(int idx);
+const char *ll_idx_n2a(int idx, char *buf);
+/* int ll_index_to_type(int idx); */
+unsigned ll_index_to_flags(int idx);
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif /* __LL_MAP_H__ */
diff --git a/networking/libiproute/ll_proto.c b/networking/libiproute/ll_proto.c
new file mode 100644
index 0000000..b826873
--- /dev/null
+++ b/networking/libiproute/ll_proto.c
@@ -0,0 +1,127 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ll_proto.c
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include "libbb.h"
+#include "rt_names.h"
+#include "utils.h"
+
+#if defined(__GLIBC__) && __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1
+#include <net/ethernet.h>
+#else
+#include <linux/if_ether.h>
+#endif
+
+#if !ENABLE_WERROR
+#warning de-bloat
+#endif
+/* Before re-enabling this, please (1) conditionalize exotic protocols
+ * on CONFIG_something, and (2) decouple strings and numbers
+ * (use llproto_ids[] = n,n,n..; and llproto_names[] = "loop\0" "pup\0" ...;)
+ */
+
+#define __PF(f,n) { ETH_P_##f, #n },
+static struct {
+ int id;
+ const char *name;
+} llproto_names[] = {
+__PF(LOOP,loop)
+__PF(PUP,pup)
+#ifdef ETH_P_PUPAT
+__PF(PUPAT,pupat)
+#endif
+__PF(IP,ip)
+__PF(X25,x25)
+__PF(ARP,arp)
+__PF(BPQ,bpq)
+#ifdef ETH_P_IEEEPUP
+__PF(IEEEPUP,ieeepup)
+#endif
+#ifdef ETH_P_IEEEPUPAT
+__PF(IEEEPUPAT,ieeepupat)
+#endif
+__PF(DEC,dec)
+__PF(DNA_DL,dna_dl)
+__PF(DNA_RC,dna_rc)
+__PF(DNA_RT,dna_rt)
+__PF(LAT,lat)
+__PF(DIAG,diag)
+__PF(CUST,cust)
+__PF(SCA,sca)
+__PF(RARP,rarp)
+__PF(ATALK,atalk)
+__PF(AARP,aarp)
+__PF(IPX,ipx)
+__PF(IPV6,ipv6)
+#ifdef ETH_P_PPP_DISC
+__PF(PPP_DISC,ppp_disc)
+#endif
+#ifdef ETH_P_PPP_SES
+__PF(PPP_SES,ppp_ses)
+#endif
+#ifdef ETH_P_ATMMPOA
+__PF(ATMMPOA,atmmpoa)
+#endif
+#ifdef ETH_P_ATMFATE
+__PF(ATMFATE,atmfate)
+#endif
+
+__PF(802_3,802_3)
+__PF(AX25,ax25)
+__PF(ALL,all)
+__PF(802_2,802_2)
+__PF(SNAP,snap)
+__PF(DDCMP,ddcmp)
+__PF(WAN_PPP,wan_ppp)
+__PF(PPP_MP,ppp_mp)
+__PF(LOCALTALK,localtalk)
+__PF(PPPTALK,ppptalk)
+__PF(TR_802_2,tr_802_2)
+__PF(MOBITEX,mobitex)
+__PF(CONTROL,control)
+__PF(IRDA,irda)
+#ifdef ETH_P_ECONET
+__PF(ECONET,econet)
+#endif
+
+{ 0x8100, "802.1Q" },
+{ ETH_P_IP, "ipv4" },
+};
+#undef __PF
+
+
+const char *ll_proto_n2a(unsigned short id, char *buf, int len)
+{
+ unsigned i;
+ id = ntohs(id);
+ for (i = 0; i < ARRAY_SIZE(llproto_names); i++) {
+ if (llproto_names[i].id == id)
+ return llproto_names[i].name;
+ }
+ snprintf(buf, len, "[%d]", id);
+ return buf;
+}
+
+int ll_proto_a2n(unsigned short *id, char *buf)
+{
+ unsigned i;
+ for (i = 0; i < ARRAY_SIZE(llproto_names); i++) {
+ if (strcasecmp(llproto_names[i].name, buf) == 0) {
+ *id = htons(llproto_names[i].id);
+ return 0;
+ }
+ }
+ if (get_u16(id, buf, 0))
+ return -1;
+ *id = htons(*id);
+ return 0;
+}
+
diff --git a/networking/libiproute/ll_types.c b/networking/libiproute/ll_types.c
new file mode 100644
index 0000000..d5d2a1f
--- /dev/null
+++ b/networking/libiproute/ll_types.c
@@ -0,0 +1,205 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * ll_types.c
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+#include <arpa/inet.h>
+#include <linux/if_arp.h>
+
+#include "libbb.h"
+#include "rt_names.h"
+
+const char *ll_type_n2a(int type, char *buf, int len)
+{
+ static const char arphrd_name[] =
+ /* 0, */ "generic" "\0"
+ /* ARPHRD_LOOPBACK, */ "loopback" "\0"
+ /* ARPHRD_ETHER, */ "ether" "\0"
+#ifdef ARPHRD_INFINIBAND
+ /* ARPHRD_INFINIBAND, */ "infiniband" "\0"
+#endif
+#ifdef ARPHRD_IEEE802_TR
+ /* ARPHRD_IEEE802, */ "ieee802" "\0"
+ /* ARPHRD_IEEE802_TR, */ "tr" "\0"
+#else
+ /* ARPHRD_IEEE802, */ "tr" "\0"
+#endif
+#ifdef ARPHRD_IEEE80211
+ /* ARPHRD_IEEE80211, */ "ieee802.11" "\0"
+#endif
+#ifdef ARPHRD_IEEE1394
+ /* ARPHRD_IEEE1394, */ "ieee1394" "\0"
+#endif
+ /* ARPHRD_IRDA, */ "irda" "\0"
+ /* ARPHRD_SLIP, */ "slip" "\0"
+ /* ARPHRD_CSLIP, */ "cslip" "\0"
+ /* ARPHRD_SLIP6, */ "slip6" "\0"
+ /* ARPHRD_CSLIP6, */ "cslip6" "\0"
+ /* ARPHRD_PPP, */ "ppp" "\0"
+ /* ARPHRD_TUNNEL, */ "ipip" "\0"
+ /* ARPHRD_TUNNEL6, */ "tunnel6" "\0"
+ /* ARPHRD_SIT, */ "sit" "\0"
+ /* ARPHRD_IPGRE, */ "gre" "\0"
+#ifdef ARPHRD_VOID
+ /* ARPHRD_VOID, */ "void" "\0"
+#endif
+
+#if ENABLE_FEATURE_IP_RARE_PROTOCOLS
+ /* ARPHRD_EETHER, */ "eether" "\0"
+ /* ARPHRD_AX25, */ "ax25" "\0"
+ /* ARPHRD_PRONET, */ "pronet" "\0"
+ /* ARPHRD_CHAOS, */ "chaos" "\0"
+ /* ARPHRD_ARCNET, */ "arcnet" "\0"
+ /* ARPHRD_APPLETLK, */ "atalk" "\0"
+ /* ARPHRD_DLCI, */ "dlci" "\0"
+#ifdef ARPHRD_ATM
+ /* ARPHRD_ATM, */ "atm" "\0"
+#endif
+ /* ARPHRD_METRICOM, */ "metricom" "\0"
+ /* ARPHRD_RSRVD, */ "rsrvd" "\0"
+ /* ARPHRD_ADAPT, */ "adapt" "\0"
+ /* ARPHRD_ROSE, */ "rose" "\0"
+ /* ARPHRD_X25, */ "x25" "\0"
+#ifdef ARPHRD_HWX25
+ /* ARPHRD_HWX25, */ "hwx25" "\0"
+#endif
+ /* ARPHRD_HDLC, */ "hdlc" "\0"
+ /* ARPHRD_LAPB, */ "lapb" "\0"
+#ifdef ARPHRD_DDCMP
+ /* ARPHRD_DDCMP, */ "ddcmp" "\0"
+ /* ARPHRD_RAWHDLC, */ "rawhdlc" "\0"
+#endif
+ /* ARPHRD_FRAD, */ "frad" "\0"
+ /* ARPHRD_SKIP, */ "skip" "\0"
+ /* ARPHRD_LOCALTLK, */ "ltalk" "\0"
+ /* ARPHRD_FDDI, */ "fddi" "\0"
+ /* ARPHRD_BIF, */ "bif" "\0"
+ /* ARPHRD_IPDDP, */ "ip/ddp" "\0"
+ /* ARPHRD_PIMREG, */ "pimreg" "\0"
+ /* ARPHRD_HIPPI, */ "hippi" "\0"
+ /* ARPHRD_ASH, */ "ash" "\0"
+ /* ARPHRD_ECONET, */ "econet" "\0"
+ /* ARPHRD_FCPP, */ "fcpp" "\0"
+ /* ARPHRD_FCAL, */ "fcal" "\0"
+ /* ARPHRD_FCPL, */ "fcpl" "\0"
+ /* ARPHRD_FCFABRIC, */ "fcfb0" "\0"
+ /* ARPHRD_FCFABRIC+1, */ "fcfb1" "\0"
+ /* ARPHRD_FCFABRIC+2, */ "fcfb2" "\0"
+ /* ARPHRD_FCFABRIC+3, */ "fcfb3" "\0"
+ /* ARPHRD_FCFABRIC+4, */ "fcfb4" "\0"
+ /* ARPHRD_FCFABRIC+5, */ "fcfb5" "\0"
+ /* ARPHRD_FCFABRIC+6, */ "fcfb6" "\0"
+ /* ARPHRD_FCFABRIC+7, */ "fcfb7" "\0"
+ /* ARPHRD_FCFABRIC+8, */ "fcfb8" "\0"
+ /* ARPHRD_FCFABRIC+9, */ "fcfb9" "\0"
+ /* ARPHRD_FCFABRIC+10, */ "fcfb10" "\0"
+ /* ARPHRD_FCFABRIC+11, */ "fcfb11" "\0"
+ /* ARPHRD_FCFABRIC+12, */ "fcfb12" "\0"
+#endif /* FEATURE_IP_RARE_PROTOCOLS */
+ ;
+
+ /* Keep these arrays in sync! */
+
+ static const uint16_t arphrd_type[] = {
+ 0, /* "generic" "\0" */
+ ARPHRD_LOOPBACK, /* "loopback" "\0" */
+ ARPHRD_ETHER, /* "ether" "\0" */
+#ifdef ARPHRD_INFINIBAND
+ ARPHRD_INFINIBAND, /* "infiniband" "\0" */
+#endif
+#ifdef ARPHRD_IEEE802_TR
+ ARPHRD_IEEE802, /* "ieee802" "\0" */
+ ARPHRD_IEEE802_TR, /* "tr" "\0" */
+#else
+ ARPHRD_IEEE802, /* "tr" "\0" */
+#endif
+#ifdef ARPHRD_IEEE80211
+ ARPHRD_IEEE80211, /* "ieee802.11" "\0" */
+#endif
+#ifdef ARPHRD_IEEE1394
+ ARPHRD_IEEE1394, /* "ieee1394" "\0" */
+#endif
+ ARPHRD_IRDA, /* "irda" "\0" */
+ ARPHRD_SLIP, /* "slip" "\0" */
+ ARPHRD_CSLIP, /* "cslip" "\0" */
+ ARPHRD_SLIP6, /* "slip6" "\0" */
+ ARPHRD_CSLIP6, /* "cslip6" "\0" */
+ ARPHRD_PPP, /* "ppp" "\0" */
+ ARPHRD_TUNNEL, /* "ipip" "\0" */
+ ARPHRD_TUNNEL6, /* "tunnel6" "\0" */
+ ARPHRD_SIT, /* "sit" "\0" */
+ ARPHRD_IPGRE, /* "gre" "\0" */
+#ifdef ARPHRD_VOID
+ ARPHRD_VOID, /* "void" "\0" */
+#endif
+
+#if ENABLE_FEATURE_IP_RARE_PROTOCOLS
+ ARPHRD_EETHER, /* "eether" "\0" */
+ ARPHRD_AX25, /* "ax25" "\0" */
+ ARPHRD_PRONET, /* "pronet" "\0" */
+ ARPHRD_CHAOS, /* "chaos" "\0" */
+ ARPHRD_ARCNET, /* "arcnet" "\0" */
+ ARPHRD_APPLETLK, /* "atalk" "\0" */
+ ARPHRD_DLCI, /* "dlci" "\0" */
+#ifdef ARPHRD_ATM
+ ARPHRD_ATM, /* "atm" "\0" */
+#endif
+ ARPHRD_METRICOM, /* "metricom" "\0" */
+ ARPHRD_RSRVD, /* "rsrvd" "\0" */
+ ARPHRD_ADAPT, /* "adapt" "\0" */
+ ARPHRD_ROSE, /* "rose" "\0" */
+ ARPHRD_X25, /* "x25" "\0" */
+#ifdef ARPHRD_HWX25
+ ARPHRD_HWX25, /* "hwx25" "\0" */
+#endif
+ ARPHRD_HDLC, /* "hdlc" "\0" */
+ ARPHRD_LAPB, /* "lapb" "\0" */
+#ifdef ARPHRD_DDCMP
+ ARPHRD_DDCMP, /* "ddcmp" "\0" */
+ ARPHRD_RAWHDLC, /* "rawhdlc" "\0" */
+#endif
+ ARPHRD_FRAD, /* "frad" "\0" */
+ ARPHRD_SKIP, /* "skip" "\0" */
+ ARPHRD_LOCALTLK, /* "ltalk" "\0" */
+ ARPHRD_FDDI, /* "fddi" "\0" */
+ ARPHRD_BIF, /* "bif" "\0" */
+ ARPHRD_IPDDP, /* "ip/ddp" "\0" */
+ ARPHRD_PIMREG, /* "pimreg" "\0" */
+ ARPHRD_HIPPI, /* "hippi" "\0" */
+ ARPHRD_ASH, /* "ash" "\0" */
+ ARPHRD_ECONET, /* "econet" "\0" */
+ ARPHRD_FCPP, /* "fcpp" "\0" */
+ ARPHRD_FCAL, /* "fcal" "\0" */
+ ARPHRD_FCPL, /* "fcpl" "\0" */
+ ARPHRD_FCFABRIC, /* "fcfb0" "\0" */
+ ARPHRD_FCFABRIC+1, /* "fcfb1" "\0" */
+ ARPHRD_FCFABRIC+2, /* "fcfb2" "\0" */
+ ARPHRD_FCFABRIC+3, /* "fcfb3" "\0" */
+ ARPHRD_FCFABRIC+4, /* "fcfb4" "\0" */
+ ARPHRD_FCFABRIC+5, /* "fcfb5" "\0" */
+ ARPHRD_FCFABRIC+6, /* "fcfb6" "\0" */
+ ARPHRD_FCFABRIC+7, /* "fcfb7" "\0" */
+ ARPHRD_FCFABRIC+8, /* "fcfb8" "\0" */
+ ARPHRD_FCFABRIC+9, /* "fcfb9" "\0" */
+ ARPHRD_FCFABRIC+10, /* "fcfb10" "\0" */
+ ARPHRD_FCFABRIC+11, /* "fcfb11" "\0" */
+ ARPHRD_FCFABRIC+12, /* "fcfb12" "\0" */
+#endif /* FEATURE_IP_RARE_PROTOCOLS */
+ };
+
+ unsigned i;
+ const char *aname = arphrd_name;
+ for (i = 0; i < ARRAY_SIZE(arphrd_type); i++) {
+ if (arphrd_type[i] == type)
+ return aname;
+ aname += strlen(aname) + 1;
+ }
+ snprintf(buf, len, "[%d]", type);
+ return buf;
+}
diff --git a/networking/libiproute/rt_names.c b/networking/libiproute/rt_names.c
new file mode 100644
index 0000000..e4d1061
--- /dev/null
+++ b/networking/libiproute/rt_names.c
@@ -0,0 +1,349 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * rt_names.c rtnetlink names DB.
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include "libbb.h"
+#include "rt_names.h"
+
+/* so far all callers have size == 256 */
+#define rtnl_tab_initialize(file, tab, size) rtnl_tab_initialize(file, tab)
+#define size 256
+static void rtnl_tab_initialize(const char *file, const char **tab, int size)
+{
+ char *token[2];
+ parser_t *parser = config_open2(file, fopen_for_read);
+ while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+ int id = bb_strtou(token[0], NULL, 0);
+ if (id < 0 || id > size) {
+ bb_error_msg("database %s is corrupted at line %d",
+ file, parser->lineno);
+ break;
+ }
+ tab[id] = xstrdup(token[1]);
+ }
+ config_close(parser);
+}
+#undef size
+
+static const char **rtnl_rtprot_tab; /* [256] */
+
+static void rtnl_rtprot_initialize(void)
+{
+ static const char *const init_tab[] = {
+ "none",
+ "redirect",
+ "kernel",
+ "boot",
+ "static",
+ NULL,
+ NULL,
+ NULL,
+ "gated",
+ "ra",
+ "mrt",
+ "zebra",
+ "bird",
+ };
+ if (rtnl_rtprot_tab) return;
+ rtnl_rtprot_tab = xzalloc(256 * sizeof(rtnl_rtprot_tab[0]));
+ memcpy(rtnl_rtprot_tab, init_tab, sizeof(init_tab));
+ rtnl_tab_initialize("/etc/iproute2/rt_protos",
+ rtnl_rtprot_tab, 256);
+}
+
+
+const char* rtnl_rtprot_n2a(int id, char *buf, int len)
+{
+ if (id < 0 || id >= 256) {
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtprot_initialize();
+
+ if (rtnl_rtprot_tab[id])
+ return rtnl_rtprot_tab[id];
+ snprintf(buf, len, "%d", id);
+ return buf;
+}
+
+int rtnl_rtprot_a2n(uint32_t *id, char *arg)
+{
+ static const char *cache = NULL;
+ static unsigned long res;
+ int i;
+
+ if (cache && strcmp(cache, arg) == 0) {
+ *id = res;
+ return 0;
+ }
+
+ rtnl_rtprot_initialize();
+
+ for (i = 0; i < 256; i++) {
+ if (rtnl_rtprot_tab[i] &&
+ strcmp(rtnl_rtprot_tab[i], arg) == 0) {
+ cache = rtnl_rtprot_tab[i];
+ res = i;
+ *id = res;
+ return 0;
+ }
+ }
+
+ res = bb_strtoul(arg, NULL, 0);
+ if (errno || res > 255)
+ return -1;
+ *id = res;
+ return 0;
+}
+
+
+static const char **rtnl_rtscope_tab; /* [256] */
+
+static void rtnl_rtscope_initialize(void)
+{
+ if (rtnl_rtscope_tab) return;
+ rtnl_rtscope_tab = xzalloc(256 * sizeof(rtnl_rtscope_tab[0]));
+ rtnl_rtscope_tab[0] = "global";
+ rtnl_rtscope_tab[255] = "nowhere";
+ rtnl_rtscope_tab[254] = "host";
+ rtnl_rtscope_tab[253] = "link";
+ rtnl_rtscope_tab[200] = "site";
+ rtnl_tab_initialize("/etc/iproute2/rt_scopes",
+ rtnl_rtscope_tab, 256);
+}
+
+
+const char* rtnl_rtscope_n2a(int id, char *buf, int len)
+{
+ if (id < 0 || id >= 256) {
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtscope_initialize();
+
+ if (rtnl_rtscope_tab[id])
+ return rtnl_rtscope_tab[id];
+ snprintf(buf, len, "%d", id);
+ return buf;
+}
+
+int rtnl_rtscope_a2n(uint32_t *id, char *arg)
+{
+ static const char *cache = NULL;
+ static unsigned long res;
+ int i;
+
+ if (cache && strcmp(cache, arg) == 0) {
+ *id = res;
+ return 0;
+ }
+
+ rtnl_rtscope_initialize();
+
+ for (i = 0; i < 256; i++) {
+ if (rtnl_rtscope_tab[i] &&
+ strcmp(rtnl_rtscope_tab[i], arg) == 0) {
+ cache = rtnl_rtscope_tab[i];
+ res = i;
+ *id = res;
+ return 0;
+ }
+ }
+
+ res = bb_strtoul(arg, NULL, 0);
+ if (errno || res > 255)
+ return -1;
+ *id = res;
+ return 0;
+}
+
+
+static const char **rtnl_rtrealm_tab; /* [256] */
+
+static void rtnl_rtrealm_initialize(void)
+{
+ if (rtnl_rtrealm_tab) return;
+ rtnl_rtrealm_tab = xzalloc(256 * sizeof(rtnl_rtrealm_tab[0]));
+ rtnl_rtrealm_tab[0] = "unknown";
+ rtnl_tab_initialize("/etc/iproute2/rt_realms",
+ rtnl_rtrealm_tab, 256);
+}
+
+
+int rtnl_rtrealm_a2n(uint32_t *id, char *arg)
+{
+ static const char *cache = NULL;
+ static unsigned long res;
+ int i;
+
+ if (cache && strcmp(cache, arg) == 0) {
+ *id = res;
+ return 0;
+ }
+
+ rtnl_rtrealm_initialize();
+
+ for (i = 0; i < 256; i++) {
+ if (rtnl_rtrealm_tab[i] &&
+ strcmp(rtnl_rtrealm_tab[i], arg) == 0) {
+ cache = rtnl_rtrealm_tab[i];
+ res = i;
+ *id = res;
+ return 0;
+ }
+ }
+
+ res = bb_strtoul(arg, NULL, 0);
+ if (errno || res > 255)
+ return -1;
+ *id = res;
+ return 0;
+}
+
+#if ENABLE_FEATURE_IP_RULE
+const char* rtnl_rtrealm_n2a(int id, char *buf, int len)
+{
+ if (id < 0 || id >= 256) {
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtrealm_initialize();
+
+ if (rtnl_rtrealm_tab[id])
+ return rtnl_rtrealm_tab[id];
+ snprintf(buf, len, "%d", id);
+ return buf;
+}
+#endif
+
+
+static const char **rtnl_rtdsfield_tab; /* [256] */
+
+static void rtnl_rtdsfield_initialize(void)
+{
+ if (rtnl_rtdsfield_tab) return;
+ rtnl_rtdsfield_tab = xzalloc(256 * sizeof(rtnl_rtdsfield_tab[0]));
+ rtnl_rtdsfield_tab[0] = "0";
+ rtnl_tab_initialize("/etc/iproute2/rt_dsfield",
+ rtnl_rtdsfield_tab, 256);
+}
+
+
+const char * rtnl_dsfield_n2a(int id, char *buf, int len)
+{
+ if (id < 0 || id >= 256) {
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+
+ rtnl_rtdsfield_initialize();
+
+ if (rtnl_rtdsfield_tab[id])
+ return rtnl_rtdsfield_tab[id];
+ snprintf(buf, len, "0x%02x", id);
+ return buf;
+}
+
+
+int rtnl_dsfield_a2n(uint32_t *id, char *arg)
+{
+ static const char *cache = NULL;
+ static unsigned long res;
+ int i;
+
+ if (cache && strcmp(cache, arg) == 0) {
+ *id = res;
+ return 0;
+ }
+
+ rtnl_rtdsfield_initialize();
+
+ for (i = 0; i < 256; i++) {
+ if (rtnl_rtdsfield_tab[i] &&
+ strcmp(rtnl_rtdsfield_tab[i], arg) == 0) {
+ cache = rtnl_rtdsfield_tab[i];
+ res = i;
+ *id = res;
+ return 0;
+ }
+ }
+
+ res = bb_strtoul(arg, NULL, 16);
+ if (errno || res > 255)
+ return -1;
+ *id = res;
+ return 0;
+}
+
+
+#if ENABLE_FEATURE_IP_RULE
+static const char **rtnl_rttable_tab; /* [256] */
+
+static void rtnl_rttable_initialize(void)
+{
+ if (rtnl_rtdsfield_tab) return;
+ rtnl_rttable_tab = xzalloc(256 * sizeof(rtnl_rttable_tab[0]));
+ rtnl_rttable_tab[0] = "unspec";
+ rtnl_rttable_tab[255] = "local";
+ rtnl_rttable_tab[254] = "main";
+ rtnl_rttable_tab[253] = "default";
+ rtnl_tab_initialize("/etc/iproute2/rt_tables", rtnl_rttable_tab, 256);
+}
+
+
+const char *rtnl_rttable_n2a(int id, char *buf, int len)
+{
+ if (id < 0 || id >= 256) {
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+
+ rtnl_rttable_initialize();
+
+ if (rtnl_rttable_tab[id])
+ return rtnl_rttable_tab[id];
+ snprintf(buf, len, "%d", id);
+ return buf;
+}
+
+int rtnl_rttable_a2n(uint32_t * id, char *arg)
+{
+ static char *cache = NULL;
+ static unsigned long res;
+ int i;
+
+ if (cache && strcmp(cache, arg) == 0) {
+ *id = res;
+ return 0;
+ }
+
+ rtnl_rttable_initialize();
+
+ for (i = 0; i < 256; i++) {
+ if (rtnl_rttable_tab[i] && strcmp(rtnl_rttable_tab[i], arg) == 0) {
+ cache = (char*)rtnl_rttable_tab[i];
+ res = i;
+ *id = res;
+ return 0;
+ }
+ }
+
+ i = bb_strtoul(arg, NULL, 0);
+ if (errno || i > 255)
+ return -1;
+ *id = i;
+ return 0;
+}
+
+#endif
diff --git a/networking/libiproute/rt_names.h b/networking/libiproute/rt_names.h
new file mode 100644
index 0000000..3d68b67
--- /dev/null
+++ b/networking/libiproute/rt_names.h
@@ -0,0 +1,35 @@
+/* vi: set sw=4 ts=4: */
+#ifndef RT_NAMES_H_
+#define RT_NAMES_H_ 1
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+extern const char* rtnl_rtprot_n2a(int id, char *buf, int len);
+extern const char* rtnl_rtscope_n2a(int id, char *buf, int len);
+extern const char* rtnl_rtrealm_n2a(int id, char *buf, int len);
+extern const char* rtnl_dsfield_n2a(int id, char *buf, int len);
+extern const char* rtnl_rttable_n2a(int id, char *buf, int len);
+extern int rtnl_rtprot_a2n(uint32_t *id, char *arg);
+extern int rtnl_rtscope_a2n(uint32_t *id, char *arg);
+extern int rtnl_rtrealm_a2n(uint32_t *id, char *arg);
+extern int rtnl_dsfield_a2n(uint32_t *id, char *arg);
+extern int rtnl_rttable_a2n(uint32_t *id, char *arg);
+
+
+extern const char* ll_type_n2a(int type, char *buf, int len);
+
+extern const char* ll_addr_n2a(unsigned char *addr, int alen, int type,
+ char *buf, int blen);
+extern int ll_addr_a2n(unsigned char *lladdr, int len, char *arg);
+
+
+extern const char* ll_proto_n2a(unsigned short id, char *buf, int len);
+extern int ll_proto_a2n(unsigned short *id, char *buf);
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif
diff --git a/networking/libiproute/rtm_map.c b/networking/libiproute/rtm_map.c
new file mode 100644
index 0000000..ca2f443
--- /dev/null
+++ b/networking/libiproute/rtm_map.c
@@ -0,0 +1,118 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * rtm_map.c
+ *
+ * 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; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include "libbb.h"
+#include "rt_names.h"
+#include "utils.h"
+
+const char *rtnl_rtntype_n2a(int id, char *buf, int len)
+{
+ switch (id) {
+ case RTN_UNSPEC:
+ return "none";
+ case RTN_UNICAST:
+ return "unicast";
+ case RTN_LOCAL:
+ return "local";
+ case RTN_BROADCAST:
+ return "broadcast";
+ case RTN_ANYCAST:
+ return "anycast";
+ case RTN_MULTICAST:
+ return "multicast";
+ case RTN_BLACKHOLE:
+ return "blackhole";
+ case RTN_UNREACHABLE:
+ return "unreachable";
+ case RTN_PROHIBIT:
+ return "prohibit";
+ case RTN_THROW:
+ return "throw";
+ case RTN_NAT:
+ return "nat";
+ case RTN_XRESOLVE:
+ return "xresolve";
+ default:
+ snprintf(buf, len, "%d", id);
+ return buf;
+ }
+}
+
+
+int rtnl_rtntype_a2n(int *id, char *arg)
+{
+ static const char keywords[] ALIGN1 =
+ "local\0""nat\0""broadcast\0""brd\0""anycast\0"
+ "multicast\0""prohibit\0""unreachable\0""blackhole\0"
+ "xresolve\0""unicast\0""throw\0";
+ enum {
+ ARG_local = 1, ARG_nat, ARG_broadcast, ARG_brd, ARG_anycast,
+ ARG_multicast, ARG_prohibit, ARG_unreachable, ARG_blackhole,
+ ARG_xresolve, ARG_unicast, ARG_throw
+ };
+ const smalluint key = index_in_substrings(keywords, arg) + 1;
+ char *end;
+ unsigned long res;
+
+ if (key == ARG_local)
+ res = RTN_LOCAL;
+ else if (key == ARG_nat)
+ res = RTN_NAT;
+ else if (key == ARG_broadcast || key == ARG_brd)
+ res = RTN_BROADCAST;
+ else if (key == ARG_anycast)
+ res = RTN_ANYCAST;
+ else if (key == ARG_multicast)
+ res = RTN_MULTICAST;
+ else if (key == ARG_prohibit)
+ res = RTN_PROHIBIT;
+ else if (key == ARG_unreachable)
+ res = RTN_UNREACHABLE;
+ else if (key == ARG_blackhole)
+ res = RTN_BLACKHOLE;
+ else if (key == ARG_xresolve)
+ res = RTN_XRESOLVE;
+ else if (key == ARG_unicast)
+ res = RTN_UNICAST;
+ else if (key == ARG_throw)
+ res = RTN_THROW;
+ else {
+ res = strtoul(arg, &end, 0);
+ if (!end || end == arg || *end || res > 255)
+ return -1;
+ }
+ *id = res;
+ return 0;
+}
+
+int get_rt_realms(uint32_t *realms, char *arg)
+{
+ uint32_t realm = 0;
+ char *p = strchr(arg, '/');
+
+ *realms = 0;
+ if (p) {
+ *p = 0;
+ if (rtnl_rtrealm_a2n(realms, arg)) {
+ *p = '/';
+ return -1;
+ }
+ *realms <<= 16;
+ *p = '/';
+ arg = p+1;
+ }
+ if (*arg && rtnl_rtrealm_a2n(&realm, arg))
+ return -1;
+ *realms |= realm;
+ return 0;
+}
diff --git a/networking/libiproute/rtm_map.h b/networking/libiproute/rtm_map.h
new file mode 100644
index 0000000..02fa77e
--- /dev/null
+++ b/networking/libiproute/rtm_map.h
@@ -0,0 +1,18 @@
+/* vi: set sw=4 ts=4: */
+#ifndef __RTM_MAP_H__
+#define __RTM_MAP_H__ 1
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+const char *rtnl_rtntype_n2a(int id, char *buf, int len);
+int rtnl_rtntype_a2n(int *id, char *arg);
+
+int get_rt_realms(uint32_t *realms, char *arg);
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif /* __RTM_MAP_H__ */
diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c
new file mode 100644
index 0000000..cd101f1
--- /dev/null
+++ b/networking/libiproute/utils.c
@@ -0,0 +1,324 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * utils.c
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> 980929: resolve addresses
+ */
+
+#include "libbb.h"
+#include "utils.h"
+#include "inet_common.h"
+
+int get_integer(int *val, char *arg, int base)
+{
+ long res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtol(arg, &ptr, base);
+ if (!ptr || ptr == arg || *ptr || res > INT_MAX || res < INT_MIN)
+ return -1;
+ *val = res;
+ return 0;
+}
+//XXX: FIXME: use some libbb function instead
+int get_unsigned(unsigned *val, char *arg, int base)
+{
+ unsigned long res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtoul(arg, &ptr, base);
+ if (!ptr || ptr == arg || *ptr || res > UINT_MAX)
+ return -1;
+ *val = res;
+ return 0;
+}
+
+int get_u32(uint32_t * val, char *arg, int base)
+{
+ unsigned long res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtoul(arg, &ptr, base);
+ if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL)
+ return -1;
+ *val = res;
+ return 0;
+}
+
+int get_u16(uint16_t * val, char *arg, int base)
+{
+ unsigned long res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtoul(arg, &ptr, base);
+ if (!ptr || ptr == arg || *ptr || res > 0xFFFF)
+ return -1;
+ *val = res;
+ return 0;
+}
+
+int get_u8(uint8_t * val, char *arg, int base)
+{
+ unsigned long res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtoul(arg, &ptr, base);
+ if (!ptr || ptr == arg || *ptr || res > 0xFF)
+ return -1;
+ *val = res;
+ return 0;
+}
+
+int get_s16(int16_t * val, char *arg, int base)
+{
+ long res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtol(arg, &ptr, base);
+ if (!ptr || ptr == arg || *ptr || res > 0x7FFF || res < -0x8000)
+ return -1;
+ *val = res;
+ return 0;
+}
+
+int get_s8(int8_t * val, char *arg, int base)
+{
+ long res;
+ char *ptr;
+
+ if (!arg || !*arg)
+ return -1;
+ res = strtol(arg, &ptr, base);
+ if (!ptr || ptr == arg || *ptr || res > 0x7F || res < -0x80)
+ return -1;
+ *val = res;
+ return 0;
+}
+
+int get_addr_1(inet_prefix * addr, char *name, int family)
+{
+ memset(addr, 0, sizeof(*addr));
+
+ if (strcmp(name, bb_str_default) == 0 ||
+ strcmp(name, "all") == 0 || strcmp(name, "any") == 0) {
+ addr->family = family;
+ addr->bytelen = (family == AF_INET6 ? 16 : 4);
+ addr->bitlen = -1;
+ return 0;
+ }
+
+ if (strchr(name, ':')) {
+ addr->family = AF_INET6;
+ if (family != AF_UNSPEC && family != AF_INET6)
+ return -1;
+ if (inet_pton(AF_INET6, name, addr->data) <= 0)
+ return -1;
+ addr->bytelen = 16;
+ addr->bitlen = -1;
+ return 0;
+ }
+
+ addr->family = AF_INET;
+ if (family != AF_UNSPEC && family != AF_INET)
+ return -1;
+ if (inet_pton(AF_INET, name, addr->data) <= 0)
+ return -1;
+ addr->bytelen = 4;
+ addr->bitlen = -1;
+ return 0;
+}
+
+int get_prefix_1(inet_prefix * dst, char *arg, int family)
+{
+ int err;
+ unsigned plen;
+ char *slash;
+
+ memset(dst, 0, sizeof(*dst));
+
+ if (strcmp(arg, bb_str_default) == 0 || strcmp(arg, "any") == 0) {
+ dst->family = family;
+ dst->bytelen = 0;
+ dst->bitlen = 0;
+ return 0;
+ }
+
+ slash = strchr(arg, '/');
+ if (slash)
+ *slash = '\0';
+ err = get_addr_1(dst, arg, family);
+ if (err == 0) {
+ dst->bitlen = (dst->family == AF_INET6) ? 128 : 32;
+ if (slash) {
+ inet_prefix netmask_pfx;
+
+ netmask_pfx.family = AF_UNSPEC;
+ if ((get_unsigned(&plen, slash + 1, 0) || plen > dst->bitlen)
+ && (get_addr_1(&netmask_pfx, slash + 1, family)))
+ err = -1;
+ else if (netmask_pfx.family == AF_INET) {
+ /* fill in prefix length of dotted quad */
+ uint32_t mask = ntohl(netmask_pfx.data[0]);
+ uint32_t host = ~mask;
+
+ /* a valid netmask must be 2^n - 1 */
+ if (!(host & (host + 1))) {
+ for (plen = 0; mask; mask <<= 1)
+ ++plen;
+ if (plen >= 0 && plen <= dst->bitlen) {
+ dst->bitlen = plen;
+ /* dst->flags |= PREFIXLEN_SPECIFIED; */
+ } else
+ err = -1;
+ } else
+ err = -1;
+ } else {
+ /* plain prefix */
+ dst->bitlen = plen;
+ }
+ }
+ }
+ if (slash)
+ *slash = '/';
+ return err;
+}
+
+int get_addr(inet_prefix * dst, char *arg, int family)
+{
+ if (family == AF_PACKET) {
+ bb_error_msg_and_die("\"%s\" may be inet %s, but it is not allowed in this context", arg, "address");
+ }
+ if (get_addr_1(dst, arg, family)) {
+ bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "inet", "address", arg);
+ }
+ return 0;
+}
+
+int get_prefix(inet_prefix * dst, char *arg, int family)
+{
+ if (family == AF_PACKET) {
+ bb_error_msg_and_die("\"%s\" may be inet %s, but it is not allowed in this context", arg, "prefix");
+ }
+ if (get_prefix_1(dst, arg, family)) {
+ bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "inet", "prefix", arg);
+ }
+ return 0;
+}
+
+uint32_t get_addr32(char *name)
+{
+ inet_prefix addr;
+
+ if (get_addr_1(&addr, name, AF_INET)) {
+ bb_error_msg_and_die("an %s %s is expected rather than \"%s\"", "IP", "address", name);
+ }
+ return addr.data[0];
+}
+
+void incomplete_command(void)
+{
+ bb_error_msg_and_die("command line is not complete, try option \"help\"");
+}
+
+void invarg(const char *arg, const char *opt)
+{
+ bb_error_msg_and_die(bb_msg_invalid_arg, arg, opt);
+}
+
+void duparg(const char *key, const char *arg)
+{
+ bb_error_msg_and_die("duplicate \"%s\": \"%s\" is the second value", key, arg);
+}
+
+void duparg2(const char *key, const char *arg)
+{
+ bb_error_msg_and_die("either \"%s\" is duplicate, or \"%s\" is garbage", key, arg);
+}
+
+int inet_addr_match(inet_prefix * a, inet_prefix * b, int bits)
+{
+ uint32_t *a1 = a->data;
+ uint32_t *a2 = b->data;
+ int words = bits >> 0x05;
+
+ bits &= 0x1f;
+
+ if (words)
+ if (memcmp(a1, a2, words << 2))
+ return -1;
+
+ if (bits) {
+ uint32_t w1, w2;
+ uint32_t mask;
+
+ w1 = a1[words];
+ w2 = a2[words];
+
+ mask = htonl((0xffffffff) << (0x20 - bits));
+
+ if ((w1 ^ w2) & mask)
+ return 1;
+ }
+
+ return 0;
+}
+
+const char *rt_addr_n2a(int af, int UNUSED_PARAM len,
+ void *addr, char *buf, int buflen)
+{
+ switch (af) {
+ case AF_INET:
+ case AF_INET6:
+ return inet_ntop(af, addr, buf, buflen);
+ default:
+ return "???";
+ }
+}
+
+
+const char *format_host(int af, int len, void *addr, char *buf, int buflen)
+{
+#ifdef RESOLVE_HOSTNAMES
+ if (resolve_hosts) {
+ struct hostent *h_ent;
+
+ if (len <= 0) {
+ switch (af) {
+ case AF_INET:
+ len = 4;
+ break;
+ case AF_INET6:
+ len = 16;
+ break;
+ default:;
+ }
+ }
+ if (len > 0) {
+ h_ent = gethostbyaddr(addr, len, af);
+ if (h_ent != NULL) {
+ safe_strncpy(buf, h_ent->h_name, buflen);
+ return buf;
+ }
+ }
+ }
+#endif
+ return rt_addr_n2a(af, len, addr, buf, buflen);
+}
diff --git a/networking/libiproute/utils.h b/networking/libiproute/utils.h
new file mode 100644
index 0000000..1af39ff
--- /dev/null
+++ b/networking/libiproute/utils.h
@@ -0,0 +1,96 @@
+/* vi: set sw=4 ts=4: */
+#ifndef __UTILS_H__
+#define __UTILS_H__ 1
+
+#include "libnetlink.h"
+#include "ll_map.h"
+#include "rtm_map.h"
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+extern family_t preferred_family;
+extern smallint show_stats; /* UNUSED */
+extern smallint show_details; /* UNUSED */
+extern smallint show_raw; /* UNUSED */
+extern smallint resolve_hosts; /* UNUSED */
+extern smallint oneline;
+extern char _SL_;
+
+#ifndef IPPROTO_ESP
+#define IPPROTO_ESP 50
+#endif
+#ifndef IPPROTO_AH
+#define IPPROTO_AH 51
+#endif
+
+#define SPRINT_BSIZE 64
+#define SPRINT_BUF(x) char x[SPRINT_BSIZE]
+
+extern void incomplete_command(void) NORETURN;
+
+#define NEXT_ARG() do { if (!*++argv) incomplete_command(); } while (0)
+
+typedef struct {
+ uint8_t family;
+ uint8_t bytelen;
+ int16_t bitlen;
+ uint32_t data[4];
+} inet_prefix;
+
+#define PREFIXLEN_SPECIFIED 1
+
+#define DN_MAXADDL 20
+#ifndef AF_DECnet
+#define AF_DECnet 12
+#endif
+
+struct dn_naddr {
+ unsigned short a_len;
+ unsigned char a_addr[DN_MAXADDL];
+};
+
+#define IPX_NODE_LEN 6
+
+struct ipx_addr {
+ uint32_t ipx_net;
+ uint8_t ipx_node[IPX_NODE_LEN];
+};
+
+extern uint32_t get_addr32(char *name);
+extern int get_addr_1(inet_prefix *dst, char *arg, int family);
+extern int get_prefix_1(inet_prefix *dst, char *arg, int family);
+extern int get_addr(inet_prefix *dst, char *arg, int family);
+extern int get_prefix(inet_prefix *dst, char *arg, int family);
+
+extern int get_integer(int *val, char *arg, int base);
+extern int get_unsigned(unsigned *val, char *arg, int base);
+#define get_byte get_u8
+#define get_ushort get_u16
+#define get_short get_s16
+extern int get_u32(uint32_t *val, char *arg, int base);
+extern int get_u16(uint16_t *val, char *arg, int base);
+extern int get_s16(int16_t *val, char *arg, int base);
+extern int get_u8(uint8_t *val, char *arg, int base);
+extern int get_s8(int8_t *val, char *arg, int base);
+
+extern const char *format_host(int af, int len, void *addr, char *buf, int buflen);
+extern const char *rt_addr_n2a(int af, int len, void *addr, char *buf, int buflen);
+
+void invarg(const char *, const char *) NORETURN;
+void duparg(const char *, const char *) NORETURN;
+void duparg2(const char *, const char *) NORETURN;
+int inet_addr_match(inet_prefix *a, inet_prefix *b, int bits);
+
+const char *dnet_ntop(int af, const void *addr, char *str, size_t len);
+int dnet_pton(int af, const char *src, void *addr);
+
+const char *ipx_ntop(int af, const void *addr, char *str, size_t len);
+int ipx_pton(int af, const char *src, void *addr);
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif /* __UTILS_H__ */
diff --git a/networking/nameif.c b/networking/nameif.c
new file mode 100644
index 0000000..75829fa
--- /dev/null
+++ b/networking/nameif.c
@@ -0,0 +1,232 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * nameif.c - Naming Interfaces based on MAC address for busybox.
+ *
+ * Written 2000 by Andi Kleen.
+ * Busybox port 2002 by Nick Fedchik <nick@fedchik.org.ua>
+ * Glenn McGrath
+ * Extended matching support 2008 by Nico Erfurth <masta@perlgolf.de>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+#include <syslog.h>
+#include <net/if.h>
+#include <netinet/ether.h>
+#include <linux/sockios.h>
+
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+/* Taken from linux/sockios.h */
+#define SIOCSIFNAME 0x8923 /* set interface name */
+
+/* Octets in one Ethernet addr, from <linux/if_ether.h> */
+#define ETH_ALEN 6
+
+#ifndef ifr_newname
+#define ifr_newname ifr_ifru.ifru_slave
+#endif
+
+typedef struct ethtable_s {
+ struct ethtable_s *next;
+ struct ethtable_s *prev;
+ char *ifname;
+ struct ether_addr *mac;
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ char *bus_info;
+ char *driver;
+#endif
+} ethtable_t;
+
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+/* Cut'n'paste from ethtool.h */
+#define ETHTOOL_BUSINFO_LEN 32
+/* these strings are set to whatever the driver author decides... */
+struct ethtool_drvinfo {
+ uint32_t cmd;
+ char driver[32]; /* driver short name, "tulip", "eepro100" */
+ char version[32]; /* driver version string */
+ char fw_version[32]; /* firmware version string, if applicable */
+ char bus_info[ETHTOOL_BUSINFO_LEN]; /* Bus info for this IF. */
+ /* For PCI devices, use pci_dev->slot_name. */
+ char reserved1[32];
+ char reserved2[16];
+ uint32_t n_stats; /* number of u64's from ETHTOOL_GSTATS */
+ uint32_t testinfo_len;
+ uint32_t eedump_len; /* Size of data from ETHTOOL_GEEPROM (bytes) */
+ uint32_t regdump_len; /* Size of data from ETHTOOL_GREGS (bytes) */
+};
+#define ETHTOOL_GDRVINFO 0x00000003 /* Get driver info. */
+#endif
+
+
+static void nameif_parse_selector(ethtable_t *ch, char *selector)
+{
+ struct ether_addr *lmac;
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ int found_selector = 0;
+
+ while (*selector) {
+ char *next;
+#endif
+ selector = skip_whitespace(selector);
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ if (*selector == '\0')
+ break;
+ /* Search for the end .... */
+ next = skip_non_whitespace(selector);
+ if (*next)
+ *next++ = '\0';
+ /* Check for selectors, mac= is assumed */
+ if (strncmp(selector, "bus=", 4) == 0) {
+ ch->bus_info = xstrdup(selector + 4);
+ found_selector++;
+ } else if (strncmp(selector, "driver=", 7) == 0) {
+ ch->driver = xstrdup(selector + 7);
+ found_selector++;
+ } else {
+#endif
+ lmac = xmalloc(ETH_ALEN);
+ ch->mac = ether_aton_r(selector + (strncmp(selector, "mac=", 4) ? 0 : 4), lmac);
+ if (ch->mac == NULL)
+ bb_error_msg_and_die("cannot parse %s", selector);
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ found_selector++;
+ };
+ selector = next;
+ }
+ if (found_selector == 0)
+ bb_error_msg_and_die("no selectors found for %s", ch->ifname);
+#endif
+}
+
+static void prepend_new_eth_table(ethtable_t **clist, char *ifname, char *selector)
+{
+ ethtable_t *ch;
+ if (strlen(ifname) >= IFNAMSIZ)
+ bb_error_msg_and_die("interface name '%s' too long", ifname);
+ ch = xzalloc(sizeof(*ch));
+ ch->ifname = xstrdup(ifname);
+ nameif_parse_selector(ch, selector);
+ ch->next = *clist;
+ if (*clist)
+ (*clist)->prev = ch;
+ *clist = ch;
+}
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void delete_eth_table(ethtable_t *ch)
+{
+ free(ch->ifname);
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ free(ch->bus_info);
+ free(ch->driver);
+#endif
+ free(ch->mac);
+ free(ch);
+};
+#else
+void delete_eth_table(ethtable_t *ch);
+#endif
+
+int nameif_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nameif_main(int argc, char **argv)
+{
+ ethtable_t *clist = NULL;
+ const char *fname = "/etc/mactab";
+ int ctl_sk;
+ ethtable_t *ch;
+ parser_t *parser;
+ char *token[2];
+
+ if (1 & getopt32(argv, "sc:", &fname)) {
+ openlog(applet_name, 0, LOG_LOCAL0);
+ logmode = LOGMODE_SYSLOG;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc & 1)
+ bb_show_usage();
+
+ if (argc) {
+ while (*argv) {
+ char *ifname = xstrdup(*argv++);
+ prepend_new_eth_table(&clist, ifname, *argv++);
+ }
+ } else {
+ parser = config_open(fname);
+ while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL))
+ prepend_new_eth_table(&clist, token[0], token[1]);
+ config_close(parser);
+ }
+
+ ctl_sk = xsocket(PF_INET, SOCK_DGRAM, 0);
+ parser = config_open2("/proc/net/dev", xfopen_for_read);
+
+ while (clist && config_read(parser, token, 2, 2, "\0: \t", PARSE_NORMAL)) {
+ struct ifreq ifr;
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ struct ethtool_drvinfo drvinfo;
+#endif
+ if (parser->lineno < 2)
+ continue; /* Skip the first two lines */
+
+ /* Find the current interface name and copy it to ifr.ifr_name */
+ memset(&ifr, 0, sizeof(struct ifreq));
+ strncpy(ifr.ifr_name, token[0], sizeof(ifr.ifr_name));
+
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ /* Check for driver etc. */
+ memset(&drvinfo, 0, sizeof(struct ethtool_drvinfo));
+ drvinfo.cmd = ETHTOOL_GDRVINFO;
+ ifr.ifr_data = (caddr_t) &drvinfo;
+ /* Get driver and businfo first, so we have it in drvinfo */
+ ioctl(ctl_sk, SIOCETHTOOL, &ifr);
+#endif
+ ioctl(ctl_sk, SIOCGIFHWADDR, &ifr);
+
+ /* Search the list for a matching device */
+ for (ch = clist; ch; ch = ch->next) {
+#if ENABLE_FEATURE_NAMEIF_EXTENDED
+ if (ch->bus_info && strcmp(ch->bus_info, drvinfo.bus_info) != 0)
+ continue;
+ if (ch->driver && strcmp(ch->driver, drvinfo.driver) != 0)
+ continue;
+#endif
+ if (ch->mac && memcmp(ch->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN) != 0)
+ continue;
+ /* if we came here, all selectors have matched */
+ break;
+ }
+ /* Nothing found for current interface */
+ if (!ch)
+ continue;
+
+ if (strcmp(ifr.ifr_name, ch->ifname) != 0) {
+ strcpy(ifr.ifr_newname, ch->ifname);
+ ioctl_or_perror_and_die(ctl_sk, SIOCSIFNAME, &ifr,
+ "cannot change ifname %s to %s",
+ ifr.ifr_name, ch->ifname);
+ }
+ /* Remove list entry of renamed interface */
+ if (ch->prev != NULL)
+ ch->prev->next = ch->next;
+ else
+ clist = ch->next;
+ if (ch->next != NULL)
+ ch->next->prev = ch->prev;
+ if (ENABLE_FEATURE_CLEAN_UP)
+ delete_eth_table(ch);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP) {
+ for (ch = clist; ch; ch = ch->next)
+ delete_eth_table(ch);
+ config_close(parser);
+ };
+
+ return 0;
+}
diff --git a/networking/nc.c b/networking/nc.c
new file mode 100644
index 0000000..fe845f5
--- /dev/null
+++ b/networking/nc.c
@@ -0,0 +1,201 @@
+/* vi: set sw=4 ts=4: */
+/* nc: mini-netcat - built from the ground up for LRP
+ *
+ * Copyright (C) 1998, 1999 Charles P. Wright
+ * Copyright (C) 1998 Dave Cinege
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+
+#if ENABLE_DESKTOP
+#include "nc_bloaty.c"
+#else
+
+/* Lots of small differences in features
+ * when compared to "standard" nc
+ */
+
+static void timeout(int signum UNUSED_PARAM)
+{
+ bb_error_msg_and_die("timed out");
+}
+
+int nc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nc_main(int argc, char **argv)
+{
+ /* sfd sits _here_ only because of "repeat" option (-l -l). */
+ int sfd = sfd; /* for gcc */
+ int cfd = 0;
+ unsigned lport = 0;
+ SKIP_NC_SERVER(const) unsigned do_listen = 0;
+ SKIP_NC_EXTRA (const) unsigned wsecs = 0;
+ SKIP_NC_EXTRA (const) unsigned delay = 0;
+ SKIP_NC_EXTRA (const int execparam = 0;)
+ USE_NC_EXTRA (char **execparam = NULL;)
+ len_and_sockaddr *lsa;
+ fd_set readfds, testfds;
+ int opt; /* must be signed (getopt returns -1) */
+
+ if (ENABLE_NC_SERVER || ENABLE_NC_EXTRA) {
+ /* getopt32 is _almost_ usable:
+ ** it cannot handle "... -e prog -prog-opt" */
+ while ((opt = getopt(argc, argv,
+ "" USE_NC_SERVER("lp:") USE_NC_EXTRA("w:i:f:e:") )) > 0
+ ) {
+ if (ENABLE_NC_SERVER && opt=='l')
+ USE_NC_SERVER(do_listen++);
+ else if (ENABLE_NC_SERVER && opt=='p')
+ USE_NC_SERVER(lport = bb_lookup_port(optarg, "tcp", 0));
+ else if (ENABLE_NC_EXTRA && opt=='w')
+ USE_NC_EXTRA( wsecs = xatou(optarg));
+ else if (ENABLE_NC_EXTRA && opt=='i')
+ USE_NC_EXTRA( delay = xatou(optarg));
+ else if (ENABLE_NC_EXTRA && opt=='f')
+ USE_NC_EXTRA( cfd = xopen(optarg, O_RDWR));
+ else if (ENABLE_NC_EXTRA && opt=='e' && optind <= argc) {
+ /* We cannot just 'break'. We should let getopt finish.
+ ** Or else we won't be able to find where
+ ** 'host' and 'port' params are
+ ** (think "nc -w 60 host port -e prog"). */
+ USE_NC_EXTRA(
+ char **p;
+ // +2: one for progname (optarg) and one for NULL
+ execparam = xzalloc(sizeof(char*) * (argc - optind + 2));
+ p = execparam;
+ *p++ = optarg;
+ while (optind < argc) {
+ *p++ = argv[optind++];
+ }
+ )
+ /* optind points to argv[arvc] (NULL) now.
+ ** FIXME: we assume that getopt will not count options
+ ** possibly present on "-e prog args" and will not
+ ** include them into final value of optind
+ ** which is to be used ... */
+ } else bb_show_usage();
+ }
+ argv += optind; /* ... here! */
+ argc -= optind;
+ // -l and -f don't mix
+ if (do_listen && cfd) bb_show_usage();
+ // Listen or file modes need zero arguments, client mode needs 2
+ if (do_listen || cfd) {
+ if (argc) bb_show_usage();
+ } else {
+ if (!argc || argc > 2) bb_show_usage();
+ }
+ } else {
+ if (argc != 3) bb_show_usage();
+ argc--;
+ argv++;
+ }
+
+ if (wsecs) {
+ signal(SIGALRM, timeout);
+ alarm(wsecs);
+ }
+
+ if (!cfd) {
+ if (do_listen) {
+ /* create_and_bind_stream_or_die(NULL, lport)
+ * would've work wonderfully, but we need
+ * to know lsa */
+ sfd = xsocket_stream(&lsa);
+ if (lport)
+ set_nport(lsa, htons(lport));
+ setsockopt_reuseaddr(sfd);
+ xbind(sfd, &lsa->u.sa, lsa->len);
+ xlisten(sfd, do_listen); /* can be > 1 */
+ /* If we didn't specify a port number,
+ * query and print it after listen() */
+ if (!lport) {
+ socklen_t addrlen = lsa->len;
+ getsockname(sfd, &lsa->u.sa, &addrlen);
+ lport = get_nport(&lsa->u.sa);
+ fdprintf(2, "%d\n", ntohs(lport));
+ }
+ close_on_exec_on(sfd);
+ accept_again:
+ cfd = accept(sfd, NULL, 0);
+ if (cfd < 0)
+ bb_perror_msg_and_die("accept");
+ if (!execparam)
+ close(sfd);
+ } else {
+ cfd = create_and_connect_stream_or_die(argv[0],
+ argv[1] ? bb_lookup_port(argv[1], "tcp", 0) : 0);
+ }
+ }
+
+ if (wsecs) {
+ alarm(0);
+ /* Non-ignored siganls revert to SIG_DFL on exec anyway */
+ /*signal(SIGALRM, SIG_DFL);*/
+ }
+
+ /* -e given? */
+ if (execparam) {
+ signal(SIGCHLD, SIG_IGN);
+ // With more than one -l, repeatedly act as server.
+ if (do_listen > 1 && vfork()) {
+ /* parent */
+ // This is a bit weird as cleanup goes, since we wind up with no
+ // stdin/stdout/stderr. But it's small and shouldn't hurt anything.
+ // We check for cfd == 0 above.
+ logmode = LOGMODE_NONE;
+ close(0);
+ close(1);
+ close(2);
+ goto accept_again;
+ }
+ /* child (or main thread if no multiple -l) */
+ xmove_fd(cfd, 0);
+ xdup2(0, 1);
+ xdup2(0, 2);
+ USE_NC_EXTRA(BB_EXECVP(execparam[0], execparam);)
+ /* Don't print stuff or it will go over the wire.... */
+ _exit(127);
+ }
+
+ // Select loop copying stdin to cfd, and cfd to stdout.
+
+ FD_ZERO(&readfds);
+ FD_SET(cfd, &readfds);
+ FD_SET(STDIN_FILENO, &readfds);
+
+ for (;;) {
+ int fd;
+ int ofd;
+ int nread;
+
+ testfds = readfds;
+
+ if (select(FD_SETSIZE, &testfds, NULL, NULL, NULL) < 0)
+ bb_perror_msg_and_die("select");
+
+#define iobuf bb_common_bufsiz1
+ for (fd = 0; fd < FD_SETSIZE; fd++) {
+ if (FD_ISSET(fd, &testfds)) {
+ nread = safe_read(fd, iobuf, sizeof(iobuf));
+ if (fd == cfd) {
+ if (nread < 1)
+ exit(EXIT_SUCCESS);
+ ofd = STDOUT_FILENO;
+ } else {
+ if (nread<1) {
+ // Close outgoing half-connection so they get EOF, but
+ // leave incoming alone so we can see response.
+ shutdown(cfd, 1);
+ FD_CLR(STDIN_FILENO, &readfds);
+ }
+ ofd = cfd;
+ }
+ xwrite(ofd, iobuf, nread);
+ if (delay > 0) sleep(delay);
+ }
+ }
+ }
+}
+#endif
diff --git a/networking/nc_bloaty.c b/networking/nc_bloaty.c
new file mode 100644
index 0000000..41db945
--- /dev/null
+++ b/networking/nc_bloaty.c
@@ -0,0 +1,832 @@
+/* Based on netcat 1.10 RELEASE 960320 written by hobbit@avian.org.
+ * Released into public domain by the author.
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* Author's comments from nc 1.10:
+ * =====================
+ * Netcat is entirely my own creation, although plenty of other code was used as
+ * examples. It is freely given away to the Internet community in the hope that
+ * it will be useful, with no restrictions except giving credit where it is due.
+ * No GPLs, Berkeley copyrights or any of that nonsense. The author assumes NO
+ * responsibility for how anyone uses it. If netcat makes you rich somehow and
+ * you're feeling generous, mail me a check. If you are affiliated in any way
+ * with Microsoft Network, get a life. Always ski in control. Comments,
+ * questions, and patches to hobbit@avian.org.
+ * ...
+ * Netcat and the associated package is a product of Avian Research, and is freely
+ * available in full source form with no restrictions save an obligation to give
+ * credit where due.
+ * ...
+ * A damn useful little "backend" utility begun 950915 or thereabouts,
+ * as *Hobbit*'s first real stab at some sockets programming. Something that
+ * should have and indeed may have existed ten years ago, but never became a
+ * standard Unix utility. IMHO, "nc" could take its place right next to cat,
+ * cp, rm, mv, dd, ls, and all those other cryptic and Unix-like things.
+ * =====================
+ *
+ * Much of author's comments are still retained in the code.
+ *
+ * Functionality removed (rationale):
+ * - miltiple-port ranges, randomized port scanning (use nmap)
+ * - telnet support (use telnet)
+ * - source routing
+ * - multiple DNS checks
+ * Functionalty which is different from nc 1.10:
+ * - Prog in '-e prog' can have prog's parameters and options.
+ * Because of this -e option must be last.
+ * - nc doesn't redirect stderr to the network socket for the -e prog.
+ * - numeric addresses are printed in (), not [] (IPv6 looks better),
+ * port numbers are inside (): (1.2.3.4:5678)
+ * - network read errors are reported on verbose levels > 1
+ * (nc 1.10 treats them as EOF)
+ * - TCP connects from wrong ip/ports (if peer ip:port is specified
+ * on the command line, but accept() says that it came from different addr)
+ * are closed, but nc doesn't exit - continues to listen/accept.
+ */
+
+/* done in nc.c: #include "libbb.h" */
+
+enum {
+ SLEAZE_PORT = 31337, /* for UDP-scan RTT trick, change if ya want */
+ BIGSIZ = 8192, /* big buffers */
+
+ netfd = 3,
+ ofd = 4,
+};
+
+struct globals {
+ /* global cmd flags: */
+ unsigned o_verbose;
+ unsigned o_wait;
+#if ENABLE_NC_EXTRA
+ unsigned o_interval;
+#endif
+
+ /*int netfd;*/
+ /*int ofd;*/ /* hexdump output fd */
+#if ENABLE_LFS
+#define SENT_N_RECV_M "sent %llu, rcvd %llu\n"
+ unsigned long long wrote_out; /* total stdout bytes */
+ unsigned long long wrote_net; /* total net bytes */
+#else
+#define SENT_N_RECV_M "sent %u, rcvd %u\n"
+ unsigned wrote_out; /* total stdout bytes */
+ unsigned wrote_net; /* total net bytes */
+#endif
+ /* ouraddr is never NULL and goes through three states as we progress:
+ 1 - local address before bind (IP/port possibly zero)
+ 2 - local address after bind (port is nonzero)
+ 3 - local address after connect??/recv/accept (IP and port are nonzero) */
+ struct len_and_sockaddr *ouraddr;
+ /* themaddr is NULL if no peer hostname[:port] specified on command line */
+ struct len_and_sockaddr *themaddr;
+ /* remend is set after connect/recv/accept to the actual ip:port of peer */
+ struct len_and_sockaddr remend;
+
+ jmp_buf jbuf; /* timer crud */
+
+ /* will malloc up the following globals: */
+ fd_set ding1; /* for select loop */
+ fd_set ding2;
+ char bigbuf_in[BIGSIZ]; /* data buffers */
+ char bigbuf_net[BIGSIZ];
+};
+
+#define G (*ptr_to_globals)
+#define wrote_out (G.wrote_out )
+#define wrote_net (G.wrote_net )
+#define ouraddr (G.ouraddr )
+#define themaddr (G.themaddr )
+#define remend (G.remend )
+#define jbuf (G.jbuf )
+#define ding1 (G.ding1 )
+#define ding2 (G.ding2 )
+#define bigbuf_in (G.bigbuf_in )
+#define bigbuf_net (G.bigbuf_net)
+#define o_verbose (G.o_verbose )
+#define o_wait (G.o_wait )
+#if ENABLE_NC_EXTRA
+#define o_interval (G.o_interval)
+#else
+#define o_interval 0
+#endif
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+} while (0)
+
+
+/* Must match getopt32 call! */
+enum {
+ OPT_h = (1 << 0),
+ OPT_n = (1 << 1),
+ OPT_p = (1 << 2),
+ OPT_s = (1 << 3),
+ OPT_u = (1 << 4),
+ OPT_v = (1 << 5),
+ OPT_w = (1 << 6),
+ OPT_l = (1 << 7) * ENABLE_NC_SERVER,
+ OPT_i = (1 << (7+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+ OPT_o = (1 << (8+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+ OPT_z = (1 << (9+ENABLE_NC_SERVER)) * ENABLE_NC_EXTRA,
+};
+
+#define o_nflag (option_mask32 & OPT_n)
+#define o_udpmode (option_mask32 & OPT_u)
+#if ENABLE_NC_SERVER
+#define o_listen (option_mask32 & OPT_l)
+#else
+#define o_listen 0
+#endif
+#if ENABLE_NC_EXTRA
+#define o_ofile (option_mask32 & OPT_o)
+#define o_zero (option_mask32 & OPT_z)
+#else
+#define o_ofile 0
+#define o_zero 0
+#endif
+
+/* Debug: squirt whatever message and sleep a bit so we can see it go by. */
+/* Beware: writes to stdOUT... */
+#if 0
+#define Debug(...) do { printf(__VA_ARGS__); printf("\n"); fflush(stdout); sleep(1); } while (0)
+#else
+#define Debug(...) do { } while (0)
+#endif
+
+#define holler_error(...) do { if (o_verbose) bb_error_msg(__VA_ARGS__); } while (0)
+#define holler_perror(...) do { if (o_verbose) bb_perror_msg(__VA_ARGS__); } while (0)
+
+/* catch: no-brainer interrupt handler */
+static void catch(int sig)
+{
+ if (o_verbose > 1) /* normally we don't care */
+ fprintf(stderr, SENT_N_RECV_M, wrote_net, wrote_out);
+ fprintf(stderr, "punt!\n");
+ kill_myself_with_sig(sig);
+}
+
+/* unarm */
+static void unarm(void)
+{
+ signal(SIGALRM, SIG_IGN);
+ alarm(0);
+}
+
+/* timeout and other signal handling cruft */
+static void tmtravel(int sig UNUSED_PARAM)
+{
+ unarm();
+ longjmp(jbuf, 1);
+}
+
+/* arm: set the timer. */
+static void arm(unsigned secs)
+{
+ signal(SIGALRM, tmtravel);
+ alarm(secs);
+}
+
+/* findline:
+ find the next newline in a buffer; return inclusive size of that "line",
+ or the entire buffer size, so the caller knows how much to then write().
+ Not distinguishing \n vs \r\n for the nonce; it just works as is... */
+static unsigned findline(char *buf, unsigned siz)
+{
+ char * p;
+ int x;
+ if (!buf) /* various sanity checks... */
+ return 0;
+ if (siz > BIGSIZ)
+ return 0;
+ x = siz;
+ for (p = buf; x > 0; x--) {
+ if (*p == '\n') {
+ x = (int) (p - buf);
+ x++; /* 'sokay if it points just past the end! */
+Debug("findline returning %d", x);
+ return x;
+ }
+ p++;
+ } /* for */
+Debug("findline returning whole thing: %d", siz);
+ return siz;
+} /* findline */
+
+/* doexec:
+ fiddle all the file descriptors around, and hand off to another prog. Sort
+ of like a one-off "poor man's inetd". This is the only section of code
+ that would be security-critical, which is why it's ifdefed out by default.
+ Use at your own hairy risk; if you leave shells lying around behind open
+ listening ports you deserve to lose!! */
+static int doexec(char **proggie) NORETURN;
+static int doexec(char **proggie)
+{
+ xmove_fd(netfd, 0);
+ dup2(0, 1);
+ /* dup2(0, 2); - do we *really* want this? NO!
+ * exec'ed prog can do it yourself, if needed */
+ execvp(proggie[0], proggie);
+ bb_perror_msg_and_die("exec");
+}
+
+/* connect_w_timeout:
+ return an fd for one of
+ an open outbound TCP connection, a UDP stub-socket thingie, or
+ an unconnected TCP or UDP socket to listen on.
+ Examines various global o_blah flags to figure out what to do.
+ lad can be NULL, then socket is not bound to any local ip[:port] */
+static int connect_w_timeout(int fd)
+{
+ int rr;
+
+ /* wrap connect inside a timer, and hit it */
+ arm(o_wait);
+ if (setjmp(jbuf) == 0) {
+ rr = connect(fd, &themaddr->u.sa, themaddr->len);
+ unarm();
+ } else { /* setjmp: connect failed... */
+ rr = -1;
+ errno = ETIMEDOUT; /* fake it */
+ }
+ return rr;
+}
+
+/* dolisten:
+ listens for
+ incoming and returns an open connection *from* someplace. If we were
+ given host/port args, any connections from elsewhere are rejected. This
+ in conjunction with local-address binding should limit things nicely... */
+static void dolisten(void)
+{
+ int rr;
+
+ if (!o_udpmode)
+ xlisten(netfd, 1); /* TCP: gotta listen() before we can get */
+
+ /* Various things that follow temporarily trash bigbuf_net, which might contain
+ a copy of any recvfrom()ed packet, but we'll read() another copy later. */
+
+ /* I can't believe I have to do all this to get my own goddamn bound address
+ and port number. It should just get filled in during bind() or something.
+ All this is only useful if we didn't say -p for listening, since if we
+ said -p we *know* what port we're listening on. At any rate we won't bother
+ with it all unless we wanted to see it, although listening quietly on a
+ random unknown port is probably not very useful without "netstat". */
+ if (o_verbose) {
+ char *addr;
+ rr = getsockname(netfd, &ouraddr->u.sa, &ouraddr->len);
+ if (rr < 0)
+ bb_perror_msg_and_die("getsockname after bind");
+ addr = xmalloc_sockaddr2dotted(&ouraddr->u.sa);
+ fprintf(stderr, "listening on %s ...\n", addr);
+ free(addr);
+ }
+
+ if (o_udpmode) {
+ /* UDP is a speeeeecial case -- we have to do I/O *and* get the calling
+ party's particulars all at once, listen() and accept() don't apply.
+ At least in the BSD universe, however, recvfrom/PEEK is enough to tell
+ us something came in, and we can set things up so straight read/write
+ actually does work after all. Yow. YMMV on strange platforms! */
+
+ /* I'm not completely clear on how this works -- BSD seems to make UDP
+ just magically work in a connect()ed context, but we'll undoubtedly run
+ into systems this deal doesn't work on. For now, we apparently have to
+ issue a connect() on our just-tickled socket so we can write() back.
+ Again, why the fuck doesn't it just get filled in and taken care of?!
+ This hack is anything but optimal. Basically, if you want your listener
+ to also be able to send data back, you need this connect() line, which
+ also has the side effect that now anything from a different source or even a
+ different port on the other end won't show up and will cause ICMP errors.
+ I guess that's what they meant by "connect".
+ Let's try to remember what the "U" is *really* for, eh? */
+
+ /* If peer address is specified, connect to it */
+ remend.len = LSA_SIZEOF_SA;
+ if (themaddr) {
+ remend = *themaddr;
+ xconnect(netfd, &themaddr->u.sa, themaddr->len);
+ }
+ /* peek first packet and remember peer addr */
+ arm(o_wait); /* might as well timeout this, too */
+ if (setjmp(jbuf) == 0) { /* do timeout for initial connect */
+ /* (*ouraddr) is prefilled with "default" address */
+ /* and here we block... */
+ rr = recv_from_to(netfd, NULL, 0, MSG_PEEK, /*was bigbuf_net, BIGSIZ*/
+ &remend.u.sa, &ouraddr->u.sa, ouraddr->len);
+ if (rr < 0)
+ bb_perror_msg_and_die("recvfrom");
+ unarm();
+ } else
+ bb_error_msg_and_die("timeout");
+/* Now we learned *to which IP* peer has connected, and we want to anchor
+our socket on it, so that our outbound packets will have correct local IP.
+Unfortunately, bind() on already bound socket will fail now (EINVAL):
+ xbind(netfd, &ouraddr->u.sa, ouraddr->len);
+Need to read the packet, save data, close this socket and
+create new one, and bind() it. TODO */
+ if (!themaddr)
+ xconnect(netfd, &remend.u.sa, ouraddr->len);
+ } else {
+ /* TCP */
+ arm(o_wait); /* wrap this in a timer, too; 0 = forever */
+ if (setjmp(jbuf) == 0) {
+ again:
+ remend.len = LSA_SIZEOF_SA;
+ rr = accept(netfd, &remend.u.sa, &remend.len);
+ if (rr < 0)
+ bb_perror_msg_and_die("accept");
+ if (themaddr && memcmp(&remend.u.sa, &themaddr->u.sa, remend.len) != 0) {
+ /* nc 1.10 bails out instead, and its error message
+ * is not suppressed by o_verbose */
+ if (o_verbose) {
+ char *remaddr = xmalloc_sockaddr2dotted(&remend.u.sa);
+ bb_error_msg("connect from wrong ip/port %s ignored", remaddr);
+ free(remaddr);
+ }
+ close(rr);
+ goto again;
+ }
+ unarm();
+ } else
+ bb_error_msg_and_die("timeout");
+ xmove_fd(rr, netfd); /* dump the old socket, here's our new one */
+ /* find out what address the connection was *to* on our end, in case we're
+ doing a listen-on-any on a multihomed machine. This allows one to
+ offer different services via different alias addresses, such as the
+ "virtual web site" hack. */
+ rr = getsockname(netfd, &ouraddr->u.sa, &ouraddr->len);
+ if (rr < 0)
+ bb_perror_msg_and_die("getsockname after accept");
+ }
+
+ if (o_verbose) {
+ char *lcladdr, *remaddr, *remhostname;
+
+#if ENABLE_NC_EXTRA && defined(IP_OPTIONS)
+ /* If we can, look for any IP options. Useful for testing the receiving end of
+ such things, and is a good exercise in dealing with it. We do this before
+ the connect message, to ensure that the connect msg is uniformly the LAST
+ thing to emerge after all the intervening crud. Doesn't work for UDP on
+ any machines I've tested, but feel free to surprise me. */
+ char optbuf[40];
+ socklen_t x = sizeof(optbuf);
+
+ rr = getsockopt(netfd, IPPROTO_IP, IP_OPTIONS, optbuf, &x);
+ if (rr < 0)
+ bb_perror_msg("getsockopt failed");
+ else if (x) { /* we've got options, lessee em... */
+ bin2hex(bigbuf_net, optbuf, x);
+ bigbuf_net[2*x] = '\0';
+ fprintf(stderr, "IP options: %s\n", bigbuf_net);
+ }
+#endif
+
+ /* now check out who it is. We don't care about mismatched DNS names here,
+ but any ADDR and PORT we specified had better fucking well match the caller.
+ Converting from addr to inet_ntoa and back again is a bit of a kludge, but
+ gethostpoop wants a string and there's much gnarlier code out there already,
+ so I don't feel bad.
+ The *real* question is why BFD sockets wasn't designed to allow listens for
+ connections *from* specific hosts/ports, instead of requiring the caller to
+ accept the connection and then reject undesireable ones by closing.
+ In other words, we need a TCP MSG_PEEK. */
+ /* bbox: removed most of it */
+ lcladdr = xmalloc_sockaddr2dotted(&ouraddr->u.sa);
+ remaddr = xmalloc_sockaddr2dotted(&remend.u.sa);
+ remhostname = o_nflag ? remaddr : xmalloc_sockaddr2host(&remend.u.sa);
+ fprintf(stderr, "connect to %s from %s (%s)\n",
+ lcladdr, remhostname, remaddr);
+ free(lcladdr);
+ free(remaddr);
+ if (!o_nflag)
+ free(remhostname);
+ }
+}
+
+/* udptest:
+ fire a couple of packets at a UDP target port, just to see if it's really
+ there. On BSD kernels, ICMP host/port-unreachable errors get delivered to
+ our socket as ECONNREFUSED write errors. On SV kernels, we lose; we'll have
+ to collect and analyze raw ICMP ourselves a la satan's probe_udp_ports
+ backend. Guess where one could swipe the appropriate code from...
+
+ Use the time delay between writes if given, otherwise use the "tcp ping"
+ trick for getting the RTT. [I got that idea from pluvius, and warped it.]
+ Return either the original fd, or clean up and return -1. */
+#if ENABLE_NC_EXTRA
+static int udptest(void)
+{
+ int rr;
+
+ rr = write(netfd, bigbuf_in, 1);
+ if (rr != 1)
+ bb_perror_msg("udptest first write");
+
+ if (o_wait)
+ sleep(o_wait); // can be interrupted! while (t) nanosleep(&t)?
+ else {
+ /* use the tcp-ping trick: try connecting to a normally refused port, which
+ causes us to block for the time that SYN gets there and RST gets back.
+ Not completely reliable, but it *does* mostly work. */
+ /* Set a temporary connect timeout, so packet filtration doesnt cause
+ us to hang forever, and hit it */
+ o_wait = 5; /* enough that we'll notice?? */
+ rr = xsocket(ouraddr->u.sa.sa_family, SOCK_STREAM, 0);
+ set_nport(themaddr, htons(SLEAZE_PORT));
+ connect_w_timeout(rr);
+ /* don't need to restore themaddr's port, it's not used anymore */
+ close(rr);
+ o_wait = 0; /* restore */
+ }
+
+ rr = write(netfd, bigbuf_in, 1);
+ return (rr != 1); /* if rr == 1, return 0 (success) */
+}
+#else
+int udptest(void);
+#endif
+
+/* oprint:
+ Hexdump bytes shoveled either way to a running logfile, in the format:
+ D offset - - - - --- 16 bytes --- - - - - # .... ascii .....
+ where "which" sets the direction indicator, D:
+ 0 -- sent to network, or ">"
+ 1 -- rcvd and printed to stdout, or "<"
+ and "buf" and "n" are data-block and length. If the current block generates
+ a partial line, so be it; we *want* that lockstep indication of who sent
+ what when. Adapted from dgaudet's original example -- but must be ripping
+ *fast*, since we don't want to be too disk-bound... */
+#if ENABLE_NC_EXTRA
+static void oprint(int direction, unsigned char *p, unsigned bc)
+{
+ unsigned obc; /* current "global" offset */
+ unsigned x;
+ unsigned char *op; /* out hexdump ptr */
+ unsigned char *ap; /* out asc-dump ptr */
+ unsigned char stage[100];
+
+ if (bc == 0)
+ return;
+
+ obc = wrote_net; /* use the globals! */
+ if (direction == '<')
+ obc = wrote_out;
+ stage[0] = direction;
+ stage[59] = '#'; /* preload separator */
+ stage[60] = ' ';
+
+ do { /* for chunk-o-data ... */
+ x = 16;
+ if (bc < 16) {
+ /* memset(&stage[bc*3 + 11], ' ', 16*3 - bc*3); */
+ memset(&stage[11], ' ', 16*3);
+ x = bc;
+ }
+ sprintf((char *)&stage[1], " %8.8x ", obc); /* xxx: still slow? */
+ bc -= x; /* fix current count */
+ obc += x; /* fix current offset */
+ op = &stage[11]; /* where hex starts */
+ ap = &stage[61]; /* where ascii starts */
+
+ do { /* for line of dump, however long ... */
+ *op++ = 0x20 | bb_hexdigits_upcase[*p >> 4];
+ *op++ = 0x20 | bb_hexdigits_upcase[*p & 0x0f];
+ *op++ = ' ';
+ if ((*p > 31) && (*p < 127))
+ *ap = *p; /* printing */
+ else
+ *ap = '.'; /* nonprinting, loose def */
+ ap++;
+ p++;
+ } while (--x);
+ *ap++ = '\n'; /* finish the line */
+ xwrite(ofd, stage, ap - stage);
+ } while (bc);
+}
+#else
+void oprint(int direction, unsigned char *p, unsigned bc);
+#endif
+
+/* readwrite:
+ handle stdin/stdout/network I/O. Bwahaha!! -- the select loop from hell.
+ In this instance, return what might become our exit status. */
+static int readwrite(void)
+{
+ int rr;
+ char *zp = zp; /* gcc */ /* stdin buf ptr */
+ char *np = np; /* net-in buf ptr */
+ unsigned rzleft;
+ unsigned rnleft;
+ unsigned netretry; /* net-read retry counter */
+ unsigned wretry; /* net-write sanity counter */
+ unsigned wfirst; /* one-shot flag to skip first net read */
+
+ /* if you don't have all this FD_* macro hair in sys/types.h, you'll have to
+ either find it or do your own bit-bashing: *ding1 |= (1 << fd), etc... */
+ FD_SET(netfd, &ding1); /* global: the net is open */
+ netretry = 2;
+ wfirst = 0;
+ rzleft = rnleft = 0;
+ if (o_interval)
+ sleep(o_interval); /* pause *before* sending stuff, too */
+
+ errno = 0; /* clear from sleep, close, whatever */
+ /* and now the big ol' select shoveling loop ... */
+ while (FD_ISSET(netfd, &ding1)) { /* i.e. till the *net* closes! */
+ wretry = 8200; /* more than we'll ever hafta write */
+ if (wfirst) { /* any saved stdin buffer? */
+ wfirst = 0; /* clear flag for the duration */
+ goto shovel; /* and go handle it first */
+ }
+ ding2 = ding1; /* FD_COPY ain't portable... */
+ /* some systems, notably linux, crap into their select timers on return, so
+ we create a expendable copy and give *that* to select. */
+ if (o_wait) {
+ struct timeval tmp_timer;
+ tmp_timer.tv_sec = o_wait;
+ tmp_timer.tv_usec = 0;
+ /* highest possible fd is netfd (3) */
+ rr = select(netfd+1, &ding2, NULL, NULL, &tmp_timer);
+ } else
+ rr = select(netfd+1, &ding2, NULL, NULL, NULL);
+ if (rr < 0 && errno != EINTR) { /* might have gotten ^Zed, etc */
+ holler_perror("select");
+ close(netfd);
+ return 1;
+ }
+ /* if we have a timeout AND stdin is closed AND we haven't heard anything
+ from the net during that time, assume it's dead and close it too. */
+ if (rr == 0) {
+ if (!FD_ISSET(STDIN_FILENO, &ding1))
+ netretry--; /* we actually try a coupla times. */
+ if (!netretry) {
+ if (o_verbose > 1) /* normally we don't care */
+ fprintf(stderr, "net timeout\n");
+ close(netfd);
+ return 0; /* not an error! */
+ }
+ } /* select timeout */
+ /* xxx: should we check the exception fds too? The read fds seem to give
+ us the right info, and none of the examples I found bothered. */
+
+ /* Ding!! Something arrived, go check all the incoming hoppers, net first */
+ if (FD_ISSET(netfd, &ding2)) { /* net: ding! */
+ rr = read(netfd, bigbuf_net, BIGSIZ);
+ if (rr <= 0) {
+ if (rr < 0 && o_verbose > 1) {
+ /* nc 1.10 doesn't do this */
+ bb_perror_msg("net read");
+ }
+ FD_CLR(netfd, &ding1); /* net closed, we'll finish up... */
+ rzleft = 0; /* can't write anymore: broken pipe */
+ } else {
+ rnleft = rr;
+ np = bigbuf_net;
+ }
+Debug("got %d from the net, errno %d", rr, errno);
+ } /* net:ding */
+
+ /* if we're in "slowly" mode there's probably still stuff in the stdin
+ buffer, so don't read unless we really need MORE INPUT! MORE INPUT! */
+ if (rzleft)
+ goto shovel;
+
+ /* okay, suck more stdin */
+ if (FD_ISSET(STDIN_FILENO, &ding2)) { /* stdin: ding! */
+ rr = read(STDIN_FILENO, bigbuf_in, BIGSIZ);
+ /* Considered making reads here smaller for UDP mode, but 8192-byte
+ mobygrams are kinda fun and exercise the reassembler. */
+ if (rr <= 0) { /* at end, or fukt, or ... */
+ FD_CLR(STDIN_FILENO, &ding1); /* disable and close stdin */
+ close(0);
+ } else {
+ rzleft = rr;
+ zp = bigbuf_in;
+ }
+ } /* stdin:ding */
+ shovel:
+ /* now that we've dingdonged all our thingdings, send off the results.
+ Geez, why does this look an awful lot like the big loop in "rsh"? ...
+ not sure if the order of this matters, but write net -> stdout first. */
+
+ /* sanity check. Works because they're both unsigned... */
+ if ((rzleft > 8200) || (rnleft > 8200)) {
+ holler_error("bogus buffers: %u, %u", rzleft, rnleft);
+ rzleft = rnleft = 0;
+ }
+ /* net write retries sometimes happen on UDP connections */
+ if (!wretry) { /* is something hung? */
+ holler_error("too many output retries");
+ return 1;
+ }
+ if (rnleft) {
+ rr = write(STDOUT_FILENO, np, rnleft);
+ if (rr > 0) {
+ if (o_ofile) /* log the stdout */
+ oprint('<', (unsigned char *)np, rr);
+ np += rr; /* fix up ptrs and whatnot */
+ rnleft -= rr; /* will get sanity-checked above */
+ wrote_out += rr; /* global count */
+ }
+Debug("wrote %d to stdout, errno %d", rr, errno);
+ } /* rnleft */
+ if (rzleft) {
+ if (o_interval) /* in "slowly" mode ?? */
+ rr = findline(zp, rzleft);
+ else
+ rr = rzleft;
+ rr = write(netfd, zp, rr); /* one line, or the whole buffer */
+ if (rr > 0) {
+ if (o_ofile) /* log what got sent */
+ oprint('>', (unsigned char *)zp, rr);
+ zp += rr;
+ rzleft -= rr;
+ wrote_net += rr; /* global count */
+ }
+Debug("wrote %d to net, errno %d", rr, errno);
+ } /* rzleft */
+ if (o_interval) { /* cycle between slow lines, or ... */
+ sleep(o_interval);
+ errno = 0; /* clear from sleep */
+ continue; /* ...with hairy select loop... */
+ }
+ if ((rzleft) || (rnleft)) { /* shovel that shit till they ain't */
+ wretry--; /* none left, and get another load */
+ goto shovel;
+ }
+ } /* while ding1:netfd is open */
+
+ /* XXX: maybe want a more graceful shutdown() here, or screw around with
+ linger times?? I suspect that I don't need to since I'm always doing
+ blocking reads and writes and my own manual "last ditch" efforts to read
+ the net again after a timeout. I haven't seen any screwups yet, but it's
+ not like my test network is particularly busy... */
+ close(netfd);
+ return 0;
+} /* readwrite */
+
+/* main: now we pull it all together... */
+int nc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nc_main(int argc, char **argv)
+{
+ char *str_p, *str_s;
+ USE_NC_EXTRA(char *str_i, *str_o;)
+ char *themdotted = themdotted; /* gcc */
+ char **proggie;
+ int x;
+ unsigned o_lport = 0;
+
+ INIT_G();
+
+ /* catch a signal or two for cleanup */
+ bb_signals(0
+ + (1 << SIGINT)
+ + (1 << SIGQUIT)
+ + (1 << SIGTERM)
+ , catch);
+ /* and suppress others... */
+ bb_signals(0
+#ifdef SIGURG
+ + (1 << SIGURG)
+#endif
+ + (1 << SIGPIPE) /* important! */
+ , SIG_IGN);
+
+ proggie = argv;
+ while (*++proggie) {
+ if (strcmp(*proggie, "-e") == 0) {
+ *proggie = NULL;
+ argc = proggie - argv;
+ proggie++;
+ goto e_found;
+ }
+ }
+ proggie = NULL;
+ e_found:
+
+ // -g -G -t -r deleted, unimplemented -a deleted too
+ opt_complementary = "?2:vv:w+"; /* max 2 params; -v is a counter; -w N */
+ getopt32(argv, "hnp:s:uvw:" USE_NC_SERVER("l")
+ USE_NC_EXTRA("i:o:z"),
+ &str_p, &str_s, &o_wait
+ USE_NC_EXTRA(, &str_i, &str_o, &o_verbose));
+ argv += optind;
+#if ENABLE_NC_EXTRA
+ if (option_mask32 & OPT_i) /* line-interval time */
+ o_interval = xatou_range(str_i, 1, 0xffff);
+#endif
+ //if (option_mask32 & OPT_l) /* listen mode */
+ //if (option_mask32 & OPT_n) /* numeric-only, no DNS lookups */
+ //if (option_mask32 & OPT_o) /* hexdump log */
+ if (option_mask32 & OPT_p) { /* local source port */
+ o_lport = bb_lookup_port(str_p, o_udpmode ? "udp" : "tcp", 0);
+ if (!o_lport)
+ bb_error_msg_and_die("bad local port '%s'", str_p);
+ }
+ //if (option_mask32 & OPT_r) /* randomize various things */
+ //if (option_mask32 & OPT_u) /* use UDP */
+ //if (option_mask32 & OPT_v) /* verbose */
+ //if (option_mask32 & OPT_w) /* wait time */
+ //if (option_mask32 & OPT_z) /* little or no data xfer */
+
+ /* We manage our fd's so that they are never 0,1,2 */
+ /*bb_sanitize_stdio(); - not needed */
+
+ if (argv[0]) {
+ themaddr = xhost2sockaddr(argv[0],
+ argv[1]
+ ? bb_lookup_port(argv[1], o_udpmode ? "udp" : "tcp", 0)
+ : 0);
+ }
+
+ /* create & bind network socket */
+ x = (o_udpmode ? SOCK_DGRAM : SOCK_STREAM);
+ if (option_mask32 & OPT_s) { /* local address */
+ /* if o_lport is still 0, then we will use random port */
+ ouraddr = xhost2sockaddr(str_s, o_lport);
+#ifdef BLOAT
+ /* prevent spurious "UDP listen needs !0 port" */
+ o_lport = get_nport(ouraddr);
+ o_lport = ntohs(o_lport);
+#endif
+ x = xsocket(ouraddr->u.sa.sa_family, x, 0);
+ } else {
+ /* We try IPv6, then IPv4, unless addr family is
+ * implicitly set by way of remote addr/port spec */
+ x = xsocket_type(&ouraddr,
+ (themaddr ? themaddr->u.sa.sa_family : AF_UNSPEC),
+ x);
+ if (o_lport)
+ set_nport(ouraddr, htons(o_lport));
+ }
+ xmove_fd(x, netfd);
+ setsockopt_reuseaddr(netfd);
+ if (o_udpmode)
+ socket_want_pktinfo(netfd);
+ xbind(netfd, &ouraddr->u.sa, ouraddr->len);
+#if 0
+ setsockopt(netfd, SOL_SOCKET, SO_RCVBUF, &o_rcvbuf, sizeof o_rcvbuf);
+ setsockopt(netfd, SOL_SOCKET, SO_SNDBUF, &o_sndbuf, sizeof o_sndbuf);
+#endif
+
+#ifdef BLOAT
+ if (OPT_l && (option_mask32 & (OPT_u|OPT_l)) == (OPT_u|OPT_l)) {
+ /* apparently UDP can listen ON "port 0",
+ but that's not useful */
+ if (!o_lport)
+ bb_error_msg_and_die("UDP listen needs nonzero -p port");
+ }
+#endif
+
+ FD_SET(STDIN_FILENO, &ding1); /* stdin *is* initially open */
+ if (proggie) {
+ close(0); /* won't need stdin */
+ option_mask32 &= ~OPT_o; /* -o with -e is meaningless! */
+ }
+#if ENABLE_NC_EXTRA
+ if (o_ofile)
+ xmove_fd(xopen(str_o, O_WRONLY|O_CREAT|O_TRUNC), ofd);
+#endif
+
+ if (o_listen) {
+ dolisten();
+ /* dolisten does its own connect reporting */
+ if (proggie) /* -e given? */
+ doexec(proggie);
+ x = readwrite(); /* it even works with UDP! */
+ } else {
+ /* Outbound connects. Now we're more picky about args... */
+ if (!themaddr)
+ bb_error_msg_and_die("no destination");
+
+ remend = *themaddr;
+ if (o_verbose)
+ themdotted = xmalloc_sockaddr2dotted(&themaddr->u.sa);
+
+ x = connect_w_timeout(netfd);
+ if (o_zero && x == 0 && o_udpmode) /* if UDP scanning... */
+ x = udptest();
+ if (x == 0) { /* Yow, are we OPEN YET?! */
+ if (o_verbose)
+ fprintf(stderr, "%s (%s) open\n", argv[0], themdotted);
+ if (proggie) /* exec is valid for outbound, too */
+ doexec(proggie);
+ if (!o_zero)
+ x = readwrite();
+ } else { /* connect or udptest wasn't successful */
+ x = 1; /* exit status */
+ /* if we're scanning at a "one -v" verbosity level, don't print refusals.
+ Give it another -v if you want to see everything. */
+ if (o_verbose > 1 || (o_verbose && errno != ECONNREFUSED))
+ bb_perror_msg("%s (%s)", argv[0], themdotted);
+ }
+ }
+ if (o_verbose > 1) /* normally we don't care */
+ fprintf(stderr, SENT_N_RECV_M, wrote_net, wrote_out);
+ return x;
+}
diff --git a/networking/netstat.c b/networking/netstat.c
new file mode 100644
index 0000000..b246280
--- /dev/null
+++ b/networking/netstat.c
@@ -0,0 +1,712 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini netstat implementation(s) for busybox
+ * based in part on the netstat implementation from net-tools.
+ *
+ * Copyright (C) 2002 by Bart Visscher <magick@linux-fan.com>
+ *
+ * 2002-04-20
+ * IPV6 support added by Bart Visscher <magick@linux-fan.com>
+ *
+ * 2008-07-10
+ * optional '-p' flag support ported from net-tools by G. Somlo <somlo@cmu.edu>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "inet_common.h"
+
+#define NETSTAT_OPTS "laentuwx" \
+ USE_ROUTE( "r") \
+ USE_FEATURE_NETSTAT_WIDE("W") \
+ USE_FEATURE_NETSTAT_PRG( "p")
+
+enum {
+ OPTBIT_KEEP_OLD = 7,
+ USE_ROUTE( OPTBIT_ROUTE,)
+ USE_FEATURE_NETSTAT_WIDE(OPTBIT_WIDE ,)
+ USE_FEATURE_NETSTAT_PRG( OPTBIT_PRG ,)
+ OPT_sock_listen = 1 << 0, // l
+ OPT_sock_all = 1 << 1, // a
+ OPT_extended = 1 << 2, // e
+ OPT_noresolve = 1 << 3, // n
+ OPT_sock_tcp = 1 << 4, // t
+ OPT_sock_udp = 1 << 5, // u
+ OPT_sock_raw = 1 << 6, // w
+ OPT_sock_unix = 1 << 7, // x
+ OPT_route = USE_ROUTE( (1 << OPTBIT_ROUTE)) + 0, // r
+ OPT_wide = USE_FEATURE_NETSTAT_WIDE((1 << OPTBIT_WIDE )) + 0, // W
+ OPT_prg = USE_FEATURE_NETSTAT_PRG( (1 << OPTBIT_PRG )) + 0, // p
+};
+
+#define NETSTAT_CONNECTED 0x01
+#define NETSTAT_LISTENING 0x02
+#define NETSTAT_NUMERIC 0x04
+/* Must match getopt32 option string */
+#define NETSTAT_TCP 0x10
+#define NETSTAT_UDP 0x20
+#define NETSTAT_RAW 0x40
+#define NETSTAT_UNIX 0x80
+#define NETSTAT_ALLPROTO (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW|NETSTAT_UNIX)
+
+
+enum {
+ TCP_ESTABLISHED = 1,
+ TCP_SYN_SENT,
+ TCP_SYN_RECV,
+ TCP_FIN_WAIT1,
+ TCP_FIN_WAIT2,
+ TCP_TIME_WAIT,
+ TCP_CLOSE,
+ TCP_CLOSE_WAIT,
+ TCP_LAST_ACK,
+ TCP_LISTEN,
+ TCP_CLOSING, /* now a valid state */
+};
+
+static const char *const tcp_state[] = {
+ "",
+ "ESTABLISHED",
+ "SYN_SENT",
+ "SYN_RECV",
+ "FIN_WAIT1",
+ "FIN_WAIT2",
+ "TIME_WAIT",
+ "CLOSE",
+ "CLOSE_WAIT",
+ "LAST_ACK",
+ "LISTEN",
+ "CLOSING"
+};
+
+typedef enum {
+ SS_FREE = 0, /* not allocated */
+ SS_UNCONNECTED, /* unconnected to any socket */
+ SS_CONNECTING, /* in process of connecting */
+ SS_CONNECTED, /* connected to socket */
+ SS_DISCONNECTING /* in process of disconnecting */
+} socket_state;
+
+#define SO_ACCEPTCON (1<<16) /* performed a listen */
+#define SO_WAITDATA (1<<17) /* wait data to read */
+#define SO_NOSPACE (1<<18) /* no space to write */
+
+/* Standard printout size */
+#define PRINT_IP_MAX_SIZE 23
+#define PRINT_NET_CONN "%s %6ld %6ld %-23s %-23s %-12s"
+#define PRINT_NET_CONN_HEADER "\nProto Recv-Q Send-Q %-23s %-23s State "
+
+/* When there are IPv6 connections the IPv6 addresses will be
+ * truncated to none-recognition. The '-W' option makes the
+ * address columns wide enough to accomodate for longest possible
+ * IPv6 addresses, i.e. addresses of the form
+ * xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd
+ */
+#define PRINT_IP_MAX_SIZE_WIDE 51 /* INET6_ADDRSTRLEN + 5 for the port number */
+#define PRINT_NET_CONN_WIDE "%s %6ld %6ld %-51s %-51s %-12s"
+#define PRINT_NET_CONN_HEADER_WIDE "\nProto Recv-Q Send-Q %-51s %-51s State "
+
+
+#define PROGNAME_WIDTH 20
+#define PROGNAME_WIDTH_STR "20"
+/* PROGNAME_WIDTH chars: 12345678901234567890 */
+#define PROGNAME_BANNER "PID/Program name "
+
+struct prg_node {
+ struct prg_node *next;
+ long inode;
+ char name[PROGNAME_WIDTH];
+};
+
+#define PRG_HASH_SIZE 211
+
+
+struct globals {
+ const char *net_conn_line;
+ smallint flags;
+#if ENABLE_FEATURE_NETSTAT_PRG
+ smallint prg_cache_loaded;
+ struct prg_node *prg_hash[PRG_HASH_SIZE];
+#endif
+};
+#define G (*ptr_to_globals)
+#define flags (G.flags )
+#define net_conn_line (G.net_conn_line )
+#define prg_hash (G.prg_hash )
+#define prg_cache_loaded (G.prg_cache_loaded)
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; \
+ net_conn_line = PRINT_NET_CONN; \
+} while (0)
+
+
+#if ENABLE_FEATURE_NETSTAT_PRG
+
+/* Deliberately truncating long to unsigned *int* */
+#define PRG_HASHIT(x) ((unsigned)(x) % PRG_HASH_SIZE)
+
+#define print_progname_banner() do { \
+ if (option_mask32 & OPT_prg) printf(PROGNAME_BANNER); \
+} while (0)
+
+static void prg_cache_add(long inode, char *name)
+{
+ unsigned hi = PRG_HASHIT(inode);
+ struct prg_node **pnp, *pn;
+
+ prg_cache_loaded = 2;
+ for (pnp = prg_hash + hi; (pn = *pnp) != NULL; pnp = &pn->next) {
+ if (pn->inode == inode) {
+ /* Some warning should be appropriate here
+ as we got multiple processes for one i-node */
+ return;
+ }
+ }
+ *pnp = xzalloc(sizeof(struct prg_node));
+ pn = *pnp;
+ pn->inode = inode;
+ safe_strncpy(pn->name, name, PROGNAME_WIDTH);
+}
+
+static const char *prg_cache_get(long inode)
+{
+ unsigned hi = PRG_HASHIT(inode);
+ struct prg_node *pn;
+
+ for (pn = prg_hash[hi]; pn; pn = pn->next)
+ if (pn->inode == inode)
+ return pn->name;
+ return "-";
+}
+
+#if ENABLE_FEATURE_CLEAN_UP
+static void prg_cache_clear(void)
+{
+ struct prg_node **pnp, *pn;
+
+ for (pnp = prg_hash; pnp < prg_hash + PRG_HASH_SIZE; pnp++) {
+ while ((pn = *pnp) != NULL) {
+ *pnp = pn->next;
+ free(pn);
+ }
+ }
+}
+#else
+#define prg_cache_clear() ((void)0)
+#endif
+
+static long extract_socket_inode(const char *lname)
+{
+ long inode = -1;
+
+ if (strncmp(lname, "socket:[", sizeof("socket:[")-1) == 0) {
+ /* "socket:[12345]", extract the "12345" as inode */
+ inode = bb_strtol(lname + sizeof("socket:[")-1, (char**)&lname, 0);
+ if (*lname != ']')
+ inode = -1;
+ } else if (strncmp(lname, "[0000]:", sizeof("[0000]:")-1) == 0) {
+ /* "[0000]:12345", extract the "12345" as inode */
+ inode = bb_strtol(lname + sizeof("[0000]:")-1, NULL, 0);
+ if (errno) /* not NUL terminated? */
+ inode = -1;
+ }
+
+#if 0 /* bb_strtol returns all-ones bit pattern on ERANGE anyway */
+ if (errno == ERANGE)
+ inode = -1;
+#endif
+ return inode;
+}
+
+static int FAST_FUNC file_act(const char *fileName,
+ struct stat *statbuf UNUSED_PARAM,
+ void *userData,
+ int depth UNUSED_PARAM)
+{
+ char *linkname;
+ long inode;
+
+ linkname = xmalloc_readlink(fileName);
+ if (linkname != NULL) {
+ inode = extract_socket_inode(linkname);
+ free(linkname);
+ if (inode >= 0)
+ prg_cache_add(inode, (char *)userData);
+ }
+ return TRUE;
+}
+
+static int FAST_FUNC dir_act(const char *fileName,
+ struct stat *statbuf UNUSED_PARAM,
+ void *userData UNUSED_PARAM,
+ int depth)
+{
+ const char *shortName;
+ char *p, *q;
+ char cmdline_buf[512];
+ int i;
+
+ if (depth == 0) /* "/proc" itself */
+ return TRUE; /* continue looking one level below /proc */
+
+ shortName = fileName + sizeof("/proc/")-1; /* point after "/proc/" */
+ if (!isdigit(shortName[0])) /* skip /proc entries whic aren't processes */
+ return SKIP;
+
+ p = concat_path_file(fileName, "cmdline"); /* "/proc/PID/cmdline" */
+ i = open_read_close(p, cmdline_buf, sizeof(cmdline_buf) - 1);
+ free(p);
+ if (i < 0)
+ return FALSE;
+ cmdline_buf[i] = '\0';
+ q = concat_path_file(shortName, bb_basename(cmdline_buf)); /* "PID/argv0" */
+
+ /* go through all files in /proc/PID/fd */
+ p = concat_path_file(fileName, "fd");
+ i = recursive_action(p, ACTION_RECURSE | ACTION_QUIET,
+ file_act, NULL, (void *)q, 0);
+
+ free(p);
+ free(q);
+
+ if (!i)
+ return FALSE; /* signal permissions error to caller */
+
+ return SKIP; /* caller should not recurse further into this dir. */
+}
+
+static void prg_cache_load(void)
+{
+ int load_ok;
+
+ prg_cache_loaded = 1;
+ load_ok = recursive_action("/proc", ACTION_RECURSE | ACTION_QUIET,
+ NULL, dir_act, NULL, 0);
+ if (load_ok)
+ return;
+
+ if (prg_cache_loaded == 1)
+ bb_error_msg("can't scan /proc - are you root?");
+ else
+ bb_error_msg("showing only processes with your user ID");
+}
+
+#else
+
+#define prg_cache_clear() ((void)0)
+#define print_progname_banner() ((void)0)
+
+#endif //ENABLE_FEATURE_NETSTAT_PRG
+
+
+#if ENABLE_FEATURE_IPV6
+static void build_ipv6_addr(char* local_addr, struct sockaddr_in6* localaddr)
+{
+ char addr6[INET6_ADDRSTRLEN];
+ struct in6_addr in6;
+
+ sscanf(local_addr, "%08X%08X%08X%08X",
+ &in6.s6_addr32[0], &in6.s6_addr32[1],
+ &in6.s6_addr32[2], &in6.s6_addr32[3]);
+ inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6));
+ inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr->sin6_addr);
+
+ localaddr->sin6_family = AF_INET6;
+}
+#endif
+
+#if ENABLE_FEATURE_IPV6
+static void build_ipv4_addr(char* local_addr, struct sockaddr_in6* localaddr)
+#else
+static void build_ipv4_addr(char* local_addr, struct sockaddr_in* localaddr)
+#endif
+{
+ sscanf(local_addr, "%X",
+ &((struct sockaddr_in *) localaddr)->sin_addr.s_addr);
+ ((struct sockaddr *) localaddr)->sa_family = AF_INET;
+}
+
+static const char *get_sname(int port, const char *proto, int numeric)
+{
+ if (!port)
+ return "*";
+ if (!numeric) {
+ struct servent *se = getservbyport(port, proto);
+ if (se)
+ return se->s_name;
+ }
+ /* hummm, we may return static buffer here!! */
+ return itoa(ntohs(port));
+}
+
+static char *ip_port_str(struct sockaddr *addr, int port, const char *proto, int numeric)
+{
+ char *host, *host_port;
+
+ /* Code which used "*" for INADDR_ANY is removed: it's ambiguous
+ * in IPv6, while "0.0.0.0" is not. */
+
+ host = numeric ? xmalloc_sockaddr2dotted_noport(addr)
+ : xmalloc_sockaddr2host_noport(addr);
+
+ host_port = xasprintf("%s:%s", host, get_sname(htons(port), proto, numeric));
+ free(host);
+ return host_port;
+}
+
+struct inet_params {
+ int local_port, rem_port, state, uid;
+#if ENABLE_FEATURE_IPV6
+ struct sockaddr_in6 localaddr, remaddr;
+#else
+ struct sockaddr_in localaddr, remaddr;
+#endif
+ unsigned long rxq, txq, inode;
+};
+
+static int scan_inet_proc_line(struct inet_params *param, char *line)
+{
+ int num;
+ char local_addr[64], rem_addr[64];
+
+ num = sscanf(line,
+ "%*d: %64[0-9A-Fa-f]:%X "
+ "%64[0-9A-Fa-f]:%X %X "
+ "%lX:%lX %*X:%*X "
+ "%*X %d %*d %ld ",
+ local_addr, &param->local_port,
+ rem_addr, &param->rem_port, &param->state,
+ &param->txq, &param->rxq,
+ &param->uid, &param->inode);
+ if (num < 9) {
+ return 1; /* error */
+ }
+
+ if (strlen(local_addr) > 8) {
+#if ENABLE_FEATURE_IPV6
+ build_ipv6_addr(local_addr, &param->localaddr);
+ build_ipv6_addr(rem_addr, &param->remaddr);
+#endif
+ } else {
+ build_ipv4_addr(local_addr, &param->localaddr);
+ build_ipv4_addr(rem_addr, &param->remaddr);
+ }
+ return 0;
+}
+
+static void print_inet_line(struct inet_params *param,
+ const char *state_str, const char *proto, int is_connected)
+{
+ if ((is_connected && (flags & NETSTAT_CONNECTED))
+ || (!is_connected && (flags & NETSTAT_LISTENING))
+ ) {
+ char *l = ip_port_str(
+ (struct sockaddr *) &param->localaddr, param->local_port,
+ proto, flags & NETSTAT_NUMERIC);
+ char *r = ip_port_str(
+ (struct sockaddr *) &param->remaddr, param->rem_port,
+ proto, flags & NETSTAT_NUMERIC);
+ printf(net_conn_line,
+ proto, param->rxq, param->txq, l, r, state_str);
+#if ENABLE_FEATURE_NETSTAT_PRG
+ if (option_mask32 & OPT_prg)
+ printf("%."PROGNAME_WIDTH_STR"s", prg_cache_get(param->inode));
+#endif
+ bb_putchar('\n');
+ free(l);
+ free(r);
+ }
+}
+
+static int FAST_FUNC tcp_do_one(char *line)
+{
+ struct inet_params param;
+
+ if (scan_inet_proc_line(&param, line))
+ return 1;
+
+ print_inet_line(&param, tcp_state[param.state], "tcp", param.rem_port);
+ return 0;
+}
+
+#if ENABLE_FEATURE_IPV6
+# define notnull(A) ( \
+ ( (A.sin6_family == AF_INET6) \
+ && (A.sin6_addr.s6_addr32[0] | A.sin6_addr.s6_addr32[1] | \
+ A.sin6_addr.s6_addr32[2] | A.sin6_addr.s6_addr32[3]) \
+ ) || ( \
+ (A.sin6_family == AF_INET) \
+ && ((struct sockaddr_in*)&A)->sin_addr.s_addr \
+ ) \
+)
+#else
+# define notnull(A) (A.sin_addr.s_addr)
+#endif
+
+static int FAST_FUNC udp_do_one(char *line)
+{
+ int have_remaddr;
+ const char *state_str;
+ struct inet_params param;
+
+ if (scan_inet_proc_line(&param, line))
+ return 1;
+
+ state_str = "UNKNOWN";
+ switch (param.state) {
+ case TCP_ESTABLISHED:
+ state_str = "ESTABLISHED";
+ break;
+ case TCP_CLOSE:
+ state_str = "";
+ break;
+ }
+
+ have_remaddr = notnull(param.remaddr);
+ print_inet_line(&param, state_str, "udp", have_remaddr);
+ return 0;
+}
+
+static int FAST_FUNC raw_do_one(char *line)
+{
+ int have_remaddr;
+ struct inet_params param;
+
+ if (scan_inet_proc_line(&param, line))
+ return 1;
+
+ have_remaddr = notnull(param.remaddr);
+ print_inet_line(&param, itoa(param.state), "raw", have_remaddr);
+ return 0;
+}
+
+static int FAST_FUNC unix_do_one(char *line)
+{
+ unsigned long refcnt, proto, unix_flags;
+ unsigned long inode;
+ int type, state;
+ int num, path_ofs;
+ const char *ss_proto, *ss_state, *ss_type;
+ char ss_flags[32];
+
+ /* 2.6.15 may report lines like "... @/tmp/fam-user-^@^@^@^@^@^@^@..."
+ * Other users report long lines filled by NUL bytes.
+ * (those ^@ are NUL bytes too). We see them as empty lines. */
+ if (!line[0])
+ return 0;
+
+ path_ofs = 0; /* paranoia */
+ num = sscanf(line, "%*p: %lX %lX %lX %X %X %lu %n",
+ &refcnt, &proto, &unix_flags, &type, &state, &inode, &path_ofs);
+ if (num < 6) {
+ return 1; /* error */
+ }
+ if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) != (NETSTAT_LISTENING|NETSTAT_CONNECTED)) {
+ if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) {
+ if (!(flags & NETSTAT_LISTENING))
+ return 0;
+ } else {
+ if (!(flags & NETSTAT_CONNECTED))
+ return 0;
+ }
+ }
+
+ switch (proto) {
+ case 0:
+ ss_proto = "unix";
+ break;
+ default:
+ ss_proto = "??";
+ }
+
+ switch (type) {
+ case SOCK_STREAM:
+ ss_type = "STREAM";
+ break;
+ case SOCK_DGRAM:
+ ss_type = "DGRAM";
+ break;
+ case SOCK_RAW:
+ ss_type = "RAW";
+ break;
+ case SOCK_RDM:
+ ss_type = "RDM";
+ break;
+ case SOCK_SEQPACKET:
+ ss_type = "SEQPACKET";
+ break;
+ default:
+ ss_type = "UNKNOWN";
+ }
+
+ switch (state) {
+ case SS_FREE:
+ ss_state = "FREE";
+ break;
+ case SS_UNCONNECTED:
+ /*
+ * Unconnected sockets may be listening
+ * for something.
+ */
+ if (unix_flags & SO_ACCEPTCON) {
+ ss_state = "LISTENING";
+ } else {
+ ss_state = "";
+ }
+ break;
+ case SS_CONNECTING:
+ ss_state = "CONNECTING";
+ break;
+ case SS_CONNECTED:
+ ss_state = "CONNECTED";
+ break;
+ case SS_DISCONNECTING:
+ ss_state = "DISCONNECTING";
+ break;
+ default:
+ ss_state = "UNKNOWN";
+ }
+
+ strcpy(ss_flags, "[ ");
+ if (unix_flags & SO_ACCEPTCON)
+ strcat(ss_flags, "ACC ");
+ if (unix_flags & SO_WAITDATA)
+ strcat(ss_flags, "W ");
+ if (unix_flags & SO_NOSPACE)
+ strcat(ss_flags, "N ");
+ strcat(ss_flags, "]");
+
+ printf("%-5s %-6ld %-11s %-10s %-13s %6lu ",
+ ss_proto, refcnt, ss_flags, ss_type, ss_state, inode
+ );
+
+#if ENABLE_FEATURE_NETSTAT_PRG
+ if (option_mask32 & OPT_prg)
+ printf("%-"PROGNAME_WIDTH_STR"s", prg_cache_get(inode));
+#endif
+
+ /* TODO: currently we stop at first NUL byte. Is it a problem? */
+ line += path_ofs;
+ *strchrnul(line, '\n') = '\0';
+ while (*line)
+ fputc_printable(*line++, stdout);
+ bb_putchar('\n');
+ return 0;
+}
+
+static void do_info(const char *file, int FAST_FUNC (*proc)(char *))
+{
+ int lnr;
+ FILE *procinfo;
+ char *buffer;
+
+ /* _stdin is just to save "r" param */
+ procinfo = fopen_or_warn_stdin(file);
+ if (procinfo == NULL) {
+ return;
+ }
+ lnr = 0;
+ /* Why xmalloc_fgets_str? because it doesn't stop on NULs */
+ while ((buffer = xmalloc_fgets_str(procinfo, "\n")) != NULL) {
+ /* line 0 is skipped */
+ if (lnr && proc(buffer))
+ bb_error_msg("%s: bogus data on line %d", file, lnr + 1);
+ lnr++;
+ free(buffer);
+ }
+ fclose(procinfo);
+}
+
+int netstat_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int netstat_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *net_conn_line_header = PRINT_NET_CONN_HEADER;
+ unsigned opt;
+
+ INIT_G();
+
+ /* Option string must match NETSTAT_xxx constants */
+ opt = getopt32(argv, NETSTAT_OPTS);
+ if (opt & 0x1) { // -l
+ flags &= ~NETSTAT_CONNECTED;
+ flags |= NETSTAT_LISTENING;
+ }
+ if (opt & 0x2) flags |= NETSTAT_LISTENING | NETSTAT_CONNECTED; // -a
+ //if (opt & 0x4) // -e
+ if (opt & 0x8) flags |= NETSTAT_NUMERIC; // -n
+ //if (opt & 0x10) // -t: NETSTAT_TCP
+ //if (opt & 0x20) // -u: NETSTAT_UDP
+ //if (opt & 0x40) // -w: NETSTAT_RAW
+ //if (opt & 0x80) // -x: NETSTAT_UNIX
+ if (opt & OPT_route) { // -r
+#if ENABLE_ROUTE
+ bb_displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended));
+ return 0;
+#else
+ bb_show_usage();
+#endif
+ }
+ if (opt & OPT_wide) { // -W
+ net_conn_line = PRINT_NET_CONN_WIDE;
+ net_conn_line_header = PRINT_NET_CONN_HEADER_WIDE;
+ }
+#if ENABLE_FEATURE_NETSTAT_PRG
+ if (opt & OPT_prg) { // -p
+ prg_cache_load();
+ }
+#endif
+
+ opt &= NETSTAT_ALLPROTO;
+ if (opt) {
+ flags &= ~NETSTAT_ALLPROTO;
+ flags |= opt;
+ }
+ if (flags & (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW)) {
+ printf("Active Internet connections "); /* xxx */
+
+ if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) == (NETSTAT_LISTENING|NETSTAT_CONNECTED))
+ printf("(servers and established)");
+ else if (flags & NETSTAT_LISTENING)
+ printf("(only servers)");
+ else
+ printf("(w/o servers)");
+ printf(net_conn_line_header, "Local Address", "Foreign Address");
+ print_progname_banner();
+ bb_putchar('\n');
+ }
+ if (flags & NETSTAT_TCP) {
+ do_info("/proc/net/tcp", tcp_do_one);
+#if ENABLE_FEATURE_IPV6
+ do_info("/proc/net/tcp6", tcp_do_one);
+#endif
+ }
+ if (flags & NETSTAT_UDP) {
+ do_info("/proc/net/udp", udp_do_one);
+#if ENABLE_FEATURE_IPV6
+ do_info("/proc/net/udp6", udp_do_one);
+#endif
+ }
+ if (flags & NETSTAT_RAW) {
+ do_info("/proc/net/raw", raw_do_one);
+#if ENABLE_FEATURE_IPV6
+ do_info("/proc/net/raw6", raw_do_one);
+#endif
+ }
+ if (flags & NETSTAT_UNIX) {
+ printf("Active UNIX domain sockets ");
+ if ((flags & (NETSTAT_LISTENING|NETSTAT_CONNECTED)) == (NETSTAT_LISTENING|NETSTAT_CONNECTED))
+ printf("(servers and established)");
+ else if (flags & NETSTAT_LISTENING)
+ printf("(only servers)");
+ else
+ printf("(w/o servers)");
+ printf("\nProto RefCnt Flags Type State I-Node ");
+ print_progname_banner();
+ printf("Path\n");
+ do_info("/proc/net/unix", unix_do_one);
+ }
+ prg_cache_clear();
+ return 0;
+}
diff --git a/networking/nslookup.c b/networking/nslookup.c
new file mode 100644
index 0000000..73ccb0d
--- /dev/null
+++ b/networking/nslookup.c
@@ -0,0 +1,155 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini nslookup implementation for busybox
+ *
+ * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu
+ * Copyright (C) 1999,2000,2001 by John Beppu <beppu@codepoet.org>
+ *
+ * Correct default name server display and explicit name server option
+ * added by Ben Zeckel <bzeckel@hmc.edu> June 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <resolv.h>
+#include "libbb.h"
+
+/*
+ * I'm only implementing non-interactive mode;
+ * I totally forgot nslookup even had an interactive mode.
+ *
+ * This applet is the only user of res_init(). Without it,
+ * you may avoid pulling in _res global from libc.
+ */
+
+/* Examples of 'standard' nslookup output
+ * $ nslookup yahoo.com
+ * Server: 128.193.0.10
+ * Address: 128.193.0.10#53
+ *
+ * Non-authoritative answer:
+ * Name: yahoo.com
+ * Address: 216.109.112.135
+ * Name: yahoo.com
+ * Address: 66.94.234.13
+ *
+ * $ nslookup 204.152.191.37
+ * Server: 128.193.4.20
+ * Address: 128.193.4.20#53
+ *
+ * Non-authoritative answer:
+ * 37.191.152.204.in-addr.arpa canonical name = 37.32-27.191.152.204.in-addr.arpa.
+ * 37.32-27.191.152.204.in-addr.arpa name = zeus-pub2.kernel.org.
+ *
+ * Authoritative answers can be found from:
+ * 32-27.191.152.204.in-addr.arpa nameserver = ns1.kernel.org.
+ * 32-27.191.152.204.in-addr.arpa nameserver = ns2.kernel.org.
+ * 32-27.191.152.204.in-addr.arpa nameserver = ns3.kernel.org.
+ * ns1.kernel.org internet address = 140.211.167.34
+ * ns2.kernel.org internet address = 204.152.191.4
+ * ns3.kernel.org internet address = 204.152.191.36
+ */
+
+static int print_host(const char *hostname, const char *header)
+{
+ /* We can't use xhost2sockaddr() - we want to get ALL addresses,
+ * not just one */
+ struct addrinfo *result = NULL;
+ int rc;
+ struct addrinfo hint;
+
+ memset(&hint, 0 , sizeof(hint));
+ /* hint.ai_family = AF_UNSPEC; - zero anyway */
+ /* Needed. Or else we will get each address thrice (or more)
+ * for each possible socket type (tcp,udp,raw...): */
+ hint.ai_socktype = SOCK_STREAM;
+ // hint.ai_flags = AI_CANONNAME;
+ rc = getaddrinfo(hostname, NULL /*service*/, &hint, &result);
+
+ if (!rc) {
+ struct addrinfo *cur = result;
+ unsigned cnt = 0;
+
+ printf("%-10s %s\n", header, hostname);
+ // puts(cur->ai_canonname); ?
+ while (cur) {
+ char *dotted, *revhost;
+ dotted = xmalloc_sockaddr2dotted_noport(cur->ai_addr);
+ revhost = xmalloc_sockaddr2hostonly_noport(cur->ai_addr);
+
+ printf("Address %u: %s%c", ++cnt, dotted, revhost ? ' ' : '\n');
+ if (revhost) {
+ puts(revhost);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(revhost);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(dotted);
+ cur = cur->ai_next;
+ }
+ } else {
+#if ENABLE_VERBOSE_RESOLUTION_ERRORS
+ bb_error_msg("can't resolve '%s': %s", hostname, gai_strerror(rc));
+#else
+ bb_error_msg("can't resolve '%s'", hostname);
+#endif
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ freeaddrinfo(result);
+ return (rc != 0);
+}
+
+/* lookup the default nameserver and display it */
+static void server_print(void)
+{
+ char *server;
+
+ server = xmalloc_sockaddr2dotted_noport((struct sockaddr*)&_res.nsaddr_list[0]);
+ /* I honestly don't know what to do if DNS server has _IPv6 address_.
+ * Probably it is listed in
+ * _res._u._ext_.nsaddrs[MAXNS] (of type "struct sockaddr_in6*" each)
+ * but how to find out whether resolver uses
+ * _res.nsaddr_list[] or _res._u._ext_.nsaddrs[], or both?
+ * Looks like classic design from hell, BIND-grade. Hard to surpass. */
+ print_host(server, "Server:");
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(server);
+ bb_putchar('\n');
+}
+
+/* alter the global _res nameserver structure to use
+ an explicit dns server instead of what is in /etc/resolv.conf */
+static void set_default_dns(char *server)
+{
+ struct in_addr server_in_addr;
+
+ if (inet_pton(AF_INET, server, &server_in_addr) > 0) {
+ _res.nscount = 1;
+ _res.nsaddr_list[0].sin_addr = server_in_addr;
+ }
+}
+
+int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int nslookup_main(int argc, char **argv)
+{
+ /* We allow 1 or 2 arguments.
+ * The first is the name to be looked up and the second is an
+ * optional DNS server with which to do the lookup.
+ * More than 3 arguments is an error to follow the pattern of the
+ * standard nslookup */
+ if (!argv[1] || argv[1][0] == '-' || argc > 3)
+ bb_show_usage();
+
+ /* initialize DNS structure _res used in printing the default
+ * name server and in the explicit name server option feature. */
+ res_init();
+ /* rfc2133 says this enables IPv6 lookups */
+ /* (but it also says "may be enabled in /etc/resolv.conf") */
+ /*_res.options |= RES_USE_INET6;*/
+
+ if (argv[2])
+ set_default_dns(argv[2]);
+
+ server_print();
+ return print_host(argv[1], "Name:");
+}
diff --git a/networking/ping.c b/networking/ping.c
new file mode 100644
index 0000000..f2a612f
--- /dev/null
+++ b/networking/ping.c
@@ -0,0 +1,812 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Mini ping implementation for busybox
+ *
+ * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
+ *
+ * Adapted from the ping in netkit-base 0.10:
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Muuss.
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+/* from ping6.c:
+ * Copyright (C) 1999 by Randolph Chung <tausq@debian.org>
+ *
+ * This version of ping is adapted from the ping in netkit-base 0.10,
+ * which is:
+ *
+ * Original copyright notice is retained at the end of this file.
+ *
+ * This version is an adaptation of ping.c from busybox.
+ * The code was modified by Bart Visscher <magick@linux-fan.com>
+ */
+
+#include <net/if.h>
+#include <netinet/ip_icmp.h>
+#include "libbb.h"
+
+#if ENABLE_PING6
+#include <netinet/icmp6.h>
+/* I see RENUMBERED constants in bits/in.h - !!?
+ * What a fuck is going on with libc? Is it a glibc joke? */
+#ifdef IPV6_2292HOPLIMIT
+#undef IPV6_HOPLIMIT
+#define IPV6_HOPLIMIT IPV6_2292HOPLIMIT
+#endif
+#endif
+
+enum {
+ DEFDATALEN = 56,
+ MAXIPLEN = 60,
+ MAXICMPLEN = 76,
+ MAXPACKET = 65468,
+ MAX_DUP_CHK = (8 * 128),
+ MAXWAIT = 10,
+ PINGINTERVAL = 1, /* 1 second */
+};
+
+/* common routines */
+
+static int in_cksum(unsigned short *buf, int sz)
+{
+ int nleft = sz;
+ int sum = 0;
+ unsigned short *w = buf;
+ unsigned short ans = 0;
+
+ while (nleft > 1) {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ if (nleft == 1) {
+ *(unsigned char *) (&ans) = *(unsigned char *) w;
+ sum += ans;
+ }
+
+ sum = (sum >> 16) + (sum & 0xFFFF);
+ sum += (sum >> 16);
+ ans = ~sum;
+ return ans;
+}
+
+#if !ENABLE_FEATURE_FANCY_PING
+
+/* simple version */
+
+static char *hostname;
+
+static void noresp(int ign UNUSED_PARAM)
+{
+ printf("No response from %s\n", hostname);
+ exit(EXIT_FAILURE);
+}
+
+static void ping4(len_and_sockaddr *lsa)
+{
+ struct sockaddr_in pingaddr;
+ struct icmp *pkt;
+ int pingsock, c;
+ char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
+
+ pingsock = create_icmp_socket();
+ pingaddr = lsa->u.sin;
+
+ pkt = (struct icmp *) packet;
+ memset(pkt, 0, sizeof(packet));
+ pkt->icmp_type = ICMP_ECHO;
+ pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet));
+
+ c = xsendto(pingsock, packet, DEFDATALEN + ICMP_MINLEN,
+ (struct sockaddr *) &pingaddr, sizeof(pingaddr));
+
+ /* listen for replies */
+ while (1) {
+ struct sockaddr_in from;
+ socklen_t fromlen = sizeof(from);
+
+ c = recvfrom(pingsock, packet, sizeof(packet), 0,
+ (struct sockaddr *) &from, &fromlen);
+ if (c < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("recvfrom");
+ continue;
+ }
+ if (c >= 76) { /* ip + icmp */
+ struct iphdr *iphdr = (struct iphdr *) packet;
+
+ pkt = (struct icmp *) (packet + (iphdr->ihl << 2)); /* skip ip hdr */
+ if (pkt->icmp_type == ICMP_ECHOREPLY)
+ break;
+ }
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(pingsock);
+}
+
+#if ENABLE_PING6
+static void ping6(len_and_sockaddr *lsa)
+{
+ struct sockaddr_in6 pingaddr;
+ struct icmp6_hdr *pkt;
+ int pingsock, c;
+ int sockopt;
+ char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN];
+
+ pingsock = create_icmp6_socket();
+ pingaddr = lsa->u.sin6;
+
+ pkt = (struct icmp6_hdr *) packet;
+ memset(pkt, 0, sizeof(packet));
+ pkt->icmp6_type = ICMP6_ECHO_REQUEST;
+
+ sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
+ setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt));
+
+ c = xsendto(pingsock, packet, DEFDATALEN + sizeof (struct icmp6_hdr),
+ (struct sockaddr *) &pingaddr, sizeof(pingaddr));
+
+ /* listen for replies */
+ while (1) {
+ struct sockaddr_in6 from;
+ socklen_t fromlen = sizeof(from);
+
+ c = recvfrom(pingsock, packet, sizeof(packet), 0,
+ (struct sockaddr *) &from, &fromlen);
+ if (c < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("recvfrom");
+ continue;
+ }
+ if (c >= 8) { /* icmp6_hdr */
+ pkt = (struct icmp6_hdr *) packet;
+ if (pkt->icmp6_type == ICMP6_ECHO_REPLY)
+ break;
+ }
+ }
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(pingsock);
+}
+#endif
+
+int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping_main(int argc UNUSED_PARAM, char **argv)
+{
+ len_and_sockaddr *lsa;
+#if ENABLE_PING6
+ sa_family_t af = AF_UNSPEC;
+
+ while ((++argv)[0] && argv[0][0] == '-') {
+ if (argv[0][1] == '4') {
+ af = AF_INET;
+ continue;
+ }
+ if (argv[0][1] == '6') {
+ af = AF_INET6;
+ continue;
+ }
+ bb_show_usage();
+ }
+#else
+ argv++;
+#endif
+
+ hostname = *argv;
+ if (!hostname)
+ bb_show_usage();
+
+#if ENABLE_PING6
+ lsa = xhost_and_af2sockaddr(hostname, 0, af);
+#else
+ lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET);
+#endif
+ /* Set timer _after_ DNS resolution */
+ signal(SIGALRM, noresp);
+ alarm(5); /* give the host 5000ms to respond */
+
+#if ENABLE_PING6
+ if (lsa->u.sa.sa_family == AF_INET6)
+ ping6(lsa);
+ else
+#endif
+ ping4(lsa);
+ printf("%s is alive!\n", hostname);
+ return EXIT_SUCCESS;
+}
+
+
+#else /* FEATURE_FANCY_PING */
+
+
+/* full(er) version */
+
+#define OPT_STRING ("qvc:s:w:W:I:4" USE_PING6("6"))
+enum {
+ OPT_QUIET = 1 << 0,
+ OPT_VERBOSE = 1 << 1,
+ OPT_c = 1 << 2,
+ OPT_s = 1 << 3,
+ OPT_w = 1 << 4,
+ OPT_W = 1 << 5,
+ OPT_I = 1 << 6,
+ OPT_IPV4 = 1 << 7,
+ OPT_IPV6 = (1 << 8) * ENABLE_PING6,
+};
+
+
+struct globals {
+ int pingsock;
+ int if_index;
+ char *str_I;
+ len_and_sockaddr *source_lsa;
+ unsigned datalen;
+ unsigned pingcount; /* must be int-sized */
+ unsigned long ntransmitted, nreceived, nrepeats;
+ uint16_t myid;
+ unsigned tmin, tmax; /* in us */
+ unsigned long long tsum; /* in us, sum of all times */
+ unsigned deadline;
+ unsigned timeout;
+ unsigned total_secs;
+ const char *hostname;
+ const char *dotted;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+#if ENABLE_PING6
+ struct sockaddr_in6 sin6;
+#endif
+ } pingaddr;
+ char rcvd_tbl[MAX_DUP_CHK / 8];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define pingsock (G.pingsock )
+#define if_index (G.if_index )
+#define source_lsa (G.source_lsa )
+#define str_I (G.str_I )
+#define datalen (G.datalen )
+#define ntransmitted (G.ntransmitted)
+#define nreceived (G.nreceived )
+#define nrepeats (G.nrepeats )
+#define pingcount (G.pingcount )
+#define myid (G.myid )
+#define tmin (G.tmin )
+#define tmax (G.tmax )
+#define tsum (G.tsum )
+#define deadline (G.deadline )
+#define timeout (G.timeout )
+#define total_secs (G.total_secs )
+#define hostname (G.hostname )
+#define dotted (G.dotted )
+#define pingaddr (G.pingaddr )
+#define rcvd_tbl (G.rcvd_tbl )
+void BUG_ping_globals_too_big(void);
+#define INIT_G() do { \
+ if (sizeof(G) > COMMON_BUFSIZE) \
+ BUG_ping_globals_too_big(); \
+ pingsock = -1; \
+ datalen = DEFDATALEN; \
+ timeout = MAXWAIT; \
+ tmin = UINT_MAX; \
+} while (0)
+
+
+#define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */
+#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */
+#define SET(bit) (A(bit) |= B(bit))
+#define CLR(bit) (A(bit) &= (~B(bit)))
+#define TST(bit) (A(bit) & B(bit))
+
+/**************************************************************************/
+
+static void print_stats_and_exit(int junk) NORETURN;
+static void print_stats_and_exit(int junk UNUSED_PARAM)
+{
+ signal(SIGINT, SIG_IGN);
+
+ printf("\n--- %s ping statistics ---\n", hostname);
+ printf("%lu packets transmitted, ", ntransmitted);
+ printf("%lu packets received, ", nreceived);
+ if (nrepeats)
+ printf("%lu duplicates, ", nrepeats);
+ if (ntransmitted)
+ ntransmitted = (ntransmitted - nreceived) * 100 / ntransmitted;
+ printf("%lu%% packet loss\n", ntransmitted);
+ if (tmin != UINT_MAX) {
+ unsigned tavg = tsum / (nreceived + nrepeats);
+ printf("round-trip min/avg/max = %u.%03u/%u.%03u/%u.%03u ms\n",
+ tmin / 1000, tmin % 1000,
+ tavg / 1000, tavg % 1000,
+ tmax / 1000, tmax % 1000);
+ }
+ /* if condition is true, exit with 1 -- 'failure' */
+ exit(nreceived == 0 || (deadline && nreceived < pingcount));
+}
+
+static void sendping_tail(void (*sp)(int), const void *pkt, int size_pkt)
+{
+ int sz;
+
+ CLR((uint16_t)ntransmitted % MAX_DUP_CHK);
+ ntransmitted++;
+
+ /* sizeof(pingaddr) can be larger than real sa size, but I think
+ * it doesn't matter */
+ sz = xsendto(pingsock, pkt, size_pkt, &pingaddr.sa, sizeof(pingaddr));
+ if (sz != size_pkt)
+ bb_error_msg_and_die(bb_msg_write_error);
+
+ if (pingcount == 0 || deadline || ntransmitted < pingcount) {
+ /* Didn't send all pings yet - schedule next in 1s */
+ signal(SIGALRM, sp);
+ if (deadline) {
+ total_secs += PINGINTERVAL;
+ if (total_secs >= deadline)
+ signal(SIGALRM, print_stats_and_exit);
+ }
+ alarm(PINGINTERVAL);
+ } else { /* -c NN, and all NN are sent (and no deadline) */
+ /* Wait for the last ping to come back.
+ * -W timeout: wait for a response in seconds.
+ * Affects only timeout in absense of any responses,
+ * otherwise ping waits for two RTTs. */
+ unsigned expire = timeout;
+
+ if (nreceived) {
+ /* approx. 2*tmax, in seconds (2 RTT) */
+ expire = tmax / (512*1024);
+ if (expire == 0)
+ expire = 1;
+ }
+ signal(SIGALRM, print_stats_and_exit);
+ alarm(expire);
+ }
+}
+
+static void sendping4(int junk UNUSED_PARAM)
+{
+ /* +4 reserves a place for timestamp, which may end up sitting
+ * *after* packet. Saves one if() */
+ struct icmp *pkt = alloca(datalen + ICMP_MINLEN + 4);
+
+ memset(pkt, 0, datalen + ICMP_MINLEN + 4);
+ pkt->icmp_type = ICMP_ECHO;
+ /*pkt->icmp_code = 0;*/
+ /*pkt->icmp_cksum = 0;*/
+ pkt->icmp_seq = htons(ntransmitted); /* don't ++ here, it can be a macro */
+ pkt->icmp_id = myid;
+
+ /* We don't do hton, because we will read it back on the same machine */
+ /*if (datalen >= 4)*/
+ *(uint32_t*)&pkt->icmp_dun = monotonic_us();
+
+ pkt->icmp_cksum = in_cksum((unsigned short *) pkt, datalen + ICMP_MINLEN);
+
+ sendping_tail(sendping4, pkt, datalen + ICMP_MINLEN);
+}
+#if ENABLE_PING6
+static void sendping6(int junk UNUSED_PARAM)
+{
+ struct icmp6_hdr *pkt = alloca(datalen + sizeof(struct icmp6_hdr) + 4);
+
+ memset(pkt, 0, datalen + sizeof(struct icmp6_hdr) + 4);
+ pkt->icmp6_type = ICMP6_ECHO_REQUEST;
+ /*pkt->icmp6_code = 0;*/
+ /*pkt->icmp6_cksum = 0;*/
+ pkt->icmp6_seq = htons(ntransmitted); /* don't ++ here, it can be a macro */
+ pkt->icmp6_id = myid;
+
+ /*if (datalen >= 4)*/
+ *(uint32_t*)(&pkt->icmp6_data8[4]) = monotonic_us();
+
+ sendping_tail(sendping6, pkt, datalen + sizeof(struct icmp6_hdr));
+}
+#endif
+
+static const char *icmp_type_name(int id)
+{
+ switch (id) {
+ case ICMP_ECHOREPLY: return "Echo Reply";
+ case ICMP_DEST_UNREACH: return "Destination Unreachable";
+ case ICMP_SOURCE_QUENCH: return "Source Quench";
+ case ICMP_REDIRECT: return "Redirect (change route)";
+ case ICMP_ECHO: return "Echo Request";
+ case ICMP_TIME_EXCEEDED: return "Time Exceeded";
+ case ICMP_PARAMETERPROB: return "Parameter Problem";
+ case ICMP_TIMESTAMP: return "Timestamp Request";
+ case ICMP_TIMESTAMPREPLY: return "Timestamp Reply";
+ case ICMP_INFO_REQUEST: return "Information Request";
+ case ICMP_INFO_REPLY: return "Information Reply";
+ case ICMP_ADDRESS: return "Address Mask Request";
+ case ICMP_ADDRESSREPLY: return "Address Mask Reply";
+ default: return "unknown ICMP type";
+ }
+}
+#if ENABLE_PING6
+/* RFC3542 changed some definitions from RFC2292 for no good reason, whee!
+ * the newer 3542 uses a MLD_ prefix where as 2292 uses ICMP6_ prefix */
+#ifndef MLD_LISTENER_QUERY
+# define MLD_LISTENER_QUERY ICMP6_MEMBERSHIP_QUERY
+#endif
+#ifndef MLD_LISTENER_REPORT
+# define MLD_LISTENER_REPORT ICMP6_MEMBERSHIP_REPORT
+#endif
+#ifndef MLD_LISTENER_REDUCTION
+# define MLD_LISTENER_REDUCTION ICMP6_MEMBERSHIP_REDUCTION
+#endif
+static const char *icmp6_type_name(int id)
+{
+ switch (id) {
+ case ICMP6_DST_UNREACH: return "Destination Unreachable";
+ case ICMP6_PACKET_TOO_BIG: return "Packet too big";
+ case ICMP6_TIME_EXCEEDED: return "Time Exceeded";
+ case ICMP6_PARAM_PROB: return "Parameter Problem";
+ case ICMP6_ECHO_REPLY: return "Echo Reply";
+ case ICMP6_ECHO_REQUEST: return "Echo Request";
+ case MLD_LISTENER_QUERY: return "Listener Query";
+ case MLD_LISTENER_REPORT: return "Listener Report";
+ case MLD_LISTENER_REDUCTION: return "Listener Reduction";
+ default: return "unknown ICMP type";
+ }
+}
+#endif
+
+static void unpack_tail(int sz, uint32_t *tp,
+ const char *from_str,
+ uint16_t recv_seq, int ttl)
+{
+ const char *dupmsg = " (DUP!)";
+ unsigned triptime = triptime; /* for gcc */
+
+ ++nreceived;
+
+ if (tp) {
+ /* (int32_t) cast is for hypothetical 64-bit unsigned */
+ /* (doesn't hurt 32-bit real-world anyway) */
+ triptime = (int32_t) ((uint32_t)monotonic_us() - *tp);
+ tsum += triptime;
+ if (triptime < tmin)
+ tmin = triptime;
+ if (triptime > tmax)
+ tmax = triptime;
+ }
+
+ if (TST(recv_seq % MAX_DUP_CHK)) {
+ ++nrepeats;
+ --nreceived;
+ } else {
+ SET(recv_seq % MAX_DUP_CHK);
+ dupmsg += 7;
+ }
+
+ if (option_mask32 & OPT_QUIET)
+ return;
+
+ printf("%d bytes from %s: seq=%u ttl=%d", sz,
+ from_str, recv_seq, ttl);
+ if (tp)
+ printf(" time=%u.%03u ms", triptime / 1000, triptime % 1000);
+ puts(dupmsg);
+ fflush(stdout);
+}
+static void unpack4(char *buf, int sz, struct sockaddr_in *from)
+{
+ struct icmp *icmppkt;
+ struct iphdr *iphdr;
+ int hlen;
+
+ /* discard if too short */
+ if (sz < (datalen + ICMP_MINLEN))
+ return;
+
+ /* check IP header */
+ iphdr = (struct iphdr *) buf;
+ hlen = iphdr->ihl << 2;
+ sz -= hlen;
+ icmppkt = (struct icmp *) (buf + hlen);
+ if (icmppkt->icmp_id != myid)
+ return; /* not our ping */
+
+ if (icmppkt->icmp_type == ICMP_ECHOREPLY) {
+ uint16_t recv_seq = ntohs(icmppkt->icmp_seq);
+ uint32_t *tp = NULL;
+
+ if (sz >= ICMP_MINLEN + sizeof(uint32_t))
+ tp = (uint32_t *) icmppkt->icmp_data;
+ unpack_tail(sz, tp,
+ inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr),
+ recv_seq, iphdr->ttl);
+ } else if (icmppkt->icmp_type != ICMP_ECHO) {
+ bb_error_msg("warning: got ICMP %d (%s)",
+ icmppkt->icmp_type,
+ icmp_type_name(icmppkt->icmp_type));
+ }
+}
+#if ENABLE_PING6
+static void unpack6(char *packet, int sz, /*struct sockaddr_in6 *from,*/ int hoplimit)
+{
+ struct icmp6_hdr *icmppkt;
+ char buf[INET6_ADDRSTRLEN];
+
+ /* discard if too short */
+ if (sz < (datalen + sizeof(struct icmp6_hdr)))
+ return;
+
+ icmppkt = (struct icmp6_hdr *) packet;
+ if (icmppkt->icmp6_id != myid)
+ return; /* not our ping */
+
+ if (icmppkt->icmp6_type == ICMP6_ECHO_REPLY) {
+ uint16_t recv_seq = ntohs(icmppkt->icmp6_seq);
+ uint32_t *tp = NULL;
+
+ if (sz >= sizeof(struct icmp6_hdr) + sizeof(uint32_t))
+ tp = (uint32_t *) &icmppkt->icmp6_data8[4];
+ unpack_tail(sz, tp,
+ inet_ntop(AF_INET6, &pingaddr.sin6.sin6_addr,
+ buf, sizeof(buf)),
+ recv_seq, hoplimit);
+ } else if (icmppkt->icmp6_type != ICMP6_ECHO_REQUEST) {
+ bb_error_msg("warning: got ICMP %d (%s)",
+ icmppkt->icmp6_type,
+ icmp6_type_name(icmppkt->icmp6_type));
+ }
+}
+#endif
+
+static void ping4(len_and_sockaddr *lsa)
+{
+ char packet[datalen + MAXIPLEN + MAXICMPLEN];
+ int sockopt;
+
+ pingsock = create_icmp_socket();
+ pingaddr.sin = lsa->u.sin;
+ if (source_lsa) {
+ if (setsockopt(pingsock, IPPROTO_IP, IP_MULTICAST_IF,
+ &source_lsa->u.sa, source_lsa->len))
+ bb_error_msg_and_die("can't set multicast source interface");
+ xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
+ }
+ if (str_I)
+ setsockopt_bindtodevice(pingsock, str_I);
+
+ /* enable broadcast pings */
+ setsockopt_broadcast(pingsock);
+
+ /* set recv buf (needed if we can get lots of responses: flood ping,
+ * broadcast ping etc) */
+ sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */
+ setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
+
+ signal(SIGINT, print_stats_and_exit);
+
+ /* start the ping's going ... */
+ sendping4(0);
+
+ /* listen for replies */
+ while (1) {
+ struct sockaddr_in from;
+ socklen_t fromlen = (socklen_t) sizeof(from);
+ int c;
+
+ c = recvfrom(pingsock, packet, sizeof(packet), 0,
+ (struct sockaddr *) &from, &fromlen);
+ if (c < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("recvfrom");
+ continue;
+ }
+ unpack4(packet, c, &from);
+ if (pingcount && nreceived >= pingcount)
+ break;
+ }
+}
+#if ENABLE_PING6
+extern int BUG_bad_offsetof_icmp6_cksum(void);
+static void ping6(len_and_sockaddr *lsa)
+{
+ char packet[datalen + MAXIPLEN + MAXICMPLEN];
+ int sockopt;
+ struct msghdr msg;
+ struct sockaddr_in6 from;
+ struct iovec iov;
+ char control_buf[CMSG_SPACE(36)];
+
+ pingsock = create_icmp6_socket();
+ pingaddr.sin6 = lsa->u.sin6;
+ /* untested whether "-I addr" really works for IPv6: */
+ if (source_lsa)
+ xbind(pingsock, &source_lsa->u.sa, source_lsa->len);
+ if (str_I)
+ setsockopt_bindtodevice(pingsock, str_I);
+
+#ifdef ICMP6_FILTER
+ {
+ struct icmp6_filter filt;
+ if (!(option_mask32 & OPT_VERBOSE)) {
+ ICMP6_FILTER_SETBLOCKALL(&filt);
+ ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt);
+ } else {
+ ICMP6_FILTER_SETPASSALL(&filt);
+ }
+ if (setsockopt(pingsock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt,
+ sizeof(filt)) < 0)
+ bb_error_msg_and_die("setsockopt(ICMP6_FILTER)");
+ }
+#endif /*ICMP6_FILTER*/
+
+ /* enable broadcast pings */
+ setsockopt_broadcast(pingsock);
+
+ /* set recv buf (needed if we can get lots of responses: flood ping,
+ * broadcast ping etc) */
+ sockopt = (datalen * 2) + 7 * 1024; /* giving it a bit of extra room */
+ setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, &sockopt, sizeof(sockopt));
+
+ sockopt = offsetof(struct icmp6_hdr, icmp6_cksum);
+ if (offsetof(struct icmp6_hdr, icmp6_cksum) != 2)
+ BUG_bad_offsetof_icmp6_cksum();
+ setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, &sockopt, sizeof(sockopt));
+
+ /* request ttl info to be returned in ancillary data */
+ setsockopt(pingsock, SOL_IPV6, IPV6_HOPLIMIT, &const_int_1, sizeof(const_int_1));
+
+ if (if_index)
+ pingaddr.sin6.sin6_scope_id = if_index;
+
+ signal(SIGINT, print_stats_and_exit);
+
+ /* start the ping's going ... */
+ sendping6(0);
+
+ /* listen for replies */
+ msg.msg_name = &from;
+ msg.msg_namelen = sizeof(from);
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control_buf;
+ iov.iov_base = packet;
+ iov.iov_len = sizeof(packet);
+ while (1) {
+ int c;
+ struct cmsghdr *mp;
+ int hoplimit = -1;
+ msg.msg_controllen = sizeof(control_buf);
+
+ c = recvmsg(pingsock, &msg, 0);
+ if (c < 0) {
+ if (errno != EINTR)
+ bb_perror_msg("recvfrom");
+ continue;
+ }
+ for (mp = CMSG_FIRSTHDR(&msg); mp; mp = CMSG_NXTHDR(&msg, mp)) {
+ if (mp->cmsg_level == SOL_IPV6
+ && mp->cmsg_type == IPV6_HOPLIMIT
+ /* don't check len - we trust the kernel: */
+ /* && mp->cmsg_len >= CMSG_LEN(sizeof(int)) */
+ ) {
+ hoplimit = *(int*)CMSG_DATA(mp);
+ }
+ }
+ unpack6(packet, c, /*&from,*/ hoplimit);
+ if (pingcount && nreceived >= pingcount)
+ break;
+ }
+}
+#endif
+
+static void ping(len_and_sockaddr *lsa)
+{
+ printf("PING %s (%s)", hostname, dotted);
+ if (source_lsa) {
+ printf(" from %s",
+ xmalloc_sockaddr2dotted_noport(&source_lsa->u.sa));
+ }
+ printf(": %d data bytes\n", datalen);
+
+#if ENABLE_PING6
+ if (lsa->u.sa.sa_family == AF_INET6)
+ ping6(lsa);
+ else
+#endif
+ ping4(lsa);
+}
+
+int ping_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping_main(int argc UNUSED_PARAM, char **argv)
+{
+ len_and_sockaddr *lsa;
+ char *str_s;
+ int opt;
+
+ INIT_G();
+
+ /* exactly one argument needed; -v and -q don't mix; -c NUM, -w NUM, -W NUM */
+ opt_complementary = "=1:q--v:v--q:c+:w+:W+";
+ opt = getopt32(argv, OPT_STRING, &pingcount, &str_s, &deadline, &timeout, &str_I);
+ if (opt & OPT_s)
+ datalen = xatou16(str_s); // -s
+ if (opt & OPT_I) { // -I
+ if_index = if_nametoindex(str_I);
+ if (!if_index) {
+ /* TODO: I'm not sure it takes IPv6 unless in [XX:XX..] format */
+ source_lsa = xdotted2sockaddr(str_I, 0);
+ str_I = NULL; /* don't try to bind to device later */
+ }
+ }
+ myid = (uint16_t) getpid();
+ hostname = argv[optind];
+#if ENABLE_PING6
+ {
+ sa_family_t af = AF_UNSPEC;
+ if (opt & OPT_IPV4)
+ af = AF_INET;
+ if (opt & OPT_IPV6)
+ af = AF_INET6;
+ lsa = xhost_and_af2sockaddr(hostname, 0, af);
+ }
+#else
+ lsa = xhost_and_af2sockaddr(hostname, 0, AF_INET);
+#endif
+
+ if (source_lsa && source_lsa->u.sa.sa_family != lsa->u.sa.sa_family)
+ /* leaking it here... */
+ source_lsa = NULL;
+
+ dotted = xmalloc_sockaddr2dotted_noport(&lsa->u.sa);
+ ping(lsa);
+ print_stats_and_exit(EXIT_SUCCESS);
+ /*return EXIT_SUCCESS;*/
+}
+#endif /* FEATURE_FANCY_PING */
+
+
+#if ENABLE_PING6
+int ping6_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ping6_main(int argc UNUSED_PARAM, char **argv)
+{
+ argv[0] = (char*)"-6";
+ return ping_main(0 /* argc+1 - but it's unused anyway */,
+ argv - 1);
+}
+#endif
+
+/* from ping6.c:
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Mike Muuss.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
+ * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
+ *
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/networking/pscan.c b/networking/pscan.c
new file mode 100644
index 0000000..5fb6af0
--- /dev/null
+++ b/networking/pscan.c
@@ -0,0 +1,154 @@
+/*
+ * Pscan is a mini port scanner implementation for busybox
+ *
+ * Copyright 2007 Tito Ragusa <farmatito@tiscali.it>
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "libbb.h"
+
+/* debugging */
+#ifdef DEBUG_PSCAN
+#define DMSG(...) bb_error_msg(__VA_ARGS__)
+#define DERR(...) bb_perror_msg(__VA_ARGS__)
+#else
+#define DMSG(...) ((void)0)
+#define DERR(...) ((void)0)
+#endif
+
+static const char *port_name(unsigned port)
+{
+ struct servent *server;
+
+ server = getservbyport(htons(port), NULL);
+ if (server)
+ return server->s_name;
+ return "unknown";
+}
+
+/* We don't expect to see 1000+ seconds delay, unsigned is enough */
+#define MONOTONIC_US() ((unsigned)monotonic_us())
+
+int pscan_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int pscan_main(int argc UNUSED_PARAM, char **argv)
+{
+ const char *opt_max_port = "1024"; /* -P: default max port */
+ const char *opt_min_port = "1"; /* -p: default min port */
+ const char *opt_timeout = "5000"; /* -t: default timeout in msec */
+ /* We estimate rtt and wait rtt*4 before concluding that port is
+ * totally blocked. min rtt of 5 ms may be too low if you are
+ * scanning an Internet host behind saturated/traffic shaped link.
+ * Rule of thumb: with min_rtt of N msec, scanning 1000 ports
+ * will take N seconds at absolute minimum */
+ const char *opt_min_rtt = "5"; /* -T: default min rtt in msec */
+ const char *result_str;
+ len_and_sockaddr *lsap;
+ int s;
+ unsigned opt;
+ unsigned port, max_port, nports;
+ unsigned closed_ports = 0;
+ unsigned open_ports = 0;
+ /* all in usec */
+ unsigned timeout;
+ unsigned min_rtt;
+ unsigned rtt_4;
+ unsigned start, diff;
+
+ opt_complementary = "=1"; /* exactly one non-option */
+ opt = getopt32(argv, "cbp:P:t:T:", &opt_min_port, &opt_max_port, &opt_timeout, &opt_min_rtt);
+ argv += optind;
+ max_port = xatou_range(opt_max_port, 1, 65535);
+ port = xatou_range(opt_min_port, 1, max_port);
+ nports = max_port - port + 1;
+ min_rtt = xatou_range(opt_min_rtt, 1, INT_MAX/1000 / 4) * 1000;
+ timeout = xatou_range(opt_timeout, 1, INT_MAX/1000 / 4) * 1000;
+ /* Initial rtt is BIG: */
+ rtt_4 = timeout;
+
+ DMSG("min_rtt %u timeout %u", min_rtt, timeout);
+
+ lsap = xhost2sockaddr(*argv, port);
+ printf("Scanning %s ports %u to %u\n Port\tProto\tState\tService\n",
+ *argv, port, max_port);
+
+ for (; port <= max_port; port++) {
+ DMSG("rtt %u", rtt_4);
+
+ /* The SOCK_STREAM socket type is implemented on the TCP/IP protocol. */
+ set_nport(lsap, htons(port));
+ s = xsocket(lsap->u.sa.sa_family, SOCK_STREAM, 0);
+ /* We need unblocking socket so we don't need to wait for ETIMEOUT. */
+ /* Nonblocking connect typically "fails" with errno == EINPROGRESS */
+ ndelay_on(s);
+
+ DMSG("connect to port %u", port);
+ result_str = NULL;
+ start = MONOTONIC_US();
+ if (connect(s, &lsap->u.sa, lsap->len) == 0) {
+ /* Unlikely, for me even localhost fails :) */
+ DMSG("connect succeeded");
+ goto open;
+ }
+ /* Check for untypical errors... */
+ if (errno != EAGAIN && errno != EINPROGRESS
+ && errno != ECONNREFUSED
+ ) {
+ bb_perror_nomsg_and_die();
+ }
+
+ diff = 0;
+ while (1) {
+ if (errno == ECONNREFUSED) {
+ if (opt & 1) /* -c: show closed too */
+ result_str = "closed";
+ closed_ports++;
+ break;
+ }
+ DERR("port %u errno %d @%u", port, errno, diff);
+
+ if (diff > rtt_4) {
+ if (opt & 2) /* -b: show blocked too */
+ result_str = "blocked";
+ break;
+ }
+ /* Can sleep (much) longer than specified delay.
+ * We check rtt BEFORE we usleep, otherwise
+ * on localhost we'll have no writes done (!)
+ * before we exceed (rather small) rtt */
+ usleep(rtt_4/8);
+ open:
+ diff = MONOTONIC_US() - start;
+ DMSG("write to port %u @%u", port, diff - start);
+ if (write(s, " ", 1) >= 0) { /* We were able to write to the socket */
+ open_ports++;
+ result_str = "open";
+ break;
+ }
+ }
+ DMSG("out of loop @%u", diff);
+ if (result_str)
+ printf("%5u" "\t" "tcp" "\t" "%s" "\t" "%s" "\n",
+ port, result_str, port_name(port));
+
+ /* Estimate new rtt - we don't want to wait entire timeout
+ * for each port. *4 allows for rise in net delay.
+ * We increase rtt quickly (rtt_4*4), decrease slowly
+ * (diff is at least rtt_4/8, *4 == rtt_4/2)
+ * because we don't want to accidentally miss ports. */
+ rtt_4 = diff * 4;
+ if (rtt_4 < min_rtt)
+ rtt_4 = min_rtt;
+ if (rtt_4 > timeout)
+ rtt_4 = timeout;
+ /* Clean up */
+ close(s);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP) free(lsap);
+
+ printf("%d closed, %d open, %d timed out (or blocked) ports\n",
+ closed_ports,
+ open_ports,
+ nports - (closed_ports + open_ports));
+ return EXIT_SUCCESS;
+}
diff --git a/networking/route.c b/networking/route.c
new file mode 100644
index 0000000..8778ecd
--- /dev/null
+++ b/networking/route.c
@@ -0,0 +1,699 @@
+/* vi: set sw=4 ts=4: */
+/* route
+ *
+ * Similar to the standard Unix route, but with only the necessary
+ * parts for AF_INET and AF_INET6
+ *
+ * Bjorn Wesen, Axis Communications AB
+ *
+ * Author of the original route:
+ * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ * (derived from FvK's 'route.c 1.70 01/04/94')
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ *
+ * displayroute() code added by Vladimir N. Oleynik <dzo@simtreas.ru>
+ * adjustments by Larry Doolittle <LRDoolittle@lbl.gov>
+ *
+ * IPV6 support added by Bart Visscher <magick@linux-fan.com>
+ */
+
+/* 2004/03/09 Manuel Novoa III <mjn3@codepoet.org>
+ *
+ * Rewritten to fix several bugs, add additional error checking, and
+ * remove ridiculous amounts of bloat.
+ */
+
+#include <net/route.h>
+#include <net/if.h>
+
+#include "libbb.h"
+#include "inet_common.h"
+
+
+#ifndef RTF_UP
+/* Keep this in sync with /usr/src/linux/include/linux/route.h */
+#define RTF_UP 0x0001 /* route usable */
+#define RTF_GATEWAY 0x0002 /* destination is a gateway */
+#define RTF_HOST 0x0004 /* host entry (net otherwise) */
+#define RTF_REINSTATE 0x0008 /* reinstate route after tmout */
+#define RTF_DYNAMIC 0x0010 /* created dyn. (by redirect) */
+#define RTF_MODIFIED 0x0020 /* modified dyn. (by redirect) */
+#define RTF_MTU 0x0040 /* specific MTU for this route */
+#ifndef RTF_MSS
+#define RTF_MSS RTF_MTU /* Compatibility :-( */
+#endif
+#define RTF_WINDOW 0x0080 /* per route window clamping */
+#define RTF_IRTT 0x0100 /* Initial round trip time */
+#define RTF_REJECT 0x0200 /* Reject route */
+#endif
+
+#if defined(SIOCADDRTOLD) || defined(RTF_IRTT) /* route */
+#define HAVE_NEW_ADDRT 1
+#endif
+
+#if HAVE_NEW_ADDRT
+#define mask_in_addr(x) (((struct sockaddr_in *)&((x).rt_genmask))->sin_addr.s_addr)
+#define full_mask(x) (x)
+#else
+#define mask_in_addr(x) ((x).rt_genmask)
+#define full_mask(x) (((struct sockaddr_in *)&(x))->sin_addr.s_addr)
+#endif
+
+/* The RTACTION entries must agree with tbl_verb[] below! */
+#define RTACTION_ADD 1
+#define RTACTION_DEL 2
+
+/* For the various tbl_*[] arrays, the 1st byte is the offset to
+ * the next entry and the 2nd byte is return value. */
+
+#define NET_FLAG 1
+#define HOST_FLAG 2
+
+/* We remap '-' to '#' to avoid problems with getopt. */
+static const char tbl_hash_net_host[] ALIGN1 =
+ "\007\001#net\0"
+/* "\010\002#host\0" */
+ "\007\002#host" /* Since last, we can save a byte. */
+;
+
+#define KW_TAKES_ARG 020
+#define KW_SETS_FLAG 040
+
+#define KW_IPVx_METRIC 020
+#define KW_IPVx_NETMASK 021
+#define KW_IPVx_GATEWAY 022
+#define KW_IPVx_MSS 023
+#define KW_IPVx_WINDOW 024
+#define KW_IPVx_IRTT 025
+#define KW_IPVx_DEVICE 026
+
+#define KW_IPVx_FLAG_ONLY 040
+#define KW_IPVx_REJECT 040
+#define KW_IPVx_MOD 041
+#define KW_IPVx_DYN 042
+#define KW_IPVx_REINSTATE 043
+
+static const char tbl_ipvx[] ALIGN1 =
+ /* 020 is the "takes an arg" bit */
+#if HAVE_NEW_ADDRT
+ "\011\020metric\0"
+#endif
+ "\012\021netmask\0"
+ "\005\022gw\0"
+ "\012\022gateway\0"
+ "\006\023mss\0"
+ "\011\024window\0"
+#ifdef RTF_IRTT
+ "\007\025irtt\0"
+#endif
+ "\006\026dev\0"
+ "\011\026device\0"
+ /* 040 is the "sets a flag" bit - MUST match flags_ipvx[] values below. */
+#ifdef RTF_REJECT
+ "\011\040reject\0"
+#endif
+ "\006\041mod\0"
+ "\006\042dyn\0"
+/* "\014\043reinstate\0" */
+ "\013\043reinstate" /* Since last, we can save a byte. */
+;
+
+static const int flags_ipvx[] = { /* MUST match tbl_ipvx[] values above. */
+#ifdef RTF_REJECT
+ RTF_REJECT,
+#endif
+ RTF_MODIFIED,
+ RTF_DYNAMIC,
+ RTF_REINSTATE
+};
+
+static int kw_lookup(const char *kwtbl, char ***pargs)
+{
+ if (**pargs) {
+ do {
+ if (strcmp(kwtbl+2, **pargs) == 0) { /* Found a match. */
+ *pargs += 1;
+ if (kwtbl[1] & KW_TAKES_ARG) {
+ if (!**pargs) { /* No more args! */
+ bb_show_usage();
+ }
+ *pargs += 1; /* Calling routine will use args[-1]. */
+ }
+ return kwtbl[1];
+ }
+ kwtbl += *kwtbl;
+ } while (*kwtbl);
+ }
+ return 0;
+}
+
+/* Add or delete a route, depending on action. */
+
+static void INET_setroute(int action, char **args)
+{
+ struct rtentry rt;
+ const char *netmask = NULL;
+ int skfd, isnet, xflag;
+
+ /* Grab the -net or -host options. Remember they were transformed. */
+ xflag = kw_lookup(tbl_hash_net_host, &args);
+
+ /* If we did grab -net or -host, make sure we still have an arg left. */
+ if (*args == NULL) {
+ bb_show_usage();
+ }
+
+ /* Clean out the RTREQ structure. */
+ memset(&rt, 0, sizeof(rt));
+
+ {
+ const char *target = *args++;
+ char *prefix;
+
+ /* recognize x.x.x.x/mask format. */
+ prefix = strchr(target, '/');
+ if (prefix) {
+ int prefix_len;
+
+ prefix_len = xatoul_range(prefix+1, 0, 32);
+ mask_in_addr(rt) = htonl( ~ (0xffffffffUL >> prefix_len));
+ *prefix = '\0';
+#if HAVE_NEW_ADDRT
+ rt.rt_genmask.sa_family = AF_INET;
+#endif
+ } else {
+ /* Default netmask. */
+ netmask = bb_str_default;
+ }
+ /* Prefer hostname lookup is -host flag (xflag==1) was given. */
+ isnet = INET_resolve(target, (struct sockaddr_in *) &rt.rt_dst,
+ (xflag & HOST_FLAG));
+ if (isnet < 0) {
+ bb_error_msg_and_die("resolving %s", target);
+ }
+ if (prefix) {
+ /* do not destroy prefix for process args */
+ *prefix = '/';
+ }
+ }
+
+ if (xflag) { /* Reinit isnet if -net or -host was specified. */
+ isnet = (xflag & NET_FLAG);
+ }
+
+ /* Fill in the other fields. */
+ rt.rt_flags = ((isnet) ? RTF_UP : (RTF_UP | RTF_HOST));
+
+ while (*args) {
+ int k = kw_lookup(tbl_ipvx, &args);
+ const char *args_m1 = args[-1];
+
+ if (k & KW_IPVx_FLAG_ONLY) {
+ rt.rt_flags |= flags_ipvx[k & 3];
+ continue;
+ }
+
+#if HAVE_NEW_ADDRT
+ if (k == KW_IPVx_METRIC) {
+ rt.rt_metric = xatoul(args_m1) + 1;
+ continue;
+ }
+#endif
+
+ if (k == KW_IPVx_NETMASK) {
+ struct sockaddr mask;
+
+ if (mask_in_addr(rt)) {
+ bb_show_usage();
+ }
+
+ netmask = args_m1;
+ isnet = INET_resolve(netmask, (struct sockaddr_in *) &mask, 0);
+ if (isnet < 0) {
+ bb_error_msg_and_die("resolving %s", netmask);
+ }
+ rt.rt_genmask = full_mask(mask);
+ continue;
+ }
+
+ if (k == KW_IPVx_GATEWAY) {
+ if (rt.rt_flags & RTF_GATEWAY) {
+ bb_show_usage();
+ }
+
+ isnet = INET_resolve(args_m1,
+ (struct sockaddr_in *) &rt.rt_gateway, 1);
+ rt.rt_flags |= RTF_GATEWAY;
+
+ if (isnet) {
+ if (isnet < 0) {
+ bb_error_msg_and_die("resolving %s", args_m1);
+ }
+ bb_error_msg_and_die("gateway %s is a NETWORK", args_m1);
+ }
+ continue;
+ }
+
+ if (k == KW_IPVx_MSS) { /* Check valid MSS bounds. */
+ rt.rt_flags |= RTF_MSS;
+ rt.rt_mss = xatoul_range(args_m1, 64, 32768);
+ continue;
+ }
+
+ if (k == KW_IPVx_WINDOW) { /* Check valid window bounds. */
+ rt.rt_flags |= RTF_WINDOW;
+ rt.rt_window = xatoul_range(args_m1, 128, INT_MAX);
+ continue;
+ }
+
+#ifdef RTF_IRTT
+ if (k == KW_IPVx_IRTT) {
+ rt.rt_flags |= RTF_IRTT;
+ rt.rt_irtt = xatoul(args_m1);
+ rt.rt_irtt *= (sysconf(_SC_CLK_TCK) / 100); /* FIXME */
+#if 0 /* FIXME: do we need to check anything of this? */
+ if (rt.rt_irtt < 1 || rt.rt_irtt > (120 * HZ)) {
+ bb_error_msg_and_die("bad irtt");
+ }
+#endif
+ continue;
+ }
+#endif
+
+ /* Device is special in that it can be the last arg specified
+ * and doesn't requre the dev/device keyword in that case. */
+ if (!rt.rt_dev && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) {
+ /* Don't use args_m1 here since args may have changed! */
+ rt.rt_dev = args[-1];
+ continue;
+ }
+
+ /* Nothing matched. */
+ bb_show_usage();
+ }
+
+#ifdef RTF_REJECT
+ if ((rt.rt_flags & RTF_REJECT) && !rt.rt_dev) {
+ rt.rt_dev = (char*)"lo";
+ }
+#endif
+
+ /* sanity checks.. */
+ if (mask_in_addr(rt)) {
+ uint32_t mask = mask_in_addr(rt);
+
+ mask = ~ntohl(mask);
+ if ((rt.rt_flags & RTF_HOST) && mask != 0xffffffff) {
+ bb_error_msg_and_die("netmask %.8x and host route conflict",
+ (unsigned int) mask);
+ }
+ if (mask & (mask + 1)) {
+ bb_error_msg_and_die("bogus netmask %s", netmask);
+ }
+ mask = ((struct sockaddr_in *) &rt.rt_dst)->sin_addr.s_addr;
+ if (mask & ~(uint32_t)mask_in_addr(rt)) {
+ bb_error_msg_and_die("netmask and route address conflict");
+ }
+ }
+
+ /* Fill out netmask if still unset */
+ if ((action == RTACTION_ADD) && (rt.rt_flags & RTF_HOST)) {
+ mask_in_addr(rt) = 0xffffffff;
+ }
+
+ /* Create a socket to the INET kernel. */
+ skfd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+ if (action == RTACTION_ADD)
+ xioctl(skfd, SIOCADDRT, &rt);
+ else
+ xioctl(skfd, SIOCDELRT, &rt);
+
+ if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
+}
+
+#if ENABLE_FEATURE_IPV6
+
+static void INET6_setroute(int action, char **args)
+{
+ struct sockaddr_in6 sa6;
+ struct in6_rtmsg rt;
+ int prefix_len, skfd;
+ const char *devname;
+
+ /* We know args isn't NULL from the check in route_main. */
+ const char *target = *args++;
+
+ if (strcmp(target, bb_str_default) == 0) {
+ prefix_len = 0;
+ memset(&sa6, 0, sizeof(sa6));
+ } else {
+ char *cp;
+ cp = strchr(target, '/'); /* Yes... const to non is ok. */
+ if (cp) {
+ *cp = '\0';
+ prefix_len = xatoul_range(cp + 1, 0, 128);
+ } else {
+ prefix_len = 128;
+ }
+ if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) {
+ bb_error_msg_and_die("resolving %s", target);
+ }
+ }
+
+ /* Clean out the RTREQ structure. */
+ memset(&rt, 0, sizeof(rt));
+
+ memcpy(&rt.rtmsg_dst, sa6.sin6_addr.s6_addr, sizeof(struct in6_addr));
+
+ /* Fill in the other fields. */
+ rt.rtmsg_dst_len = prefix_len;
+ rt.rtmsg_flags = ((prefix_len == 128) ? (RTF_UP|RTF_HOST) : RTF_UP);
+ rt.rtmsg_metric = 1;
+
+ devname = NULL;
+
+ while (*args) {
+ int k = kw_lookup(tbl_ipvx, &args);
+ const char *args_m1 = args[-1];
+
+ if ((k == KW_IPVx_MOD) || (k == KW_IPVx_DYN)) {
+ rt.rtmsg_flags |= flags_ipvx[k & 3];
+ continue;
+ }
+
+ if (k == KW_IPVx_METRIC) {
+ rt.rtmsg_metric = xatoul(args_m1);
+ continue;
+ }
+
+ if (k == KW_IPVx_GATEWAY) {
+ if (rt.rtmsg_flags & RTF_GATEWAY) {
+ bb_show_usage();
+ }
+
+ if (INET6_resolve(args_m1, (struct sockaddr_in6 *) &sa6) < 0) {
+ bb_error_msg_and_die("resolving %s", args_m1);
+ }
+ memcpy(&rt.rtmsg_gateway, sa6.sin6_addr.s6_addr,
+ sizeof(struct in6_addr));
+ rt.rtmsg_flags |= RTF_GATEWAY;
+ continue;
+ }
+
+ /* Device is special in that it can be the last arg specified
+ * and doesn't requre the dev/device keyword in that case. */
+ if (!devname && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) {
+ /* Don't use args_m1 here since args may have changed! */
+ devname = args[-1];
+ continue;
+ }
+
+ /* Nothing matched. */
+ bb_show_usage();
+ }
+
+ /* Create a socket to the INET6 kernel. */
+ skfd = xsocket(AF_INET6, SOCK_DGRAM, 0);
+
+ rt.rtmsg_ifindex = 0;
+
+ if (devname) {
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name));
+ xioctl(skfd, SIOGIFINDEX, &ifr);
+ rt.rtmsg_ifindex = ifr.ifr_ifindex;
+ }
+
+ /* Tell the kernel to accept this route. */
+ if (action == RTACTION_ADD)
+ xioctl(skfd, SIOCADDRT, &rt);
+ else
+ xioctl(skfd, SIOCDELRT, &rt);
+
+ if (ENABLE_FEATURE_CLEAN_UP) close(skfd);
+}
+#endif
+
+static const unsigned flagvals[] = { /* Must agree with flagchars[]. */
+ RTF_GATEWAY,
+ RTF_HOST,
+ RTF_REINSTATE,
+ RTF_DYNAMIC,
+ RTF_MODIFIED,
+#if ENABLE_FEATURE_IPV6
+ RTF_DEFAULT,
+ RTF_ADDRCONF,
+ RTF_CACHE
+#endif
+};
+
+#define IPV4_MASK (RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED)
+#define IPV6_MASK (RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE)
+
+/* Must agree with flagvals[]. */
+static const char flagchars[] ALIGN1 =
+ "GHRDM"
+#if ENABLE_FEATURE_IPV6
+ "DAC"
+#endif
+;
+
+static void set_flags(char *flagstr, int flags)
+{
+ int i;
+
+ *flagstr++ = 'U';
+
+ for (i = 0; (*flagstr = flagchars[i]) != 0; i++) {
+ if (flags & flagvals[i]) {
+ ++flagstr;
+ }
+ }
+}
+
+/* also used in netstat */
+void FAST_FUNC bb_displayroutes(int noresolve, int netstatfmt)
+{
+ char devname[64], flags[16], *sdest, *sgw;
+ unsigned long d, g, m;
+ int flgs, ref, use, metric, mtu, win, ir;
+ struct sockaddr_in s_addr;
+ struct in_addr mask;
+
+ FILE *fp = xfopen_for_read("/proc/net/route");
+
+ printf("Kernel IP routing table\n"
+ "Destination Gateway Genmask Flags %s Iface\n",
+ netstatfmt ? " MSS Window irtt" : "Metric Ref Use");
+
+ if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the first line. */
+ goto ERROR; /* Empty or missing line, or read error. */
+ }
+ while (1) {
+ int r;
+ r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n",
+ devname, &d, &g, &flgs, &ref, &use, &metric, &m,
+ &mtu, &win, &ir);
+ if (r != 11) {
+ if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
+ break;
+ }
+ ERROR:
+ bb_error_msg_and_die("fscanf");
+ }
+
+ if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */
+ continue;
+ }
+
+ set_flags(flags, (flgs & IPV4_MASK));
+#ifdef RTF_REJECT
+ if (flgs & RTF_REJECT) {
+ flags[0] = '!';
+ }
+#endif
+
+ memset(&s_addr, 0, sizeof(struct sockaddr_in));
+ s_addr.sin_family = AF_INET;
+ s_addr.sin_addr.s_addr = d;
+ sdest = INET_rresolve(&s_addr, (noresolve | 0x8000), m); /* 'default' instead of '*' */
+ s_addr.sin_addr.s_addr = g;
+ sgw = INET_rresolve(&s_addr, (noresolve | 0x4000), m); /* Host instead of net */
+ mask.s_addr = m;
+ /* "%15.15s" truncates hostnames, do we really want that? */
+ printf("%-15.15s %-15.15s %-16s%-6s", sdest, sgw, inet_ntoa(mask), flags);
+ free(sdest);
+ free(sgw);
+ if (netstatfmt) {
+ printf("%5d %-5d %6d %s\n", mtu, win, ir, devname);
+ } else {
+ printf("%-6d %-2d %7d %s\n", metric, ref, use, devname);
+ }
+ }
+}
+
+#if ENABLE_FEATURE_IPV6
+
+static void INET6_displayroutes(void)
+{
+ char addr6[128], *naddr6;
+ /* In addr6x, we store both 40-byte ':'-delimited ipv6 addresses.
+ * We read the non-delimited strings into the tail of the buffer
+ * using fscanf and then modify the buffer by shifting forward
+ * while inserting ':'s and the nul terminator for the first string.
+ * Hence the strings are at addr6x and addr6x+40. This generates
+ * _much_ less code than the previous (upstream) approach. */
+ char addr6x[80];
+ char iface[16], flags[16];
+ int iflags, metric, refcnt, use, prefix_len, slen;
+ struct sockaddr_in6 snaddr6;
+
+ FILE *fp = xfopen_for_read("/proc/net/ipv6_route");
+
+ printf("Kernel IPv6 routing table\n%-44s%-40s"
+ "Flags Metric Ref Use Iface\n",
+ "Destination", "Next Hop");
+
+ while (1) {
+ int r;
+ r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n",
+ addr6x+14, &prefix_len, &slen, addr6x+40+7,
+ &metric, &use, &refcnt, &iflags, iface);
+ if (r != 9) {
+ if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */
+ break;
+ }
+ ERROR:
+ bb_error_msg_and_die("fscanf");
+ }
+
+ /* Do the addr6x shift-and-insert changes to ':'-delimit addresses.
+ * For now, always do this to validate the proc route format, even
+ * if the interface is down. */
+ {
+ int i = 0;
+ char *p = addr6x+14;
+
+ do {
+ if (!*p) {
+ if (i == 40) { /* nul terminator for 1st address? */
+ addr6x[39] = 0; /* Fixup... need 0 instead of ':'. */
+ ++p; /* Skip and continue. */
+ continue;
+ }
+ goto ERROR;
+ }
+ addr6x[i++] = *p++;
+ if (!((i+1) % 5)) {
+ addr6x[i++] = ':';
+ }
+ } while (i < 40+28+7);
+ }
+
+ if (!(iflags & RTF_UP)) { /* Skip interfaces that are down. */
+ continue;
+ }
+
+ set_flags(flags, (iflags & IPV6_MASK));
+
+ r = 0;
+ do {
+ inet_pton(AF_INET6, addr6x + r,
+ (struct sockaddr *) &snaddr6.sin6_addr);
+ snaddr6.sin6_family = AF_INET6;
+ naddr6 = INET6_rresolve((struct sockaddr_in6 *) &snaddr6,
+ 0x0fff /* Apparently, upstream never resolves. */
+ );
+
+ if (!r) { /* 1st pass */
+ snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len);
+ r += 40;
+ free(naddr6);
+ } else { /* 2nd pass */
+ /* Print the info. */
+ printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n",
+ addr6, naddr6, flags, metric, refcnt, use, iface);
+ free(naddr6);
+ break;
+ }
+ } while (1);
+ }
+}
+
+#endif
+
+#define ROUTE_OPT_A 0x01
+#define ROUTE_OPT_n 0x02
+#define ROUTE_OPT_e 0x04
+#define ROUTE_OPT_INET6 0x08 /* Not an actual option. See below. */
+
+/* 1st byte is offset to next entry offset. 2nd byte is return value. */
+/* 2nd byte matches RTACTION_* code */
+static const char tbl_verb[] ALIGN1 =
+ "\006\001add\0"
+ "\006\002del\0"
+/* "\011\002delete\0" */
+ "\010\002delete" /* Since it's last, we can save a byte. */
+;
+
+int route_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int route_main(int argc UNUSED_PARAM, char **argv)
+{
+ unsigned opt;
+ int what;
+ char *family;
+ char **p;
+
+ /* First, remap '-net' and '-host' to avoid getopt problems. */
+ p = argv;
+ while (*++p) {
+ if (strcmp(*p, "-net") == 0 || strcmp(*p, "-host") == 0) {
+ p[0][0] = '#';
+ }
+ }
+
+ opt = getopt32(argv, "A:ne", &family);
+
+ if ((opt & ROUTE_OPT_A) && strcmp(family, "inet") != 0) {
+#if ENABLE_FEATURE_IPV6
+ if (strcmp(family, "inet6") == 0) {
+ opt |= ROUTE_OPT_INET6; /* Set flag for ipv6. */
+ } else
+#endif
+ bb_show_usage();
+ }
+
+ argv += optind;
+
+ /* No more args means display the routing table. */
+ if (!*argv) {
+ int noresolve = (opt & ROUTE_OPT_n) ? 0x0fff : 0;
+#if ENABLE_FEATURE_IPV6
+ if (opt & ROUTE_OPT_INET6)
+ INET6_displayroutes();
+ else
+#endif
+ bb_displayroutes(noresolve, opt & ROUTE_OPT_e);
+
+ fflush_stdout_and_exit(EXIT_SUCCESS);
+ }
+
+ /* Check verb. At the moment, must be add, del, or delete. */
+ what = kw_lookup(tbl_verb, &argv);
+ if (!what || !*argv) { /* Unknown verb or no more args. */
+ bb_show_usage();
+ }
+
+#if ENABLE_FEATURE_IPV6
+ if (opt & ROUTE_OPT_INET6)
+ INET6_setroute(what, argv);
+ else
+#endif
+ INET_setroute(what, argv);
+
+ return EXIT_SUCCESS;
+}
diff --git a/networking/slattach.c b/networking/slattach.c
new file mode 100644
index 0000000..d3212bb
--- /dev/null
+++ b/networking/slattach.c
@@ -0,0 +1,245 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Stripped down version of net-tools for busybox.
+ *
+ * Author: Ignacio Garcia Perez (iggarpe at gmail dot com)
+ *
+ * License: GPLv2 or later, see LICENSE file in this tarball.
+ *
+ * There are some differences from the standard net-tools slattach:
+ *
+ * - The -l option is not supported.
+ *
+ * - The -F options allows disabling of RTS/CTS flow control.
+ */
+
+#include "libbb.h"
+#include "libiproute/utils.h" /* invarg() */
+
+struct globals {
+ int handle;
+ int saved_disc;
+ struct termios saved_state;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define handle (G.handle )
+#define saved_disc (G.saved_disc )
+#define saved_state (G.saved_state )
+#define INIT_G() do { } while (0)
+
+
+/*
+ * Save tty state and line discipline
+ *
+ * It is fine here to bail out on errors, since we haven modified anything yet
+ */
+static void save_state(void)
+{
+ /* Save line status */
+ if (tcgetattr(handle, &saved_state) < 0)
+ bb_perror_msg_and_die("get state");
+
+ /* Save line discipline */
+ xioctl(handle, TIOCGETD, &saved_disc);
+}
+
+static int set_termios_state_or_warn(struct termios *state)
+{
+ int ret;
+
+ ret = tcsetattr(handle, TCSANOW, state);
+ if (ret < 0) {
+ bb_perror_msg("set state");
+ return 1; /* used as exitcode */
+ }
+ return 0;
+}
+
+/*
+ * Restore state and line discipline for ALL managed ttys
+ *
+ * Restoring ALL managed ttys is the only way to have a single
+ * hangup delay.
+ *
+ * Go on after errors: we want to restore as many controlled ttys
+ * as possible.
+ */
+static void restore_state_and_exit(int exitcode) NORETURN;
+static void restore_state_and_exit(int exitcode)
+{
+ struct termios state;
+
+ /* Restore line discipline */
+ if (ioctl_or_warn(handle, TIOCSETD, &saved_disc) < 0) {
+ exitcode = 1;
+ }
+
+ /* Hangup */
+ memcpy(&state, &saved_state, sizeof(state));
+ cfsetispeed(&state, B0);
+ cfsetospeed(&state, B0);
+ if (set_termios_state_or_warn(&state))
+ exitcode = 1;
+ sleep(1);
+
+ /* Restore line status */
+ if (set_termios_state_or_warn(&saved_state))
+ exit(EXIT_FAILURE);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ close(handle);
+
+ exit(exitcode);
+}
+
+/*
+ * Set tty state, line discipline and encapsulation
+ */
+static void set_state(struct termios *state, int encap)
+{
+ int disc;
+
+ /* Set line status */
+ if (set_termios_state_or_warn(state))
+ goto bad;
+ /* Set line discliple (N_SLIP always) */
+ disc = N_SLIP;
+ if (ioctl_or_warn(handle, TIOCSETD, &disc) < 0) {
+ goto bad;
+ }
+
+ /* Set encapsulation (SLIP, CSLIP, etc) */
+ if (ioctl_or_warn(handle, SIOCSIFENCAP, &encap) < 0) {
+ bad:
+ restore_state_and_exit(EXIT_FAILURE);
+ }
+}
+
+static void sig_handler(int signo UNUSED_PARAM)
+{
+ restore_state_and_exit(EXIT_SUCCESS);
+}
+
+int slattach_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int slattach_main(int argc UNUSED_PARAM, char **argv)
+{
+ /* Line discipline code table */
+ static const char proto_names[] ALIGN1 =
+ "slip\0" /* 0 */
+ "cslip\0" /* 1 */
+ "slip6\0" /* 2 */
+ "cslip6\0" /* 3 */
+ "adaptive\0" /* 8 */
+ ;
+
+ int i, encap, opt;
+ struct termios state;
+ const char *proto = "cslip";
+ const char *extcmd; /* Command to execute after hangup */
+ const char *baud_str;
+ int baud_code = -1; /* Line baud rate (system code) */
+
+ enum {
+ OPT_p_proto = 1 << 0,
+ OPT_s_baud = 1 << 1,
+ OPT_c_extcmd = 1 << 2,
+ OPT_e_quit = 1 << 3,
+ OPT_h_watch = 1 << 4,
+ OPT_m_nonraw = 1 << 5,
+ OPT_L_local = 1 << 6,
+ OPT_F_noflow = 1 << 7
+ };
+
+ INIT_G();
+
+ /* Parse command line options */
+ opt = getopt32(argv, "p:s:c:ehmLF", &proto, &baud_str, &extcmd);
+ /*argc -= optind;*/
+ argv += optind;
+
+ if (!*argv)
+ bb_show_usage();
+
+ encap = index_in_strings(proto_names, proto);
+
+ if (encap < 0)
+ invarg(proto, "protocol");
+ if (encap > 3)
+ encap = 8;
+
+ /* We want to know if the baud rate is valid before we start touching the ttys */
+ if (opt & OPT_s_baud) {
+ baud_code = tty_value_to_baud(xatoi(baud_str));
+ if (baud_code < 0)
+ invarg(baud_str, "baud rate");
+ }
+
+ /* Trap signals in order to restore tty states upon exit */
+ if (!(opt & OPT_e_quit)) {
+ bb_signals(0
+ + (1 << SIGHUP)
+ + (1 << SIGINT)
+ + (1 << SIGQUIT)
+ + (1 << SIGTERM)
+ , sig_handler);
+ }
+
+ /* Open tty */
+ handle = open(*argv, O_RDWR | O_NDELAY);
+ if (handle < 0) {
+ char *buf = concat_path_file("/dev", *argv);
+ handle = xopen(buf, O_RDWR | O_NDELAY);
+ /* maybe if (ENABLE_FEATURE_CLEAN_UP) ?? */
+ free(buf);
+ }
+
+ /* Save current tty state */
+ save_state();
+
+ /* Configure tty */
+ memcpy(&state, &saved_state, sizeof(state));
+ if (!(opt & OPT_m_nonraw)) { /* raw not suppressed */
+ memset(&state.c_cc, 0, sizeof(state.c_cc));
+ state.c_cc[VMIN] = 1;
+ state.c_iflag = IGNBRK | IGNPAR;
+ state.c_oflag = 0;
+ state.c_lflag = 0;
+ state.c_cflag = CS8 | HUPCL | CREAD
+ | ((opt & OPT_L_local) ? CLOCAL : 0)
+ | ((opt & OPT_F_noflow) ? 0 : CRTSCTS);
+ cfsetispeed(&state, cfgetispeed(&saved_state));
+ cfsetospeed(&state, cfgetospeed(&saved_state));
+ }
+
+ if (opt & OPT_s_baud) {
+ cfsetispeed(&state, baud_code);
+ cfsetospeed(&state, baud_code);
+ }
+
+ set_state(&state, encap);
+
+ /* Exit now if option -e was passed */
+ if (opt & OPT_e_quit)
+ return 0;
+
+ /* If we're not requested to watch, just keep descriptor open
+ * until we are killed */
+ if (!(opt & OPT_h_watch))
+ while (1)
+ sleep(24*60*60);
+
+ /* Watch line for hangup */
+ while (1) {
+ if (ioctl(handle, TIOCMGET, &i) < 0 || !(i & TIOCM_CAR))
+ goto no_carrier;
+ sleep(15);
+ }
+
+ no_carrier:
+
+ /* Execute command on hangup */
+ if (opt & OPT_c_extcmd)
+ system(extcmd);
+
+ /* Restore states and exit */
+ restore_state_and_exit(EXIT_SUCCESS);
+}
diff --git a/networking/tc.c b/networking/tc.c
new file mode 100644
index 0000000..6e31074
--- /dev/null
+++ b/networking/tc.c
@@ -0,0 +1,545 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * tc.c "tc" utility frontend.
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Bernhard Reutner-Fischer adjusted for busybox
+ */
+
+#include "libbb.h"
+
+#include "libiproute/utils.h"
+#include "libiproute/ip_common.h"
+#include "libiproute/rt_names.h"
+#include <linux/pkt_sched.h> /* for the TC_H_* macros */
+
+#define parse_rtattr_nested(tb, max, rta) \
+ (parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta)))
+
+/* nullifies tb on error */
+#define __parse_rtattr_nested_compat(tb, max, rta, len) \
+ ({if ((RTA_PAYLOAD(rta) >= len) && \
+ (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr))) { \
+ rta = RTA_DATA(rta) + RTA_ALIGN(len); \
+ parse_rtattr_nested(tb, max, rta); \
+ } else \
+ memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); \
+ })
+
+#define parse_rtattr_nested_compat(tb, max, rta, data, len) \
+ ({data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \
+ __parse_rtattr_nested_compat(tb, max, rta, len); })
+
+#define show_details (0) /* not implemented. Does anyone need it? */
+#define use_iec (0) /* not currently documented in the upstream manpage */
+
+
+struct globals {
+ int filter_ifindex;
+ __u32 filter_qdisc;
+ __u32 filter_parent;
+ __u32 filter_prio;
+ __u32 filter_proto;
+};
+
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define filter_ifindex (G.filter_ifindex)
+#define filter_qdisc (G.filter_qdisc)
+#define filter_parent (G.filter_parent)
+#define filter_prio (G.filter_prio)
+#define filter_proto (G.filter_proto)
+
+void BUG_tc_globals_too_big(void);
+#define INIT_G() do { \
+ if (sizeof(G) > COMMON_BUFSIZE) \
+ BUG_tc_globals_too_big(); \
+} while (0)
+
+/* Allocates a buffer containing the name of a class id.
+ * The caller must free the returned memory. */
+static char* print_tc_classid(uint32_t cid)
+{
+#if 0 /* IMPOSSIBLE */
+ if (cid == TC_H_ROOT)
+ return xasprintf("root");
+ else
+#endif
+ if (cid == TC_H_UNSPEC)
+ return xasprintf("none");
+ else if (TC_H_MAJ(cid) == 0)
+ return xasprintf(":%x", TC_H_MIN(cid));
+ else if (TC_H_MIN(cid) == 0)
+ return xasprintf("%x:", TC_H_MAJ(cid)>>16);
+ else
+ return xasprintf("%x:%x", TC_H_MAJ(cid)>>16, TC_H_MIN(cid));
+}
+
+/* Get a qdisc handle. Return 0 on success, !0 otherwise. */
+static int get_qdisc_handle(__u32 *h, const char *str) {
+ __u32 maj;
+ char *p;
+
+ maj = TC_H_UNSPEC;
+ if (!strcmp(str, "none"))
+ goto ok;
+ maj = strtoul(str, &p, 16);
+ if (p == str)
+ return 1;
+ maj <<= 16;
+ if (*p != ':' && *p!=0)
+ return 1;
+ ok:
+ *h = maj;
+ return 0;
+}
+
+/* Get class ID. Return 0 on success, !0 otherwise. */
+static int get_tc_classid(__u32 *h, const char *str) {
+ __u32 maj, min;
+ char *p;
+
+ maj = TC_H_ROOT;
+ if (!strcmp(str, "root"))
+ goto ok;
+ maj = TC_H_UNSPEC;
+ if (!strcmp(str, "none"))
+ goto ok;
+ maj = strtoul(str, &p, 16);
+ if (p == str) {
+ if (*p != ':')
+ return 1;
+ maj = 0;
+ }
+ if (*p == ':') {
+ if (maj >= (1<<16))
+ return 1;
+ maj <<= 16;
+ str = p + 1;
+ min = strtoul(str, &p, 16);
+ if (*p != 0 || min >= (1<<16))
+ return 1;
+ maj |= min;
+ } else if (*p != 0)
+ return 1;
+ ok:
+ *h = maj;
+ return 0;
+}
+
+static void print_rate(char *buf, int len, uint32_t rate)
+{
+ double tmp = (double)rate*8;
+
+ if (use_iec) {
+ if (tmp >= 1000.0*1024.0*1024.0)
+ snprintf(buf, len, "%.0fMibit", tmp/1024.0*1024.0);
+ else if (tmp >= 1000.0*1024)
+ snprintf(buf, len, "%.0fKibit", tmp/1024);
+ else
+ snprintf(buf, len, "%.0fbit", tmp);
+ } else {
+ if (tmp >= 1000.0*1000000.0)
+ snprintf(buf, len, "%.0fMbit", tmp/1000000.0);
+ else if (tmp >= 1000.0 * 1000.0)
+ snprintf(buf, len, "%.0fKbit", tmp/1000.0);
+ else
+ snprintf(buf, len, "%.0fbit", tmp);
+ }
+}
+
+/* This is "pfifo_fast". */
+static int prio_parse_opt(int argc, char **argv, struct nlmsghdr *n)
+{
+ return 0;
+}
+static int prio_print_opt(struct rtattr *opt)
+{
+ int i;
+ struct tc_prio_qopt *qopt;
+ struct rtattr *tb[TCA_PRIO_MAX+1];
+
+ if (opt == NULL)
+ return 0;
+ parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt, sizeof(*qopt));
+ if (tb == NULL)
+ return 0;
+ printf("bands %u priomap ", qopt->bands);
+ for (i=0; i<=TC_PRIO_MAX; i++)
+ printf(" %d", qopt->priomap[i]);
+
+ if (tb[TCA_PRIO_MQ])
+ printf(" multiqueue: o%s ",
+ *(unsigned char *)RTA_DATA(tb[TCA_PRIO_MQ]) ? "n" : "ff");
+
+ return 0;
+}
+
+/* Class Based Queue */
+static int cbq_parse_opt(int argc, char **argv, struct nlmsghdr *n)
+{
+ return 0;
+}
+static int cbq_print_opt(struct rtattr *opt)
+{
+ struct rtattr *tb[TCA_CBQ_MAX+1];
+ struct tc_ratespec *r = NULL;
+ struct tc_cbq_lssopt *lss = NULL;
+ struct tc_cbq_wrropt *wrr = NULL;
+ struct tc_cbq_fopt *fopt = NULL;
+ struct tc_cbq_ovl *ovl = NULL;
+ const char * const error = "CBQ: too short %s opt";
+ RESERVE_CONFIG_BUFFER(buf, 64);
+
+ if (opt == NULL)
+ goto done;
+ parse_rtattr_nested(tb, TCA_CBQ_MAX, opt);
+
+ if (tb[TCA_CBQ_RATE]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_RATE]) < sizeof(*r))
+ bb_error_msg(error, "rate");
+ else
+ r = RTA_DATA(tb[TCA_CBQ_RATE]);
+ }
+ if (tb[TCA_CBQ_LSSOPT]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT]) < sizeof(*lss))
+ bb_error_msg(error, "lss");
+ else
+ lss = RTA_DATA(tb[TCA_CBQ_LSSOPT]);
+ }
+ if (tb[TCA_CBQ_WRROPT]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_WRROPT]) < sizeof(*wrr))
+ bb_error_msg(error, "wrr");
+ else
+ wrr = RTA_DATA(tb[TCA_CBQ_WRROPT]);
+ }
+ if (tb[TCA_CBQ_FOPT]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_FOPT]) < sizeof(*fopt))
+ bb_error_msg(error, "fopt");
+ else
+ fopt = RTA_DATA(tb[TCA_CBQ_FOPT]);
+ }
+ if (tb[TCA_CBQ_OVL_STRATEGY]) {
+ if (RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]) < sizeof(*ovl))
+ bb_error_msg("CBQ: too short overlimit strategy %u/%u",
+ (unsigned) RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]),
+ (unsigned) sizeof(*ovl));
+ else
+ ovl = RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY]);
+ }
+
+ if (r) {
+ print_rate(buf, sizeof(buf), r->rate);
+ printf("rate %s ", buf);
+ if (show_details) {
+ printf("cell %ub ", 1<<r->cell_log);
+ if (r->mpu)
+ printf("mpu %ub ", r->mpu);
+ if (r->overhead)
+ printf("overhead %ub ", r->overhead);
+ }
+ }
+ if (lss && lss->flags) {
+ bool comma = false;
+ bb_putchar('(');
+ if (lss->flags&TCF_CBQ_LSS_BOUNDED) {
+ printf("bounded");
+ comma = true;
+ }
+ if (lss->flags&TCF_CBQ_LSS_ISOLATED) {
+ if (comma)
+ bb_putchar(',');
+ printf("isolated");
+ }
+ printf(") ");
+ }
+ if (wrr) {
+ if (wrr->priority != TC_CBQ_MAXPRIO)
+ printf("prio %u", wrr->priority);
+ else
+ printf("prio no-transmit");
+ if (show_details) {
+ printf("/%u ", wrr->cpriority);
+ if (wrr->weight != 1) {
+ print_rate(buf, sizeof(buf), wrr->weight);
+ printf("weight %s ", buf);
+ }
+ if (wrr->allot)
+ printf("allot %ub ", wrr->allot);
+ }
+ }
+ done:
+ RELEASE_CONFIG_BUFFER(buf);
+ return 0;
+}
+
+static int print_qdisc(const struct sockaddr_nl *who UNUSED_PARAM,
+ struct nlmsghdr *hdr, void *arg UNUSED_PARAM)
+{
+ struct tcmsg *msg = NLMSG_DATA(hdr);
+ int len = hdr->nlmsg_len;
+ struct rtattr * tb[TCA_MAX+1];
+ char *name;
+
+ if (hdr->nlmsg_type != RTM_NEWQDISC && hdr->nlmsg_type != RTM_DELQDISC) {
+ /* bb_error_msg("Not a qdisc"); */
+ return 0; /* ??? mimic upstream; should perhaps return -1 */
+ }
+ len -= NLMSG_LENGTH(sizeof(*msg));
+ if (len < 0) {
+ /* bb_error_msg("Wrong len %d", len); */
+ return -1;
+ }
+ /* not the desired interface? */
+ if (filter_ifindex && filter_ifindex != msg->tcm_ifindex)
+ return 0;
+ memset (tb, 0, sizeof(tb));
+ parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len);
+ if (tb[TCA_KIND] == NULL) {
+ /* bb_error_msg("%s: NULL kind", "qdisc"); */
+ return -1;
+ }
+ if (hdr->nlmsg_type == RTM_DELQDISC)
+ printf("deleted ");
+ name = (char*)RTA_DATA(tb[TCA_KIND]);
+ printf("qdisc %s %x: ", name, msg->tcm_handle>>16);
+ if (filter_ifindex == 0)
+ printf("dev %s ", ll_index_to_name(msg->tcm_ifindex));
+ if (msg->tcm_parent == TC_H_ROOT)
+ printf("root ");
+ else if (msg->tcm_parent) {
+ char *classid = print_tc_classid(msg->tcm_parent);
+ printf("parent %s ", classid);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(classid);
+ }
+ if (msg->tcm_info != 1)
+ printf("refcnt %d ", msg->tcm_info);
+ if (tb[TCA_OPTIONS]) {
+ static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0";
+ int qqq = index_in_strings(_q_, name);
+ if (qqq == 0) { /* pfifo_fast aka prio */
+ prio_print_opt(tb[TCA_OPTIONS]);
+ } else if (qqq == 1) { /* class based queueing */
+ cbq_print_opt(tb[TCA_OPTIONS]);
+ } else
+ bb_error_msg("unknown %s", name);
+ }
+ bb_putchar('\n');
+ return 0;
+}
+
+static int print_class(const struct sockaddr_nl *who UNUSED_PARAM,
+ struct nlmsghdr *hdr, void *arg UNUSED_PARAM)
+{
+ struct tcmsg *msg = NLMSG_DATA(hdr);
+ int len = hdr->nlmsg_len;
+ struct rtattr * tb[TCA_MAX+1];
+ char *name, *classid;
+
+ /*XXX Eventually factor out common code */
+
+ if (hdr->nlmsg_type != RTM_NEWTCLASS && hdr->nlmsg_type != RTM_DELTCLASS) {
+ /* bb_error_msg("Not a class"); */
+ return 0; /* ??? mimic upstream; should perhaps return -1 */
+ }
+ len -= NLMSG_LENGTH(sizeof(*msg));
+ if (len < 0) {
+ /* bb_error_msg("Wrong len %d", len); */
+ return -1;
+ }
+ /* not the desired interface? */
+ if (filter_qdisc && TC_H_MAJ(msg->tcm_handle^filter_qdisc))
+ return 0;
+ memset (tb, 0, sizeof(tb));
+ parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len);
+ if (tb[TCA_KIND] == NULL) {
+ /* bb_error_msg("%s: NULL kind", "class"); */
+ return -1;
+ }
+ if (hdr->nlmsg_type == RTM_DELTCLASS)
+ printf("deleted ");
+
+ name = (char*)RTA_DATA(tb[TCA_KIND]);
+ classid = !msg->tcm_handle ? NULL : print_tc_classid(
+ filter_qdisc ? TC_H_MIN(msg->tcm_parent) : msg->tcm_parent);
+ printf ("class %s %s", name, classid);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(classid);
+
+ if (filter_ifindex == 0)
+ printf("dev %s ", ll_index_to_name(msg->tcm_ifindex));
+ if (msg->tcm_parent == TC_H_ROOT)
+ printf("root ");
+ else if (msg->tcm_parent) {
+ classid = print_tc_classid(filter_qdisc ?
+ TC_H_MIN(msg->tcm_parent) : msg->tcm_parent);
+ printf("parent %s ", classid);
+ if (ENABLE_FEATURE_CLEAN_UP)
+ free(classid);
+ }
+ if (msg->tcm_info)
+ printf("leaf %x ", msg->tcm_info >> 16);
+ /* Do that get_qdisc_kind(RTA_DATA(tb[TCA_KIND])). */
+ if (tb[TCA_OPTIONS]) {
+ static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0";
+ int qqq = index_in_strings(_q_, name);
+ if (qqq == 0) { /* pfifo_fast aka prio */
+ /* nothing. */ /*prio_print_opt(tb[TCA_OPTIONS]);*/
+ } else if (qqq == 1) { /* class based queueing */
+ /* cbq_print_copt() is identical to cbq_print_opt(). */
+ cbq_print_opt(tb[TCA_OPTIONS]);
+ } else
+ bb_error_msg("unknown %s", name);
+ }
+ bb_putchar('\n');
+
+ return 0;
+}
+
+static int print_filter(const struct sockaddr_nl *who UNUSED_PARAM,
+ struct nlmsghdr *hdr, void *arg UNUSED_PARAM)
+{
+ struct tcmsg *msg = NLMSG_DATA(hdr);
+ int len = hdr->nlmsg_len;
+ struct rtattr * tb[TCA_MAX+1];
+ return 0;
+}
+
+int tc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tc_main(int argc UNUSED_PARAM, char **argv)
+{
+ static const char objects[] ALIGN1 =
+ "qdisc\0""class\0""filter\0"
+ ;
+ enum { OBJ_qdisc = 0, OBJ_class, OBJ_filter };
+ static const char commands[] ALIGN1 =
+ "add\0""delete\0""change\0"
+ "link\0" /* only qdisc */
+ "replace\0"
+ "show\0""list\0"
+ ;
+ static const char args[] ALIGN1 =
+ "dev\0" /* qdisc, class, filter */
+ "root\0" /* class, filter */
+ "parent\0" /* class, filter */
+ "qdisc\0" /* class */
+ "handle\0" /* change: qdisc, class(classid) list: filter */
+ "classid\0" /* change: for class use "handle" */
+ "preference\0""priority\0""protocol\0" /* filter */
+ ;
+ enum { CMD_add = 0, CMD_del, CMD_change, CMD_link, CMD_replace, CMD_show };
+ enum { ARG_dev = 0, ARG_root, ARG_parent, ARG_qdisc,
+ ARG_handle, ARG_classid, ARG_pref, ARG_prio, ARG_proto};
+ struct rtnl_handle rth;
+ struct tcmsg msg;
+ int ret, obj, cmd, arg;
+ char *dev = NULL;
+
+ INIT_G();
+
+ if (!*++argv)
+ bb_show_usage();
+ xrtnl_open(&rth);
+ ret = EXIT_SUCCESS;
+
+ obj = index_in_substrings(objects, *argv++);
+
+ if (obj < OBJ_qdisc)
+ bb_show_usage();
+ if (!*argv)
+ cmd = CMD_show; /* list is the default */
+ else {
+ cmd = index_in_substrings(commands, *argv);
+ if (cmd < 0)
+ bb_error_msg_and_die(bb_msg_invalid_arg, *argv, applet_name);
+ argv++;
+ }
+ memset(&msg, 0, sizeof(msg));
+ msg.tcm_family = AF_UNSPEC;
+ ll_init_map(&rth);
+ while (*argv) {
+ arg = index_in_substrings(args, *argv);
+ if (arg == ARG_dev) {
+ NEXT_ARG();
+ if (dev)
+ duparg2("dev", *argv);
+ dev = *argv++;
+ msg.tcm_ifindex = xll_name_to_index(dev);
+ if (cmd >= CMD_show)
+ filter_ifindex = msg.tcm_ifindex;
+ } else if ((arg == ARG_qdisc && obj == OBJ_class && cmd >= CMD_show)
+ || (arg == ARG_handle && obj == OBJ_qdisc
+ && cmd == CMD_change)) {
+ NEXT_ARG();
+ /* We don't care about duparg2("qdisc handle",*argv) for now */
+ if (get_qdisc_handle(&filter_qdisc, *argv))
+ invarg(*argv, "qdisc");
+ } else if (obj != OBJ_qdisc &&
+ (arg == ARG_root
+ || arg == ARG_parent
+ || (obj == OBJ_filter && arg >= ARG_pref))) {
+ } else {
+ invarg(*argv, "command");
+ }
+ NEXT_ARG();
+ if (arg == ARG_root) {
+ if (msg.tcm_parent)
+ duparg("parent", *argv);
+ msg.tcm_parent = TC_H_ROOT;
+ if (obj == OBJ_filter)
+ filter_parent = TC_H_ROOT;
+ } else if (arg == ARG_parent) {
+ __u32 handle;
+ if (msg.tcm_parent)
+ duparg(*argv, "parent");
+ if (get_tc_classid(&handle, *argv))
+ invarg(*argv, "parent");
+ msg.tcm_parent = handle;
+ if (obj == OBJ_filter)
+ filter_parent = handle;
+ } else if (arg == ARG_handle) { /* filter::list */
+ if (msg.tcm_handle)
+ duparg(*argv, "handle");
+ /* reject LONG_MIN || LONG_MAX */
+ /* TODO: for fw
+ if ((slash = strchr(handle, '/')) != NULL)
+ *slash = '\0';
+ */
+ if (get_u32(&msg.tcm_handle, *argv, 0))
+ invarg(*argv, "handle");
+ /* if (slash) {if (get_u32(__u32 &mask, slash+1,0)) inv mask;addattr32(n, MAX_MSG, TCA_FW_MASK, mask); */
+ } else if (arg == ARG_classid && obj == OBJ_class && cmd == CMD_change){
+ } else if (arg == ARG_pref || arg == ARG_prio) { /* filter::list */
+ if (filter_prio)
+ duparg(*argv, "priority");
+ if (get_u32(&filter_prio, *argv, 0))
+ invarg(*argv, "priority");
+ } else if (arg == ARG_proto) { /* filter::list */
+ __u16 tmp;
+ if (filter_proto)
+ duparg(*argv, "protocol");
+ if (ll_proto_a2n(&tmp, *argv))
+ invarg(*argv, "protocol");
+ filter_proto = tmp;
+ }
+ }
+ if (cmd >= CMD_show) { /* show or list */
+ if (obj == OBJ_filter)
+ msg.tcm_info = TC_H_MAKE(filter_prio<<16, filter_proto);
+ if (rtnl_dump_request(&rth, obj == OBJ_qdisc ? RTM_GETQDISC :
+ obj == OBJ_class ? RTM_GETTCLASS : RTM_GETTFILTER,
+ &msg, sizeof(msg)) < 0)
+ bb_simple_perror_msg_and_die("cannot send dump request");
+
+ xrtnl_dump_filter(&rth, obj == OBJ_qdisc ? print_qdisc :
+ obj == OBJ_class ? print_class : print_filter,
+ NULL);
+ }
+ if (ENABLE_FEATURE_CLEAN_UP) {
+ rtnl_close(&rth);
+ }
+ return ret;
+}
diff --git a/networking/tcpudp.c b/networking/tcpudp.c
new file mode 100644
index 0000000..3b73f21
--- /dev/null
+++ b/networking/tcpudp.c
@@ -0,0 +1,608 @@
+/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org>
+ * which are released into public domain by the author.
+ * Homepage: http://smarden.sunsite.dk/ipsvd/
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+/* Based on ipsvd-0.12.1. This tcpsvd accepts all options
+ * which are supported by one from ipsvd-0.12.1, but not all are
+ * functional. See help text at the end of this file for details.
+ *
+ * Code inside "#ifdef SSLSVD" is for sslsvd and is currently unused.
+ *
+ * Busybox version exports TCPLOCALADDR instead of
+ * TCPLOCALIP + TCPLOCALPORT pair. ADDR more closely matches reality
+ * (which is "struct sockaddr_XXX". Port is not a separate entity,
+ * it's just a part of (AF_INET[6]) sockaddr!).
+ *
+ * TCPORIGDSTADDR is Busybox-specific addition.
+ *
+ * udp server is hacked up by reusing TCP code. It has the following
+ * limitation inherent in Unix DGRAM sockets implementation:
+ * - local IP address is retrieved (using recvmsg voodoo) but
+ * child's socket is not bound to it (bind cannot be called on
+ * already bound socket). Thus it still can emit outgoing packets
+ * with wrong source IP...
+ * - don't know how to retrieve ORIGDST for udp.
+ */
+
+#include "libbb.h"
+/* Wants <limits.h> etc, thus included after libbb.h: */
+#include <linux/types.h> /* for __be32 etc */
+#include <linux/netfilter_ipv4.h>
+
+// TODO: move into this file:
+#include "tcpudp_perhost.h"
+
+#ifdef SSLSVD
+#include "matrixSsl.h"
+#include "ssl_io.h"
+#endif
+
+struct globals {
+ unsigned verbose;
+ unsigned max_per_host;
+ unsigned cur_per_host;
+ unsigned cnum;
+ unsigned cmax;
+ char **env_cur;
+ char *env_var[1]; /* actually bigger */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define verbose (G.verbose )
+#define max_per_host (G.max_per_host)
+#define cur_per_host (G.cur_per_host)
+#define cnum (G.cnum )
+#define cmax (G.cmax )
+#define env_cur (G.env_cur )
+#define env_var (G.env_var )
+#define INIT_G() do { \
+ cmax = 30; \
+ env_cur = &env_var[0]; \
+} while (0)
+
+
+/* We have to be careful about leaking memory in repeated setenv's */
+static void xsetenv_plain(const char *n, const char *v)
+{
+ char *var = xasprintf("%s=%s", n, v);
+ *env_cur++ = var;
+ putenv(var);
+}
+
+static void xsetenv_proto(const char *proto, const char *n, const char *v)
+{
+ char *var = xasprintf("%s%s=%s", proto, n, v);
+ *env_cur++ = var;
+ putenv(var);
+}
+
+static void undo_xsetenv(void)
+{
+ char **pp = env_cur = &env_var[0];
+ while (*pp) {
+ char *var = *pp;
+ *strchrnul(var, '=') = '\0';
+ unsetenv(var);
+ free(var);
+ *pp++ = NULL;
+ }
+}
+
+static void sig_term_handler(int sig)
+{
+ if (verbose)
+ bb_error_msg("got signal %u, exit", sig);
+ kill_myself_with_sig(sig);
+}
+
+/* Little bloated, but tries to give accurate info how child exited.
+ * Makes easier to spot segfaulting children etc... */
+static void print_waitstat(unsigned pid, int wstat)
+{
+ unsigned e = 0;
+ const char *cause = "?exit";
+
+ if (WIFEXITED(wstat)) {
+ cause++;
+ e = WEXITSTATUS(wstat);
+ } else if (WIFSIGNALED(wstat)) {
+ cause = "signal";
+ e = WTERMSIG(wstat);
+ }
+ bb_error_msg("end %d %s %d", pid, cause, e);
+}
+
+/* Must match getopt32 in main! */
+enum {
+ OPT_c = (1 << 0),
+ OPT_C = (1 << 1),
+ OPT_i = (1 << 2),
+ OPT_x = (1 << 3),
+ OPT_u = (1 << 4),
+ OPT_l = (1 << 5),
+ OPT_E = (1 << 6),
+ OPT_b = (1 << 7),
+ OPT_h = (1 << 8),
+ OPT_p = (1 << 9),
+ OPT_t = (1 << 10),
+ OPT_v = (1 << 11),
+ OPT_V = (1 << 12),
+ OPT_U = (1 << 13), /* from here: sslsvd only */
+ OPT_slash = (1 << 14),
+ OPT_Z = (1 << 15),
+ OPT_K = (1 << 16),
+};
+
+static void connection_status(void)
+{
+ /* "only 1 client max" desn't need this */
+ if (cmax > 1)
+ bb_error_msg("status %u/%u", cnum, cmax);
+}
+
+static void sig_child_handler(int sig UNUSED_PARAM)
+{
+ int wstat;
+ pid_t pid;
+
+ while ((pid = wait_any_nohang(&wstat)) > 0) {
+ if (max_per_host)
+ ipsvd_perhost_remove(pid);
+ if (cnum)
+ cnum--;
+ if (verbose)
+ print_waitstat(pid, wstat);
+ }
+ if (verbose)
+ connection_status();
+}
+
+int tcpudpsvd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tcpudpsvd_main(int argc UNUSED_PARAM, char **argv)
+{
+ char *str_C, *str_t;
+ char *user;
+ struct hcc *hccp;
+ const char *instructs;
+ char *msg_per_host = NULL;
+ unsigned len_per_host = len_per_host; /* gcc */
+#ifndef SSLSVD
+ struct bb_uidgid_t ugid;
+#endif
+ bool tcp;
+ uint16_t local_port;
+ char *preset_local_hostname = NULL;
+ char *remote_hostname = remote_hostname; /* for compiler */
+ char *remote_addr = remote_addr; /* for compiler */
+ len_and_sockaddr *lsa;
+ len_and_sockaddr local, remote;
+ socklen_t sa_len;
+ int pid;
+ int sock;
+ int conn;
+ unsigned backlog = 20;
+
+ INIT_G();
+
+ tcp = (applet_name[0] == 't');
+
+ /* 3+ args, -i at most once, -p implies -h, -v is counter, -b N, -c N */
+ opt_complementary = "-3:i--i:ph:vv:b+:c+";
+#ifdef SSLSVD
+ getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:vU:/:Z:K:",
+ &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname,
+ &backlog, &str_t, &ssluser, &root, &cert, &key, &verbose
+ );
+#else
+ /* "+": stop on first non-option */
+ getopt32(argv, "+c:C:i:x:u:l:Eb:hpt:v",
+ &cmax, &str_C, &instructs, &instructs, &user, &preset_local_hostname,
+ &backlog, &str_t, &verbose
+ );
+#endif
+ if (option_mask32 & OPT_C) { /* -C n[:message] */
+ max_per_host = bb_strtou(str_C, &str_C, 10);
+ if (str_C[0]) {
+ if (str_C[0] != ':')
+ bb_show_usage();
+ msg_per_host = str_C + 1;
+ len_per_host = strlen(msg_per_host);
+ }
+ }
+ if (max_per_host > cmax)
+ max_per_host = cmax;
+ if (option_mask32 & OPT_u) {
+ xget_uidgid(&ugid, user);
+ }
+#ifdef SSLSVD
+ if (option_mask32 & OPT_U) ssluser = optarg;
+ if (option_mask32 & OPT_slash) root = optarg;
+ if (option_mask32 & OPT_Z) cert = optarg;
+ if (option_mask32 & OPT_K) key = optarg;
+#endif
+ argv += optind;
+ if (!argv[0][0] || LONE_CHAR(argv[0], '0'))
+ argv[0] = (char*)"0.0.0.0";
+
+ /* Per-IP flood protection is not thought-out for UDP */
+ if (!tcp)
+ max_per_host = 0;
+
+ bb_sanitize_stdio(); /* fd# 0,1,2 must be opened */
+
+#ifdef SSLSVD
+ sslser = user;
+ client = 0;
+ if ((getuid() == 0) && !(option_mask32 & OPT_u)) {
+ xfunc_exitcode = 100;
+ bb_error_msg_and_die("-U ssluser must be set when running as root");
+ }
+ if (option_mask32 & OPT_u)
+ if (!uidgid_get(&sslugid, ssluser, 1)) {
+ if (errno) {
+ bb_perror_msg_and_die("can't get user/group: %s", ssluser);
+ }
+ bb_error_msg_and_die("unknown user/group %s", ssluser);
+ }
+ if (!cert) cert = "./cert.pem";
+ if (!key) key = cert;
+ if (matrixSslOpen() < 0)
+ fatal("cannot initialize ssl");
+ if (matrixSslReadKeys(&keys, cert, key, 0, ca) < 0) {
+ if (client)
+ fatal("cannot read cert, key, or ca file");
+ fatal("cannot read cert or key file");
+ }
+ if (matrixSslNewSession(&ssl, keys, 0, SSL_FLAGS_SERVER) < 0)
+ fatal("cannot create ssl session");
+#endif
+
+ sig_block(SIGCHLD);
+ signal(SIGCHLD, sig_child_handler);
+ bb_signals(BB_FATAL_SIGS, sig_term_handler);
+ signal(SIGPIPE, SIG_IGN);
+
+ if (max_per_host)
+ ipsvd_perhost_init(cmax);
+
+ local_port = bb_lookup_port(argv[1], tcp ? "tcp" : "udp", 0);
+ lsa = xhost2sockaddr(argv[0], local_port);
+ argv += 2;
+
+ sock = xsocket(lsa->u.sa.sa_family, tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
+ setsockopt_reuseaddr(sock);
+ sa_len = lsa->len; /* I presume sockaddr len stays the same */
+ xbind(sock, &lsa->u.sa, sa_len);
+ if (tcp)
+ xlisten(sock, backlog);
+ else /* udp: needed for recv_from_to to work: */
+ socket_want_pktinfo(sock);
+ /* ndelay_off(sock); - it is the default I think? */
+
+#ifndef SSLSVD
+ if (option_mask32 & OPT_u) {
+ /* drop permissions */
+ xsetgid(ugid.gid);
+ xsetuid(ugid.uid);
+ }
+#endif
+
+ if (verbose) {
+ char *addr = xmalloc_sockaddr2dotted(&lsa->u.sa);
+ bb_error_msg("listening on %s, starting", addr);
+ free(addr);
+#ifndef SSLSVD
+ if (option_mask32 & OPT_u)
+ printf(", uid %u, gid %u",
+ (unsigned)ugid.uid, (unsigned)ugid.gid);
+#endif
+ }
+
+ /* Main accept() loop */
+
+ again:
+ hccp = NULL;
+
+ while (cnum >= cmax)
+ wait_for_any_sig(); /* expecting SIGCHLD */
+
+ /* Accept a connection to fd #0 */
+ again1:
+ close(0);
+ again2:
+ sig_unblock(SIGCHLD);
+ local.len = remote.len = sa_len;
+ if (tcp) {
+ conn = accept(sock, &remote.u.sa, &remote.len);
+ } else {
+ /* In case recv_from_to won't be able to recover local addr.
+ * Also sets port - recv_from_to is unable to do it. */
+ local = *lsa;
+ conn = recv_from_to(sock, NULL, 0, MSG_PEEK,
+ &remote.u.sa, &local.u.sa, sa_len);
+ }
+ sig_block(SIGCHLD);
+ if (conn < 0) {
+ if (errno != EINTR)
+ bb_perror_msg(tcp ? "accept" : "recv");
+ goto again2;
+ }
+ xmove_fd(tcp ? conn : sock, 0);
+
+ if (max_per_host) {
+ /* Drop connection immediately if cur_per_host > max_per_host
+ * (minimizing load under SYN flood) */
+ remote_addr = xmalloc_sockaddr2dotted_noport(&remote.u.sa);
+ cur_per_host = ipsvd_perhost_add(remote_addr, max_per_host, &hccp);
+ if (cur_per_host > max_per_host) {
+ /* ipsvd_perhost_add detected that max is exceeded
+ * (and did not store ip in connection table) */
+ free(remote_addr);
+ if (msg_per_host) {
+ /* don't block or test for errors */
+ send(0, msg_per_host, len_per_host, MSG_DONTWAIT);
+ }
+ goto again1;
+ }
+ /* NB: remote_addr is not leaked, it is stored in conn table */
+ }
+
+ if (!tcp) {
+ /* Voodoo magic: making udp sockets each receive its own
+ * packets is not trivial, and I still not sure
+ * I do it 100% right.
+ * 1) we have to do it before fork()
+ * 2) order is important - is it right now? */
+
+ /* Open new non-connected UDP socket for further clients... */
+ sock = xsocket(lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+ setsockopt_reuseaddr(sock);
+ /* Make plain write/send work for old socket by supplying default
+ * destination address. This also restricts incoming packets
+ * to ones coming from this remote IP. */
+ xconnect(0, &remote.u.sa, sa_len);
+ /* hole? at this point we have no wildcard udp socket...
+ * can this cause clients to get "port unreachable" icmp?
+ * Yup, time window is very small, but it exists (is it?) */
+ /* ..."open new socket", continued */
+ xbind(sock, &lsa->u.sa, sa_len);
+ socket_want_pktinfo(sock);
+
+ /* Doesn't work:
+ * we cannot replace fd #0 - we will lose pending packet
+ * which is already buffered for us! And we cannot use fd #1
+ * instead - it will "intercept" all following packets, but child
+ * does not expect data coming *from fd #1*! */
+#if 0
+ /* Make it so that local addr is fixed to localp->u.sa
+ * and we don't accidentally accept packets to other local IPs. */
+ /* NB: we possibly bind to the _very_ same_ address & port as the one
+ * already bound in parent! This seems to work in Linux.
+ * (otherwise we can move socket to fd #0 only if bind succeeds) */
+ close(0);
+ set_nport(localp, htons(local_port));
+ xmove_fd(xsocket(localp->u.sa.sa_family, SOCK_DGRAM, 0), 0);
+ setsockopt_reuseaddr(0); /* crucial */
+ xbind(0, &localp->u.sa, localp->len);
+#endif
+ }
+
+ pid = vfork();
+ if (pid == -1) {
+ bb_perror_msg("vfork");
+ goto again;
+ }
+
+ if (pid != 0) {
+ /* Parent */
+ cnum++;
+ if (verbose)
+ connection_status();
+ if (hccp)
+ hccp->pid = pid;
+ /* clean up changes done by vforked child */
+ undo_xsetenv();
+ goto again;
+ }
+
+ /* Child: prepare env, log, and exec prog */
+
+ /* Closing tcp listening socket */
+ if (tcp)
+ close(sock);
+
+ { /* vfork alert! every xmalloc in this block should be freed! */
+ char *local_hostname = local_hostname; /* for compiler */
+ char *local_addr = NULL;
+ char *free_me0 = NULL;
+ char *free_me1 = NULL;
+ char *free_me2 = NULL;
+
+ if (verbose || !(option_mask32 & OPT_E)) {
+ if (!max_per_host) /* remote_addr is not yet known */
+ free_me0 = remote_addr = xmalloc_sockaddr2dotted(&remote.u.sa);
+ if (option_mask32 & OPT_h) {
+ free_me1 = remote_hostname = xmalloc_sockaddr2host_noport(&remote.u.sa);
+ if (!remote_hostname) {
+ bb_error_msg("cannot look up hostname for %s", remote_addr);
+ remote_hostname = remote_addr;
+ }
+ }
+ /* Find out local IP peer connected to.
+ * Errors ignored (I'm not paranoid enough to imagine kernel
+ * which doesn't know local IP). */
+ if (tcp)
+ getsockname(0, &local.u.sa, &local.len);
+ /* else: for UDP it is done earlier by parent */
+ local_addr = xmalloc_sockaddr2dotted(&local.u.sa);
+ if (option_mask32 & OPT_h) {
+ local_hostname = preset_local_hostname;
+ if (!local_hostname) {
+ free_me2 = local_hostname = xmalloc_sockaddr2host_noport(&local.u.sa);
+ if (!local_hostname)
+ bb_error_msg_and_die("cannot look up hostname for %s", local_addr);
+ }
+ /* else: local_hostname is not NULL, but is NOT malloced! */
+ }
+ }
+ if (verbose) {
+ pid = getpid();
+ if (max_per_host) {
+ bb_error_msg("concurrency %s %u/%u",
+ remote_addr,
+ cur_per_host, max_per_host);
+ }
+ bb_error_msg((option_mask32 & OPT_h)
+ ? "start %u %s-%s (%s-%s)"
+ : "start %u %s-%s",
+ pid,
+ local_addr, remote_addr,
+ local_hostname, remote_hostname);
+ }
+
+ if (!(option_mask32 & OPT_E)) {
+ /* setup ucspi env */
+ const char *proto = tcp ? "TCP" : "UDP";
+
+ /* Extract "original" destination addr:port
+ * from Linux firewall. Useful when you redirect
+ * an outbond connection to local handler, and it needs
+ * to know where it originally tried to connect */
+ if (tcp && getsockopt(0, SOL_IP, SO_ORIGINAL_DST, &local.u.sa, &local.len) == 0) {
+ char *addr = xmalloc_sockaddr2dotted(&local.u.sa);
+ xsetenv_plain("TCPORIGDSTADDR", addr);
+ free(addr);
+ }
+ xsetenv_plain("PROTO", proto);
+ xsetenv_proto(proto, "LOCALADDR", local_addr);
+ xsetenv_proto(proto, "REMOTEADDR", remote_addr);
+ if (option_mask32 & OPT_h) {
+ xsetenv_proto(proto, "LOCALHOST", local_hostname);
+ xsetenv_proto(proto, "REMOTEHOST", remote_hostname);
+ }
+ //compat? xsetenv_proto(proto, "REMOTEINFO", "");
+ /* additional */
+ if (cur_per_host > 0) /* can not be true for udp */
+ xsetenv_plain("TCPCONCURRENCY", utoa(cur_per_host));
+ }
+ free(local_addr);
+ free(free_me0);
+ free(free_me1);
+ free(free_me2);
+ }
+
+ xdup2(0, 1);
+
+ signal(SIGPIPE, SIG_DFL); /* this one was SIG_IGNed */
+ /* Non-ignored signals revert to SIG_DFL on exec anyway */
+ /*signal(SIGCHLD, SIG_DFL);*/
+ sig_unblock(SIGCHLD);
+
+#ifdef SSLSVD
+ strcpy(id, utoa(pid));
+ ssl_io(0, argv);
+#else
+ BB_EXECVP(argv[0], argv);
+#endif
+ bb_perror_msg_and_die("exec '%s'", argv[0]);
+}
+
+/*
+tcpsvd [-hpEvv] [-c n] [-C n:msg] [-b n] [-u user] [-l name]
+ [-i dir|-x cdb] [ -t sec] host port prog
+
+tcpsvd creates a TCP/IP socket, binds it to the address host:port,
+and listens on the socket for incoming connections.
+
+On each incoming connection, tcpsvd conditionally runs a program,
+with standard input reading from the socket, and standard output
+writing to the socket, to handle this connection. tcpsvd keeps
+listening on the socket for new connections, and can handle
+multiple connections simultaneously.
+
+tcpsvd optionally checks for special instructions depending
+on the IP address or hostname of the client that initiated
+the connection, see ipsvd-instruct(5).
+
+host
+ host either is a hostname, or a dotted-decimal IP address,
+ or 0. If host is 0, tcpsvd accepts connections to any local
+ IP address.
+ * busybox accepts IPv6 addresses and host:port pairs too
+ In this case second parameter is ignored
+port
+ tcpsvd accepts connections to host:port. port may be a name
+ from /etc/services or a number.
+prog
+ prog consists of one or more arguments. For each connection,
+ tcpsvd normally runs prog, with file descriptor 0 reading from
+ the network, and file descriptor 1 writing to the network.
+ By default it also sets up TCP-related environment variables,
+ see tcp-environ(5)
+-i dir
+ read instructions for handling new connections from the instructions
+ directory dir. See ipsvd-instruct(5) for details.
+ * ignored by busyboxed version
+-x cdb
+ read instructions for handling new connections from the constant database
+ cdb. The constant database normally is created from an instructions
+ directory by running ipsvd-cdb(8).
+ * ignored by busyboxed version
+-t sec
+ timeout. This option only takes effect if the -i option is given.
+ While checking the instructions directory, check the time of last access
+ of the file that matches the clients address or hostname if any, discard
+ and remove the file if it wasn't accessed within the last sec seconds;
+ tcpsvd does not discard or remove a file if the user's write permission
+ is not set, for those files the timeout is disabled. Default is 0,
+ which means that the timeout is disabled.
+ * ignored by busyboxed version
+-l name
+ local hostname. Do not look up the local hostname in DNS, but use name
+ as hostname. This option must be set if tcpsvd listens on port 53
+ to avoid loops.
+-u user[:group]
+ drop permissions. Switch user ID to user's UID, and group ID to user's
+ primary GID after creating and binding to the socket. If user is followed
+ by a colon and a group name, the group ID is switched to the GID of group
+ instead. All supplementary groups are removed.
+-c n
+ concurrency. Handle up to n connections simultaneously. Default is 30.
+ If there are n connections active, tcpsvd defers acceptance of a new
+ connection until an active connection is closed.
+-C n[:msg]
+ per host concurrency. Allow only up to n connections from the same IP
+ address simultaneously. If there are n active connections from one IP
+ address, new incoming connections from this IP address are closed
+ immediately. If n is followed by :msg, the message msg is written
+ to the client if possible, before closing the connection. By default
+ msg is empty. See ipsvd-instruct(5) for supported escape sequences in msg.
+
+ For each accepted connection, the current per host concurrency is
+ available through the environment variable TCPCONCURRENCY. n and msg
+ can be overwritten by ipsvd(7) instructions, see ipsvd-instruct(5).
+ By default tcpsvd doesn't keep track of connections.
+-h
+ Look up the client's hostname in DNS.
+-p
+ paranoid. After looking up the client's hostname in DNS, look up the IP
+ addresses in DNS for that hostname, and forget about the hostname
+ if none of the addresses match the client's IP address. You should
+ set this option if you use hostname based instructions. The -p option
+ implies the -h option.
+ * ignored by busyboxed version
+-b n
+ backlog. Allow a backlog of approximately n TCP SYNs. On some systems n
+ is silently limited. Default is 20.
+-E
+ no special environment. Do not set up TCP-related environment variables.
+-v
+ verbose. Print verbose messsages to standard output.
+-vv
+ more verbose. Print more verbose messages to standard output.
+ * no difference between -v and -vv in busyboxed version
+*/
diff --git a/networking/tcpudp_perhost.c b/networking/tcpudp_perhost.c
new file mode 100644
index 0000000..3005f12
--- /dev/null
+++ b/networking/tcpudp_perhost.c
@@ -0,0 +1,65 @@
+/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org>
+ * which are released into public domain by the author.
+ * Homepage: http://smarden.sunsite.dk/ipsvd/
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#include "libbb.h"
+#include "tcpudp_perhost.h"
+
+static struct hcc *cc;
+static unsigned cclen;
+
+/* to be optimized */
+
+void ipsvd_perhost_init(unsigned c)
+{
+// free(cc);
+ cc = xzalloc(c * sizeof(*cc));
+ cclen = c;
+}
+
+unsigned ipsvd_perhost_add(char *ip, unsigned maxconn, struct hcc **hccpp)
+{
+ unsigned i;
+ unsigned conn = 1;
+ int freepos = -1;
+
+ for (i = 0; i < cclen; ++i) {
+ if (!cc[i].ip) {
+ freepos = i;
+ continue;
+ }
+ if (strcmp(cc[i].ip, ip) == 0) {
+ conn++;
+ continue;
+ }
+ }
+ if (freepos == -1) return 0;
+ if (conn <= maxconn) {
+ cc[freepos].ip = ip;
+ *hccpp = &cc[freepos];
+ }
+ return conn;
+}
+
+void ipsvd_perhost_remove(int pid)
+{
+ unsigned i;
+ for (i = 0; i < cclen; ++i) {
+ if (cc[i].pid == pid) {
+ free(cc[i].ip);
+ cc[i].ip = NULL;
+ cc[i].pid = 0;
+ return;
+ }
+ }
+}
+
+//void ipsvd_perhost_free(void)
+//{
+// free(cc);
+//}
diff --git a/networking/tcpudp_perhost.h b/networking/tcpudp_perhost.h
new file mode 100644
index 0000000..2e093c1
--- /dev/null
+++ b/networking/tcpudp_perhost.h
@@ -0,0 +1,37 @@
+/* Based on ipsvd utilities written by Gerrit Pape <pape@smarden.org>
+ * which are released into public domain by the author.
+ * Homepage: http://smarden.sunsite.dk/ipsvd/
+ *
+ * Copyright (C) 2007 Denys Vlasenko.
+ *
+ * Licensed under GPLv2, see file LICENSE in this tarball for details.
+ */
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+struct hcc {
+ char *ip;
+ int pid;
+};
+
+void ipsvd_perhost_init(unsigned);
+
+/* Returns number of already opened connects to this ips, including this one.
+ * ip should be a malloc'ed ptr.
+ * If return value is <= maxconn, ip is inserted into the table
+ * and pointer to table entry if stored in *hccpp
+ * (useful for storing pid later).
+ * Else ip is NOT inserted (you must take care of it - free() etc) */
+unsigned ipsvd_perhost_add(char *ip, unsigned maxconn, struct hcc **hccpp);
+
+/* Finds and frees element with pid */
+void ipsvd_perhost_remove(int pid);
+
+//unsigned ipsvd_perhost_setpid(int pid);
+//void ipsvd_perhost_free(void);
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
diff --git a/networking/telnet.c b/networking/telnet.c
new file mode 100644
index 0000000..5d7ecef
--- /dev/null
+++ b/networking/telnet.c
@@ -0,0 +1,649 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * telnet implementation for busybox
+ *
+ * Author: Tomi Ollila <too@iki.fi>
+ * Copyright (C) 1994-2000 by Tomi Ollila
+ *
+ * Created: Thu Apr 7 13:29:41 1994 too
+ * Last modified: Fri Jun 9 14:34:24 2000 too
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ *
+ * HISTORY
+ * Revision 3.1 1994/04/17 11:31:54 too
+ * initial revision
+ * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen <andersen@codepoet.org>
+ * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan
+ * <jam@ltsp.org>
+ * Modified 2004/02/11 to add ability to pass the USER variable to remote host
+ * by Fernando Silveira <swrh@gmx.net>
+ *
+ */
+
+#include <termios.h>
+#include <arpa/telnet.h>
+#include <netinet/in.h>
+#include "libbb.h"
+
+#ifdef DOTRACE
+#define TRACE(x, y) do { if (x) printf y; } while (0)
+#else
+#define TRACE(x, y)
+#endif
+
+enum {
+ DATABUFSIZE = 128,
+ IACBUFSIZE = 128,
+
+ CHM_TRY = 0,
+ CHM_ON = 1,
+ CHM_OFF = 2,
+
+ UF_ECHO = 0x01,
+ UF_SGA = 0x02,
+
+ TS_0 = 1,
+ TS_IAC = 2,
+ TS_OPT = 3,
+ TS_SUB1 = 4,
+ TS_SUB2 = 5,
+};
+
+typedef unsigned char byte;
+
+enum { netfd = 3 };
+
+struct globals {
+ int iaclen; /* could even use byte, but it's a loss on x86 */
+ byte telstate; /* telnet negotiation state from network input */
+ byte telwish; /* DO, DONT, WILL, WONT */
+ byte charmode;
+ byte telflags;
+ byte do_termios;
+#if ENABLE_FEATURE_TELNET_TTYPE
+ char *ttype;
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+ const char *autologin;
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+ unsigned win_width, win_height;
+#endif
+ /* same buffer used both for network and console read/write */
+ char buf[DATABUFSIZE];
+ /* buffer to handle telnet negotiations */
+ char iacbuf[IACBUFSIZE];
+ struct termios termios_def;
+ struct termios termios_raw;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+void BUG_telnet_globals_too_big(void);
+#define INIT_G() do { \
+ if (sizeof(G) > COMMON_BUFSIZE) \
+ BUG_telnet_globals_too_big(); \
+ /* memset(&G, 0, sizeof G); - already is */ \
+} while (0)
+
+/* Function prototypes */
+static void rawmode(void);
+static void cookmode(void);
+static void do_linemode(void);
+static void will_charmode(void);
+static void telopt(byte c);
+static int subneg(byte c);
+
+static void iacflush(void)
+{
+ write(netfd, G.iacbuf, G.iaclen);
+ G.iaclen = 0;
+}
+
+#define write_str(fd, str) write(fd, str, sizeof(str) - 1)
+
+static void doexit(int ev) NORETURN;
+static void doexit(int ev)
+{
+ cookmode();
+ exit(ev);
+}
+
+static void conescape(void)
+{
+ char b;
+
+ if (bb_got_signal) /* came from line mode... go raw */
+ rawmode();
+
+ write_str(1, "\r\nConsole escape. Commands are:\r\n\n"
+ " l go to line mode\r\n"
+ " c go to character mode\r\n"
+ " z suspend telnet\r\n"
+ " e exit telnet\r\n");
+
+ if (read(STDIN_FILENO, &b, 1) <= 0)
+ doexit(EXIT_FAILURE);
+
+ switch (b) {
+ case 'l':
+ if (!bb_got_signal) {
+ do_linemode();
+ goto rrturn;
+ }
+ break;
+ case 'c':
+ if (bb_got_signal) {
+ will_charmode();
+ goto rrturn;
+ }
+ break;
+ case 'z':
+ cookmode();
+ kill(0, SIGTSTP);
+ rawmode();
+ break;
+ case 'e':
+ doexit(EXIT_SUCCESS);
+ }
+
+ write_str(1, "continuing...\r\n");
+
+ if (bb_got_signal)
+ cookmode();
+
+ rrturn:
+ bb_got_signal = 0;
+
+}
+
+static void handlenetoutput(int len)
+{
+ /* here we could do smart tricks how to handle 0xFF:s in output
+ * stream like writing twice every sequence of FF:s (thus doing
+ * many write()s. But I think interactive telnet application does
+ * not need to be 100% 8-bit clean, so changing every 0xff:s to
+ * 0x7f:s
+ *
+ * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com)
+ * I don't agree.
+ * first - I cannot use programs like sz/rz
+ * second - the 0x0D is sent as one character and if the next
+ * char is 0x0A then it's eaten by a server side.
+ * third - whay doy you have to make 'many write()s'?
+ * I don't understand.
+ * So I implemented it. It's realy useful for me. I hope that
+ * others people will find it interesting too.
+ */
+
+ int i, j;
+ byte * p = (byte*)G.buf;
+ byte outbuf[4*DATABUFSIZE];
+
+ for (i = len, j = 0; i > 0; i--, p++) {
+ if (*p == 0x1d) {
+ conescape();
+ return;
+ }
+ outbuf[j++] = *p;
+ if (*p == 0xff)
+ outbuf[j++] = 0xff;
+ else if (*p == 0x0d)
+ outbuf[j++] = 0x00;
+ }
+ if (j > 0)
+ write(netfd, outbuf, j);
+}
+
+static void handlenetinput(int len)
+{
+ int i;
+ int cstart = 0;
+
+ for (i = 0; i < len; i++) {
+ byte c = G.buf[i];
+
+ if (G.telstate == 0) { /* most of the time state == 0 */
+ if (c == IAC) {
+ cstart = i;
+ G.telstate = TS_IAC;
+ }
+ } else
+ switch (G.telstate) {
+ case TS_0:
+ if (c == IAC)
+ G.telstate = TS_IAC;
+ else
+ G.buf[cstart++] = c;
+ break;
+
+ case TS_IAC:
+ if (c == IAC) { /* IAC IAC -> 0xFF */
+ G.buf[cstart++] = c;
+ G.telstate = TS_0;
+ break;
+ }
+ /* else */
+ switch (c) {
+ case SB:
+ G.telstate = TS_SUB1;
+ break;
+ case DO:
+ case DONT:
+ case WILL:
+ case WONT:
+ G.telwish = c;
+ G.telstate = TS_OPT;
+ break;
+ default:
+ G.telstate = TS_0; /* DATA MARK must be added later */
+ }
+ break;
+ case TS_OPT: /* WILL, WONT, DO, DONT */
+ telopt(c);
+ G.telstate = TS_0;
+ break;
+ case TS_SUB1: /* Subnegotiation */
+ case TS_SUB2: /* Subnegotiation */
+ if (subneg(c))
+ G.telstate = TS_0;
+ break;
+ }
+ }
+ if (G.telstate) {
+ if (G.iaclen) iacflush();
+ if (G.telstate == TS_0) G.telstate = 0;
+ len = cstart;
+ }
+
+ if (len)
+ write(STDOUT_FILENO, G.buf, len);
+}
+
+static void putiac(int c)
+{
+ G.iacbuf[G.iaclen++] = c;
+}
+
+static void putiac2(byte wwdd, byte c)
+{
+ if (G.iaclen + 3 > IACBUFSIZE)
+ iacflush();
+
+ putiac(IAC);
+ putiac(wwdd);
+ putiac(c);
+}
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void putiac_subopt(byte c, char *str)
+{
+ int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 )
+
+ if (G.iaclen + len > IACBUFSIZE)
+ iacflush();
+
+ putiac(IAC);
+ putiac(SB);
+ putiac(c);
+ putiac(0);
+
+ while (*str)
+ putiac(*str++);
+
+ putiac(IAC);
+ putiac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void putiac_subopt_autologin(void)
+{
+ int len = strlen(G.autologin) + 6; // (2 + 1 + 1 + strlen + 2)
+ const char *user = "USER";
+
+ if (G.iaclen + len > IACBUFSIZE)
+ iacflush();
+
+ putiac(IAC);
+ putiac(SB);
+ putiac(TELOPT_NEW_ENVIRON);
+ putiac(TELQUAL_IS);
+ putiac(NEW_ENV_VAR);
+
+ while (*user)
+ putiac(*user++);
+
+ putiac(NEW_ENV_VALUE);
+
+ while (*G.autologin)
+ putiac(*G.autologin++);
+
+ putiac(IAC);
+ putiac(SE);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void putiac_naws(byte c, int x, int y)
+{
+ if (G.iaclen + 9 > IACBUFSIZE)
+ iacflush();
+
+ putiac(IAC);
+ putiac(SB);
+ putiac(c);
+
+ putiac((x >> 8) & 0xff);
+ putiac(x & 0xff);
+ putiac((y >> 8) & 0xff);
+ putiac(y & 0xff);
+
+ putiac(IAC);
+ putiac(SE);
+}
+#endif
+
+static char const escapecharis[] ALIGN1 = "\r\nEscape character is ";
+
+static void setConMode(void)
+{
+ if (G.telflags & UF_ECHO) {
+ if (G.charmode == CHM_TRY) {
+ G.charmode = CHM_ON;
+ printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis);
+ rawmode();
+ }
+ } else {
+ if (G.charmode != CHM_OFF) {
+ G.charmode = CHM_OFF;
+ printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis);
+ cookmode();
+ }
+ }
+}
+
+static void will_charmode(void)
+{
+ G.charmode = CHM_TRY;
+ G.telflags |= (UF_ECHO | UF_SGA);
+ setConMode();
+
+ putiac2(DO, TELOPT_ECHO);
+ putiac2(DO, TELOPT_SGA);
+ iacflush();
+}
+
+static void do_linemode(void)
+{
+ G.charmode = CHM_TRY;
+ G.telflags &= ~(UF_ECHO | UF_SGA);
+ setConMode();
+
+ putiac2(DONT, TELOPT_ECHO);
+ putiac2(DONT, TELOPT_SGA);
+ iacflush();
+}
+
+static void to_notsup(char c)
+{
+ if (G.telwish == WILL)
+ putiac2(DONT, c);
+ else if (G.telwish == DO)
+ putiac2(WONT, c);
+}
+
+static void to_echo(void)
+{
+ /* if server requests ECHO, don't agree */
+ if (G.telwish == DO) {
+ putiac2(WONT, TELOPT_ECHO);
+ return;
+ }
+ if (G.telwish == DONT)
+ return;
+
+ if (G.telflags & UF_ECHO) {
+ if (G.telwish == WILL)
+ return;
+ } else if (G.telwish == WONT)
+ return;
+
+ if (G.charmode != CHM_OFF)
+ G.telflags ^= UF_ECHO;
+
+ if (G.telflags & UF_ECHO)
+ putiac2(DO, TELOPT_ECHO);
+ else
+ putiac2(DONT, TELOPT_ECHO);
+
+ setConMode();
+ write_str(1, "\r\n"); /* sudden modec */
+}
+
+static void to_sga(void)
+{
+ /* daemon always sends will/wont, client do/dont */
+
+ if (G.telflags & UF_SGA) {
+ if (G.telwish == WILL)
+ return;
+ } else if (G.telwish == WONT)
+ return;
+
+ G.telflags ^= UF_SGA; /* toggle */
+ if (G.telflags & UF_SGA)
+ putiac2(DO, TELOPT_SGA);
+ else
+ putiac2(DONT, TELOPT_SGA);
+}
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+static void to_ttype(void)
+{
+ /* Tell server we will (or won't) do TTYPE */
+
+ if (G.ttype)
+ putiac2(WILL, TELOPT_TTYPE);
+ else
+ putiac2(WONT, TELOPT_TTYPE);
+}
+#endif
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+static void to_new_environ(void)
+{
+ /* Tell server we will (or will not) do AUTOLOGIN */
+
+ if (G.autologin)
+ putiac2(WILL, TELOPT_NEW_ENVIRON);
+ else
+ putiac2(WONT, TELOPT_NEW_ENVIRON);
+}
+#endif
+
+#if ENABLE_FEATURE_AUTOWIDTH
+static void to_naws(void)
+{
+ /* Tell server we will do NAWS */
+ putiac2(WILL, TELOPT_NAWS);
+}
+#endif
+
+static void telopt(byte c)
+{
+ switch (c) {
+ case TELOPT_ECHO:
+ to_echo(); break;
+ case TELOPT_SGA:
+ to_sga(); break;
+#if ENABLE_FEATURE_TELNET_TTYPE
+ case TELOPT_TTYPE:
+ to_ttype(); break;
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+ case TELOPT_NEW_ENVIRON:
+ to_new_environ(); break;
+#endif
+#if ENABLE_FEATURE_AUTOWIDTH
+ case TELOPT_NAWS:
+ to_naws();
+ putiac_naws(c, G.win_width, G.win_height);
+ break;
+#endif
+ default:
+ to_notsup(c);
+ break;
+ }
+}
+
+/* subnegotiation -- ignore all (except TTYPE,NAWS) */
+static int subneg(byte c)
+{
+ switch (G.telstate) {
+ case TS_SUB1:
+ if (c == IAC)
+ G.telstate = TS_SUB2;
+#if ENABLE_FEATURE_TELNET_TTYPE
+ else
+ if (c == TELOPT_TTYPE)
+ putiac_subopt(TELOPT_TTYPE, G.ttype);
+#endif
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+ else
+ if (c == TELOPT_NEW_ENVIRON)
+ putiac_subopt_autologin();
+#endif
+ break;
+ case TS_SUB2:
+ if (c == SE)
+ return TRUE;
+ G.telstate = TS_SUB1;
+ /* break; */
+ }
+ return FALSE;
+}
+
+static void rawmode(void)
+{
+ if (G.do_termios)
+ tcsetattr(0, TCSADRAIN, &G.termios_raw);
+}
+
+static void cookmode(void)
+{
+ if (G.do_termios)
+ tcsetattr(0, TCSADRAIN, &G.termios_def);
+}
+
+/* poll gives smaller (-70 bytes) code */
+#define USE_POLL 1
+
+int telnet_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int telnet_main(int argc UNUSED_PARAM, char **argv)
+{
+ char *host;
+ int port;
+ int len;
+#ifdef USE_POLL
+ struct pollfd ufds[2];
+#else
+ fd_set readfds;
+ int maxfd;
+#endif
+
+ INIT_G();
+
+#if ENABLE_FEATURE_AUTOWIDTH
+ get_terminal_width_height(0, &G.win_width, &G.win_height);
+#endif
+
+#if ENABLE_FEATURE_TELNET_TTYPE
+ G.ttype = getenv("TERM");
+#endif
+
+ if (tcgetattr(0, &G.termios_def) >= 0) {
+ G.do_termios = 1;
+ G.termios_raw = G.termios_def;
+ cfmakeraw(&G.termios_raw);
+ }
+
+#if ENABLE_FEATURE_TELNET_AUTOLOGIN
+ if (1 & getopt32(argv, "al:", &G.autologin))
+ G.autologin = getenv("USER");
+ argv += optind;
+#else
+ argv++;
+#endif
+ if (!*argv)
+ bb_show_usage();
+ host = *argv++;
+ port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23);
+ if (*argv) /* extra params?? */
+ bb_show_usage();
+
+ xmove_fd(create_and_connect_stream_or_die(host, port), netfd);
+
+ setsockopt(netfd, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+
+ signal(SIGINT, record_signo);
+
+#ifdef USE_POLL
+ ufds[0].fd = 0; ufds[1].fd = netfd;
+ ufds[0].events = ufds[1].events = POLLIN;
+#else
+ FD_ZERO(&readfds);
+ FD_SET(STDIN_FILENO, &readfds);
+ FD_SET(netfd, &readfds);
+ maxfd = netfd + 1;
+#endif
+
+ while (1) {
+#ifndef USE_POLL
+ fd_set rfds = readfds;
+
+ switch (select(maxfd, &rfds, NULL, NULL, NULL))
+#else
+ switch (poll(ufds, 2, -1))
+#endif
+ {
+ case 0:
+ /* timeout */
+ case -1:
+ /* error, ignore and/or log something, bay go to loop */
+ if (bb_got_signal)
+ conescape();
+ else
+ sleep(1);
+ break;
+ default:
+
+#ifdef USE_POLL
+ if (ufds[0].revents) /* well, should check POLLIN, but ... */
+#else
+ if (FD_ISSET(STDIN_FILENO, &rfds))
+#endif
+ {
+ len = read(STDIN_FILENO, G.buf, DATABUFSIZE);
+ if (len <= 0)
+ doexit(EXIT_SUCCESS);
+ TRACE(0, ("Read con: %d\n", len));
+ handlenetoutput(len);
+ }
+
+#ifdef USE_POLL
+ if (ufds[1].revents) /* well, should check POLLIN, but ... */
+#else
+ if (FD_ISSET(netfd, &rfds))
+#endif
+ {
+ len = read(netfd, G.buf, DATABUFSIZE);
+ if (len <= 0) {
+ write_str(1, "Connection closed by foreign host\r\n");
+ doexit(EXIT_FAILURE);
+ }
+ TRACE(0, ("Read netfd (%d): %d\n", netfd, len));
+ handlenetinput(len);
+ }
+ }
+ } /* while (1) */
+}
diff --git a/networking/telnetd.c b/networking/telnetd.c
new file mode 100644
index 0000000..46dfb31
--- /dev/null
+++ b/networking/telnetd.c
@@ -0,0 +1,610 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Simple telnet server
+ * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ *
+ * ---------------------------------------------------------------------------
+ * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
+ ****************************************************************************
+ *
+ * The telnetd manpage says it all:
+ *
+ * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for
+ * a client, then creating a login process which has the slave side of the
+ * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
+ * master side of the pseudo-terminal, implementing the telnet protocol and
+ * passing characters between the remote client and the login process.
+ *
+ * Vladimir Oleynik <dzo@simtreas.ru> 2001
+ * Set process group corrections, initial busybox port
+ */
+
+#define DEBUG 0
+
+#include "libbb.h"
+#include <syslog.h>
+
+#if DEBUG
+#define TELCMDS
+#define TELOPTS
+#endif
+#include <arpa/telnet.h>
+
+/* Structure that describes a session */
+struct tsession {
+ struct tsession *next;
+ int sockfd_read, sockfd_write, ptyfd;
+ int shell_pid;
+
+ /* two circular buffers */
+ /*char *buf1, *buf2;*/
+/*#define TS_BUF1 ts->buf1*/
+/*#define TS_BUF2 TS_BUF2*/
+#define TS_BUF1 ((unsigned char*)(ts + 1))
+#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE)
+ int rdidx1, wridx1, size1;
+ int rdidx2, wridx2, size2;
+};
+
+/* Two buffers are directly after tsession in malloced memory.
+ * Make whole thing fit in 4k */
+enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
+
+
+/* Globals */
+static int maxfd;
+static struct tsession *sessions;
+static const char *loginpath = "/bin/login";
+static const char *issuefile = "/etc/issue.net";
+
+
+/*
+ Remove all IAC's from buf1 (received IACs are ignored and must be removed
+ so as to not be interpreted by the terminal). Make an uninterrupted
+ string of characters fit for the terminal. Do this by packing
+ all characters meant for the terminal sequentially towards the end of buf.
+
+ Return a pointer to the beginning of the characters meant for the terminal.
+ and make *num_totty the number of characters that should be sent to
+ the terminal.
+
+ Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
+ past (bf + len) then that IAC will be left unprocessed and *processed
+ will be less than len.
+
+ FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
+ what is the escape character? We aren't handling that situation here.
+
+ CR-LF ->'s CR mapping is also done here, for convenience.
+
+ NB: may fail to remove iacs which wrap around buffer!
+ */
+static unsigned char *
+remove_iacs(struct tsession *ts, int *pnum_totty)
+{
+ unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
+ unsigned char *ptr = ptr0;
+ unsigned char *totty = ptr;
+ unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
+ int num_totty;
+
+ while (ptr < end) {
+ if (*ptr != IAC) {
+ char c = *ptr;
+
+ *totty++ = c;
+ ptr++;
+ /* We map \r\n ==> \r for pragmatic reasons.
+ * Many client implementations send \r\n when
+ * the user hits the CarriageReturn key.
+ */
+ if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
+ ptr++;
+ continue;
+ }
+
+ if ((ptr+1) >= end)
+ break;
+ if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */
+ ptr += 2;
+ continue;
+ }
+ if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */
+ *totty++ = ptr[1];
+ ptr += 2;
+ continue;
+ }
+
+ /*
+ * TELOPT_NAWS support!
+ */
+ if ((ptr+2) >= end) {
+ /* only the beginning of the IAC is in the
+ buffer we were asked to process, we can't
+ process this char. */
+ break;
+ }
+ /*
+ * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
+ */
+ if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
+ struct winsize ws;
+ if ((ptr+8) >= end)
+ break; /* incomplete, can't process */
+ ws.ws_col = (ptr[3] << 8) | ptr[4];
+ ws.ws_row = (ptr[5] << 8) | ptr[6];
+ ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
+ ptr += 9;
+ continue;
+ }
+ /* skip 3-byte IAC non-SB cmd */
+#if DEBUG
+ fprintf(stderr, "Ignoring IAC %s,%s\n",
+ TELCMD(ptr[1]), TELOPT(ptr[2]));
+#endif
+ ptr += 3;
+ }
+
+ num_totty = totty - ptr0;
+ *pnum_totty = num_totty;
+ /* the difference between ptr and totty is number of iacs
+ we removed from the stream. Adjust buf1 accordingly. */
+ if ((ptr - totty) == 0) /* 99.999% of cases */
+ return ptr0;
+ ts->wridx1 += ptr - totty;
+ ts->size1 -= ptr - totty;
+ /* move chars meant for the terminal towards the end of the buffer */
+ return memmove(ptr - num_totty, ptr0, num_totty);
+}
+
+
+static struct tsession *
+make_new_session(
+ USE_FEATURE_TELNETD_STANDALONE(int sock)
+ SKIP_FEATURE_TELNETD_STANDALONE(void)
+) {
+ const char *login_argv[2];
+ struct termios termbuf;
+ int fd, pid;
+ char tty_name[GETPTY_BUFSIZE];
+ struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
+
+ /*ts->buf1 = (char *)(ts + 1);*/
+ /*ts->buf2 = ts->buf1 + BUFSIZE;*/
+
+ /* Got a new connection, set up a tty. */
+ fd = xgetpty(tty_name);
+ if (fd > maxfd)
+ maxfd = fd;
+ ts->ptyfd = fd;
+ ndelay_on(fd);
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+ ts->sockfd_read = sock;
+ /* SO_KEEPALIVE by popular demand */
+ setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+ ndelay_on(sock);
+ if (!sock) { /* We are called with fd 0 - we are in inetd mode */
+ sock++; /* so use fd 1 for output */
+ ndelay_on(sock);
+ }
+ ts->sockfd_write = sock;
+ if (sock > maxfd)
+ maxfd = sock;
+#else
+ /* SO_KEEPALIVE by popular demand */
+ setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
+ /* ts->sockfd_read = 0; - done by xzalloc */
+ ts->sockfd_write = 1;
+ ndelay_on(0);
+ ndelay_on(1);
+#endif
+ /* Make the telnet client understand we will echo characters so it
+ * should not do it locally. We don't tell the client to run linemode,
+ * because we want to handle line editing and tab completion and other
+ * stuff that requires char-by-char support. */
+ {
+ static const char iacs_to_send[] ALIGN1 = {
+ IAC, DO, TELOPT_ECHO,
+ IAC, DO, TELOPT_NAWS,
+ IAC, DO, TELOPT_LFLOW,
+ IAC, WILL, TELOPT_ECHO,
+ IAC, WILL, TELOPT_SGA
+ };
+ memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
+ ts->rdidx2 = sizeof(iacs_to_send);
+ ts->size2 = sizeof(iacs_to_send);
+ }
+
+ fflush(NULL); /* flush all streams */
+ pid = vfork(); /* NOMMU-friendly */
+ if (pid < 0) {
+ free(ts);
+ close(fd);
+ /* sock will be closed by caller */
+ bb_perror_msg("vfork");
+ return NULL;
+ }
+ if (pid > 0) {
+ /* Parent */
+ ts->shell_pid = pid;
+ return ts;
+ }
+
+ /* Child */
+ /* Careful - we are after vfork! */
+
+ /* make new session and process group */
+ setsid();
+
+ /* Restore default signal handling */
+ bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
+
+ /* open the child's side of the tty. */
+ /* NB: setsid() disconnects from any previous ctty's. Therefore
+ * we must open child's side of the tty AFTER setsid! */
+ close(0);
+ xopen(tty_name, O_RDWR); /* becomes our ctty */
+ xdup2(0, 1);
+ xdup2(0, 2);
+ tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
+
+ /* The pseudo-terminal allocated to the client is configured to operate in
+ * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
+ tcgetattr(0, &termbuf);
+ termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
+ termbuf.c_oflag |= ONLCR | XTABS;
+ termbuf.c_iflag |= ICRNL;
+ termbuf.c_iflag &= ~IXOFF;
+ /*termbuf.c_lflag &= ~ICANON;*/
+ tcsetattr_stdin_TCSANOW(&termbuf);
+
+ /* Uses FILE-based I/O to stdout, but does fflush(stdout),
+ * so should be safe with vfork.
+ * I fear, though, that some users will have ridiculously big
+ * issue files, and they may block writing to fd 1,
+ * (parent is supposed to read it, but parent waits
+ * for vforked child to exec!) */
+ print_login_issue(issuefile, tty_name);
+
+ /* Exec shell / login / whatever */
+ login_argv[0] = loginpath;
+ login_argv[1] = NULL;
+ /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
+ * exec external program */
+ BB_EXECVP(loginpath, (char **)login_argv);
+ /* _exit is safer with vfork, and we shouldn't send message
+ * to remote clients anyway */
+ _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
+}
+
+/* Must match getopt32 string */
+enum {
+ OPT_WATCHCHILD = (1 << 2), /* -K */
+ OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
+ OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
+ OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
+};
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+
+static void
+free_session(struct tsession *ts)
+{
+ struct tsession *t = sessions;
+
+ if (option_mask32 & OPT_INETD)
+ exit(EXIT_SUCCESS);
+
+ /* Unlink this telnet session from the session list */
+ if (t == ts)
+ sessions = ts->next;
+ else {
+ while (t->next != ts)
+ t = t->next;
+ t->next = ts->next;
+ }
+
+#if 0
+ /* It was said that "normal" telnetd just closes ptyfd,
+ * doesn't send SIGKILL. When we close ptyfd,
+ * kernel sends SIGHUP to processes having slave side opened. */
+ kill(ts->shell_pid, SIGKILL);
+ wait4(ts->shell_pid, NULL, 0, NULL);
+#endif
+ close(ts->ptyfd);
+ close(ts->sockfd_read);
+ /* We do not need to close(ts->sockfd_write), it's the same
+ * as sockfd_read unless we are in inetd mode. But in inetd mode
+ * we do not reach this */
+ free(ts);
+
+ /* Scan all sessions and find new maxfd */
+ maxfd = 0;
+ ts = sessions;
+ while (ts) {
+ if (maxfd < ts->ptyfd)
+ maxfd = ts->ptyfd;
+ if (maxfd < ts->sockfd_read)
+ maxfd = ts->sockfd_read;
+#if 0
+ /* Again, sockfd_write == sockfd_read here */
+ if (maxfd < ts->sockfd_write)
+ maxfd = ts->sockfd_write;
+#endif
+ ts = ts->next;
+ }
+}
+
+#else /* !FEATURE_TELNETD_STANDALONE */
+
+/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
+#define free_session(ts) return 0
+
+#endif
+
+static void handle_sigchld(int sig UNUSED_PARAM)
+{
+ pid_t pid;
+ struct tsession *ts;
+
+ /* Looping: more than one child may have exited */
+ while (1) {
+ pid = wait_any_nohang(NULL);
+ if (pid <= 0)
+ break;
+ ts = sessions;
+ while (ts) {
+ if (ts->shell_pid == pid) {
+ ts->shell_pid = -1;
+ break;
+ }
+ ts = ts->next;
+ }
+ }
+}
+
+int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int telnetd_main(int argc UNUSED_PARAM, char **argv)
+{
+ fd_set rdfdset, wrfdset;
+ unsigned opt;
+ int count;
+ struct tsession *ts;
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+#define IS_INETD (opt & OPT_INETD)
+ int master_fd = master_fd; /* be happy, gcc */
+ unsigned portnbr = 23;
+ char *opt_bindaddr = NULL;
+ char *opt_portnbr;
+#else
+ enum {
+ IS_INETD = 1,
+ master_fd = -1,
+ portnbr = 23,
+ };
+#endif
+ /* Even if !STANDALONE, we accept (and ignore) -i, thus people
+ * don't need to guess whether it's ok to pass -i to us */
+ opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
+ &issuefile, &loginpath
+ USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
+ if (!IS_INETD /*&& !re_execed*/) {
+ /* inform that we start in standalone mode?
+ * May be useful when people forget to give -i */
+ /*bb_error_msg("listening for connections");*/
+ if (!(opt & OPT_FOREGROUND)) {
+ /* DAEMON_CHDIR_ROOT was giving inconsistent
+ * behavior with/without -F, -i */
+ bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
+ }
+ }
+ /* Redirect log to syslog early, if needed */
+ if (IS_INETD || !(opt & OPT_FOREGROUND)) {
+ openlog(applet_name, 0, LOG_USER);
+ logmode = LOGMODE_SYSLOG;
+ }
+ USE_FEATURE_TELNETD_STANDALONE(
+ if (opt & OPT_PORT)
+ portnbr = xatou16(opt_portnbr);
+ );
+
+ /* Used to check access(loginpath, X_OK) here. Pointless.
+ * exec will do this for us for free later. */
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+ if (IS_INETD) {
+ sessions = make_new_session(0);
+ if (!sessions) /* pty opening or vfork problem, exit */
+ return 1; /* make_new_session prints error message */
+ } else {
+ master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
+ xlisten(master_fd, 1);
+ }
+#else
+ sessions = make_new_session();
+ if (!sessions) /* pty opening or vfork problem, exit */
+ return 1; /* make_new_session prints error message */
+#endif
+
+ /* We don't want to die if just one session is broken */
+ signal(SIGPIPE, SIG_IGN);
+
+ if (opt & OPT_WATCHCHILD)
+ signal(SIGCHLD, handle_sigchld);
+ else /* prevent dead children from becoming zombies */
+ signal(SIGCHLD, SIG_IGN);
+
+/*
+ This is how the buffers are used. The arrows indicate the movement
+ of data.
+ +-------+ wridx1++ +------+ rdidx1++ +----------+
+ | | <-------------- | buf1 | <-------------- | |
+ | | size1-- +------+ size1++ | |
+ | pty | | socket |
+ | | rdidx2++ +------+ wridx2++ | |
+ | | --------------> | buf2 | --------------> | |
+ +-------+ size2++ +------+ size2-- +----------+
+
+ size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
+ size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
+
+ Each session has got two buffers. Buffers are circular. If sizeN == 0,
+ buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
+ rdidxN == wridxN.
+*/
+ again:
+ FD_ZERO(&rdfdset);
+ FD_ZERO(&wrfdset);
+
+ /* Select on the master socket, all telnet sockets and their
+ * ptys if there is room in their session buffers.
+ * NB: scalability problem: we recalculate entire bitmap
+ * before each select. Can be a problem with 500+ connections. */
+ ts = sessions;
+ while (ts) {
+ struct tsession *next = ts->next; /* in case we free ts. */
+ if (ts->shell_pid == -1) {
+ /* Child died and we detected that */
+ free_session(ts);
+ } else {
+ if (ts->size1 > 0) /* can write to pty */
+ FD_SET(ts->ptyfd, &wrfdset);
+ if (ts->size1 < BUFSIZE) /* can read from socket */
+ FD_SET(ts->sockfd_read, &rdfdset);
+ if (ts->size2 > 0) /* can write to socket */
+ FD_SET(ts->sockfd_write, &wrfdset);
+ if (ts->size2 < BUFSIZE) /* can read from pty */
+ FD_SET(ts->ptyfd, &rdfdset);
+ }
+ ts = next;
+ }
+ if (!IS_INETD) {
+ FD_SET(master_fd, &rdfdset);
+ /* This is needed because free_session() does not
+ * take master_fd into account when it finds new
+ * maxfd among remaining fd's */
+ if (master_fd > maxfd)
+ maxfd = master_fd;
+ }
+
+ count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
+ if (count < 0)
+ goto again; /* EINTR or ENOMEM */
+
+#if ENABLE_FEATURE_TELNETD_STANDALONE
+ /* First check for and accept new sessions. */
+ if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
+ int fd;
+ struct tsession *new_ts;
+
+ fd = accept(master_fd, NULL, NULL);
+ if (fd < 0)
+ goto again;
+ /* Create a new session and link it into our active list */
+ new_ts = make_new_session(fd);
+ if (new_ts) {
+ new_ts->next = sessions;
+ sessions = new_ts;
+ } else {
+ close(fd);
+ }
+ }
+#endif
+
+ /* Then check for data tunneling. */
+ ts = sessions;
+ while (ts) { /* For all sessions... */
+ struct tsession *next = ts->next; /* in case we free ts. */
+
+ if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
+ int num_totty;
+ unsigned char *ptr;
+ /* Write to pty from buffer 1. */
+ ptr = remove_iacs(ts, &num_totty);
+ count = safe_write(ts->ptyfd, ptr, num_totty);
+ if (count < 0) {
+ if (errno == EAGAIN)
+ goto skip1;
+ goto kill_session;
+ }
+ ts->size1 -= count;
+ ts->wridx1 += count;
+ if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
+ ts->wridx1 = 0;
+ }
+ skip1:
+ if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
+ /* Write to socket from buffer 2. */
+ count = MIN(BUFSIZE - ts->wridx2, ts->size2);
+ count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
+ if (count < 0) {
+ if (errno == EAGAIN)
+ goto skip2;
+ goto kill_session;
+ }
+ ts->size2 -= count;
+ ts->wridx2 += count;
+ if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
+ ts->wridx2 = 0;
+ }
+ skip2:
+ /* Should not be needed, but... remove_iacs is actually buggy
+ * (it cannot process iacs which wrap around buffer's end)!
+ * Since properly fixing it requires writing bigger code,
+ * we rely instead on this code making it virtually impossible
+ * to have wrapped iac (people don't type at 2k/second).
+ * It also allows for bigger reads in common case. */
+ if (ts->size1 == 0) {
+ ts->rdidx1 = 0;
+ ts->wridx1 = 0;
+ }
+ if (ts->size2 == 0) {
+ ts->rdidx2 = 0;
+ ts->wridx2 = 0;
+ }
+
+ if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
+ /* Read from socket to buffer 1. */
+ count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
+ count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
+ if (count <= 0) {
+ if (count < 0 && errno == EAGAIN)
+ goto skip3;
+ goto kill_session;
+ }
+ /* Ignore trailing NUL if it is there */
+ if (!TS_BUF1[ts->rdidx1 + count - 1]) {
+ --count;
+ }
+ ts->size1 += count;
+ ts->rdidx1 += count;
+ if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
+ ts->rdidx1 = 0;
+ }
+ skip3:
+ if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
+ /* Read from pty to buffer 2. */
+ count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
+ count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
+ if (count <= 0) {
+ if (count < 0 && errno == EAGAIN)
+ goto skip4;
+ goto kill_session;
+ }
+ ts->size2 += count;
+ ts->rdidx2 += count;
+ if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
+ ts->rdidx2 = 0;
+ }
+ skip4:
+ ts = next;
+ continue;
+ kill_session:
+ free_session(ts);
+ ts = next;
+ }
+
+ goto again;
+}
diff --git a/networking/tftp.c b/networking/tftp.c
new file mode 100644
index 0000000..1f70685
--- /dev/null
+++ b/networking/tftp.c
@@ -0,0 +1,752 @@
+/* vi: set sw=4 ts=4: */
+/* -------------------------------------------------------------------------
+ * tftp.c
+ *
+ * A simple tftp client/server for busybox.
+ * Tries to follow RFC1350.
+ * Only "octet" mode supported.
+ * Optional blocksize negotiation (RFC2347 + RFC2348)
+ *
+ * Copyright (C) 2001 Magnus Damm <damm@opensource.se>
+ *
+ * Parts of the code based on:
+ *
+ * atftp: Copyright (C) 2000 Jean-Pierre Lefebvre <helix@step.polymtl.ca>
+ * and Remi Lefebvre <remi@debian.org>
+ *
+ * utftp: Copyright (C) 1999 Uwe Ohse <uwe@ohse.de>
+ *
+ * tftpd added by Denys Vlasenko & Vladimir Dronnikov
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ * ------------------------------------------------------------------------- */
+
+#include "libbb.h"
+
+#if ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT
+
+#define TFTP_BLKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */
+#define TFTP_BLKSIZE_DEFAULT_STR "512"
+#define TFTP_TIMEOUT_MS 50
+#define TFTP_MAXTIMEOUT_MS 2000
+#define TFTP_NUM_RETRIES 12 /* number of backed-off retries */
+
+/* opcodes we support */
+#define TFTP_RRQ 1
+#define TFTP_WRQ 2
+#define TFTP_DATA 3
+#define TFTP_ACK 4
+#define TFTP_ERROR 5
+#define TFTP_OACK 6
+
+/* error codes sent over network (we use only 0, 1, 3 and 8) */
+/* generic (error message is included in the packet) */
+#define ERR_UNSPEC 0
+#define ERR_NOFILE 1
+#define ERR_ACCESS 2
+/* disk full or allocation exceeded */
+#define ERR_WRITE 3
+#define ERR_OP 4
+#define ERR_BAD_ID 5
+#define ERR_EXIST 6
+#define ERR_BAD_USER 7
+#define ERR_BAD_OPT 8
+
+/* masks coming from getopt32 */
+enum {
+ TFTP_OPT_GET = (1 << 0),
+ TFTP_OPT_PUT = (1 << 1),
+ /* pseudo option: if set, it's tftpd */
+ TFTPD_OPT = (1 << 7) * ENABLE_TFTPD,
+ TFTPD_OPT_r = (1 << 8) * ENABLE_TFTPD,
+ TFTPD_OPT_c = (1 << 9) * ENABLE_TFTPD,
+ TFTPD_OPT_u = (1 << 10) * ENABLE_TFTPD,
+};
+
+#if ENABLE_FEATURE_TFTP_GET && !ENABLE_FEATURE_TFTP_PUT
+#define USE_GETPUT(...)
+#define CMD_GET(cmd) 1
+#define CMD_PUT(cmd) 0
+#elif !ENABLE_FEATURE_TFTP_GET && ENABLE_FEATURE_TFTP_PUT
+#define USE_GETPUT(...)
+#define CMD_GET(cmd) 0
+#define CMD_PUT(cmd) 1
+#else
+#define USE_GETPUT(...) __VA_ARGS__
+#define CMD_GET(cmd) ((cmd) & TFTP_OPT_GET)
+#define CMD_PUT(cmd) ((cmd) & TFTP_OPT_PUT)
+#endif
+/* NB: in the code below
+ * CMD_GET(cmd) and CMD_PUT(cmd) are mutually exclusive
+ */
+
+
+struct globals {
+ /* u16 TFTP_ERROR; u16 reason; both network-endian, then error text: */
+ uint8_t error_pkt[4 + 32];
+ char *user_opt;
+ /* used in tftpd_main(), a bit big for stack: */
+ char block_buf[TFTP_BLKSIZE_DEFAULT];
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define block_buf (G.block_buf )
+#define user_opt (G.user_opt )
+#define error_pkt (G.error_pkt )
+#define INIT_G() do { } while (0)
+
+#define error_pkt_reason (error_pkt[3])
+#define error_pkt_str (error_pkt + 4)
+
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+
+static int tftp_blksize_check(const char *blksize_str, int maxsize)
+{
+ /* Check if the blksize is valid:
+ * RFC2348 says between 8 and 65464,
+ * but our implementation makes it impossible
+ * to use blksizes smaller than 22 octets. */
+ unsigned blksize = bb_strtou(blksize_str, NULL, 10);
+ if (errno
+ || (blksize < 24) || (blksize > maxsize)
+ ) {
+ bb_error_msg("bad blocksize '%s'", blksize_str);
+ return -1;
+ }
+#if ENABLE_TFTP_DEBUG
+ bb_error_msg("using blksize %u", blksize);
+#endif
+ return blksize;
+}
+
+static char *tftp_get_option(const char *option, char *buf, int len)
+{
+ int opt_val = 0;
+ int opt_found = 0;
+ int k;
+
+ /* buf points to:
+ * "opt_name<NUL>opt_val<NUL>opt_name2<NUL>opt_val2<NUL>..." */
+
+ while (len > 0) {
+ /* Make sure options are terminated correctly */
+ for (k = 0; k < len; k++) {
+ if (buf[k] == '\0') {
+ goto nul_found;
+ }
+ }
+ return NULL;
+ nul_found:
+ if (opt_val == 0) { /* it's "name" part */
+ if (strcasecmp(buf, option) == 0) {
+ opt_found = 1;
+ }
+ } else if (opt_found) {
+ return buf;
+ }
+
+ k++;
+ buf += k;
+ len -= k;
+ opt_val ^= 1;
+ }
+
+ return NULL;
+}
+
+#endif
+
+static int tftp_protocol(
+ len_and_sockaddr *our_lsa,
+ len_and_sockaddr *peer_lsa,
+ const char *local_file
+ USE_TFTP(, const char *remote_file)
+ USE_FEATURE_TFTP_BLOCKSIZE(USE_TFTPD(, void *tsize))
+ USE_FEATURE_TFTP_BLOCKSIZE(, int blksize))
+{
+#if !ENABLE_TFTP
+#define remote_file NULL
+#endif
+#if !(ENABLE_FEATURE_TFTP_BLOCKSIZE && ENABLE_TFTPD)
+#define tsize NULL
+#endif
+#if !ENABLE_FEATURE_TFTP_BLOCKSIZE
+ enum { blksize = TFTP_BLKSIZE_DEFAULT };
+#endif
+
+ struct pollfd pfd[1];
+#define socket_fd (pfd[0].fd)
+ int len;
+ int send_len;
+ USE_FEATURE_TFTP_BLOCKSIZE(smallint want_option_ack = 0;)
+ smallint finished = 0;
+ uint16_t opcode;
+ uint16_t block_nr;
+ uint16_t recv_blk;
+ int open_mode, local_fd;
+ int retries, waittime_ms;
+ int io_bufsize = blksize + 4;
+ char *cp;
+ /* Can't use RESERVE_CONFIG_BUFFER here since the allocation
+ * size varies meaning BUFFERS_GO_ON_STACK would fail */
+ /* We must keep the transmit and receive buffers seperate */
+ /* In case we rcv a garbage pkt and we need to rexmit the last pkt */
+ char *xbuf = xmalloc(io_bufsize);
+ char *rbuf = xmalloc(io_bufsize);
+
+ socket_fd = xsocket(peer_lsa->u.sa.sa_family, SOCK_DGRAM, 0);
+ setsockopt_reuseaddr(socket_fd);
+
+ block_nr = 1;
+ cp = xbuf + 2;
+
+ if (!ENABLE_TFTP || our_lsa) {
+ /* tftpd */
+
+ /* Create a socket which is:
+ * 1. bound to IP:port peer sent 1st datagram to,
+ * 2. connected to peer's IP:port
+ * This way we will answer from the IP:port peer
+ * expects, will not get any other packets on
+ * the socket, and also plain read/write will work. */
+ xbind(socket_fd, &our_lsa->u.sa, our_lsa->len);
+ xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len);
+
+ /* Is there an error already? Send pkt and bail out */
+ if (error_pkt_reason || error_pkt_str[0])
+ goto send_err_pkt;
+
+ if (CMD_GET(option_mask32)) {
+ /* it's upload - we must ACK 1st packet (with filename)
+ * as if it's "block 0" */
+ block_nr = 0;
+ }
+
+ if (user_opt) {
+ struct passwd *pw = getpwnam(user_opt);
+ if (!pw)
+ bb_error_msg_and_die("unknown user %s", user_opt);
+ change_identity(pw); /* initgroups, setgid, setuid */
+ }
+ }
+
+ /* Open local file (must be after changing user) */
+ if (CMD_PUT(option_mask32)) {
+ open_mode = O_RDONLY;
+ } else {
+ open_mode = O_WRONLY | O_TRUNC | O_CREAT;
+#if ENABLE_TFTPD
+ if ((option_mask32 & (TFTPD_OPT+TFTPD_OPT_c)) == TFTPD_OPT) {
+ /* tftpd without -c */
+ open_mode = O_WRONLY | O_TRUNC;
+ }
+#endif
+ }
+ if (!(option_mask32 & TFTPD_OPT)) {
+ local_fd = CMD_GET(option_mask32) ? STDOUT_FILENO : STDIN_FILENO;
+ if (NOT_LONE_DASH(local_file))
+ local_fd = xopen(local_file, open_mode);
+ } else {
+ local_fd = open(local_file, open_mode);
+ if (local_fd < 0) {
+ error_pkt_reason = ERR_NOFILE;
+ strcpy((char*)error_pkt_str, "can't open file");
+ goto send_err_pkt;
+ }
+ }
+
+ if (!ENABLE_TFTP || our_lsa) {
+/* gcc 4.3.1 would NOT optimize it out as it should! */
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ if (blksize != TFTP_BLKSIZE_DEFAULT || tsize) {
+ /* Create and send OACK packet. */
+ /* For the download case, block_nr is still 1 -
+ * we expect 1st ACK from peer to be for (block_nr-1),
+ * that is, for "block 0" which is our OACK pkt */
+ opcode = TFTP_OACK;
+ goto add_blksize_opt;
+ }
+#endif
+ } else {
+/* Removing it, or using if() statement instead of #if may lead to
+ * "warning: null argument where non-null required": */
+#if ENABLE_TFTP
+ /* tftp */
+
+ /* We can't (and don't really need to) bind the socket:
+ * we don't know from which local IP datagrams will be sent,
+ * but kernel will pick the same IP every time (unless routing
+ * table is changed), thus peer will see dgrams consistently
+ * coming from the same IP.
+ * We would like to connect the socket, but since peer's
+ * UDP code can be less perfect than ours, _peer's_ IP:port
+ * in replies may differ from IP:port we used to send
+ * our first packet. We can connect() only when we get
+ * first reply. */
+
+ /* build opcode */
+ opcode = TFTP_WRQ;
+ if (CMD_GET(option_mask32)) {
+ opcode = TFTP_RRQ;
+ }
+ /* add filename and mode */
+ /* fill in packet if the filename fits into xbuf */
+ len = strlen(remote_file) + 1;
+ if (2 + len + sizeof("octet") >= io_bufsize) {
+ bb_error_msg("remote filename is too long");
+ goto ret;
+ }
+ strcpy(cp, remote_file);
+ cp += len;
+ /* add "mode" part of the package */
+ strcpy(cp, "octet");
+ cp += sizeof("octet");
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ if (blksize == TFTP_BLKSIZE_DEFAULT)
+ goto send_pkt;
+
+ /* Non-standard blocksize: add option to pkt */
+ if ((&xbuf[io_bufsize - 1] - cp) < sizeof("blksize NNNNN")) {
+ bb_error_msg("remote filename is too long");
+ goto ret;
+ }
+ want_option_ack = 1;
+#endif
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ add_blksize_opt:
+#if ENABLE_TFTPD
+ if (tsize) {
+ struct stat st;
+ /* add "tsize", <nul>, size, <nul> */
+ strcpy(cp, "tsize");
+ cp += sizeof("tsize");
+ fstat(local_fd, &st);
+ cp += snprintf(cp, 10, "%u", (int) st.st_size) + 1;
+ }
+#endif
+ if (blksize != TFTP_BLKSIZE_DEFAULT) {
+ /* add "blksize", <nul>, blksize, <nul> */
+ strcpy(cp, "blksize");
+ cp += sizeof("blksize");
+ cp += snprintf(cp, 6, "%d", blksize) + 1;
+ }
+#endif
+ /* First packet is built, so skip packet generation */
+ goto send_pkt;
+ }
+
+ /* Using mostly goto's - continue/break will be less clear
+ * in where we actually jump to */
+ while (1) {
+ /* Build ACK or DATA */
+ cp = xbuf + 2;
+ *((uint16_t*)cp) = htons(block_nr);
+ cp += 2;
+ block_nr++;
+ opcode = TFTP_ACK;
+ if (CMD_PUT(option_mask32)) {
+ opcode = TFTP_DATA;
+ len = full_read(local_fd, cp, blksize);
+ if (len < 0) {
+ goto send_read_err_pkt;
+ }
+ if (len != blksize) {
+ finished = 1;
+ }
+ cp += len;
+ }
+ send_pkt:
+ /* Send packet */
+ *((uint16_t*)xbuf) = htons(opcode); /* fill in opcode part */
+ send_len = cp - xbuf;
+ /* NB: send_len value is preserved in code below
+ * for potential resend */
+
+ retries = TFTP_NUM_RETRIES; /* re-initialize */
+ waittime_ms = TFTP_TIMEOUT_MS;
+
+ send_again:
+#if ENABLE_TFTP_DEBUG
+ fprintf(stderr, "sending %u bytes\n", send_len);
+ for (cp = xbuf; cp < &xbuf[send_len]; cp++)
+ fprintf(stderr, "%02x ", (unsigned char) *cp);
+ fprintf(stderr, "\n");
+#endif
+ xsendto(socket_fd, xbuf, send_len, &peer_lsa->u.sa, peer_lsa->len);
+ /* Was it final ACK? then exit */
+ if (finished && (opcode == TFTP_ACK))
+ goto ret;
+
+ recv_again:
+ /* Receive packet */
+ /*pfd[0].fd = socket_fd;*/
+ pfd[0].events = POLLIN;
+ switch (safe_poll(pfd, 1, waittime_ms)) {
+ default:
+ /*bb_perror_msg("poll"); - done in safe_poll */
+ goto ret;
+ case 0:
+ retries--;
+ if (retries == 0) {
+ bb_error_msg("timeout");
+ goto ret; /* no err packet sent */
+ }
+
+ /* exponential backoff with limit */
+ waittime_ms += waittime_ms/2;
+ if (waittime_ms > TFTP_MAXTIMEOUT_MS) {
+ waittime_ms = TFTP_MAXTIMEOUT_MS;
+ }
+
+ goto send_again; /* resend last sent pkt */
+ case 1:
+ if (!our_lsa) {
+ /* tftp (not tftpd!) receiving 1st packet */
+ our_lsa = ((void*)(ptrdiff_t)-1); /* not NULL */
+ len = recvfrom(socket_fd, rbuf, io_bufsize, 0,
+ &peer_lsa->u.sa, &peer_lsa->len);
+ /* Our first dgram went to port 69
+ * but reply may come from different one.
+ * Remember and use this new port (and IP) */
+ if (len >= 0)
+ xconnect(socket_fd, &peer_lsa->u.sa, peer_lsa->len);
+ } else {
+ /* tftpd, or not the very first packet:
+ * socket is connect()ed, can just read from it. */
+ /* Don't full_read()!
+ * This is not TCP, one read == one pkt! */
+ len = safe_read(socket_fd, rbuf, io_bufsize);
+ }
+ if (len < 0) {
+ goto send_read_err_pkt;
+ }
+ if (len < 4) { /* too small? */
+ goto recv_again;
+ }
+ }
+
+ /* Process recv'ed packet */
+ opcode = ntohs( ((uint16_t*)rbuf)[0] );
+ recv_blk = ntohs( ((uint16_t*)rbuf)[1] );
+#if ENABLE_TFTP_DEBUG
+ fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, recv_blk);
+#endif
+ if (opcode == TFTP_ERROR) {
+ static const char errcode_str[] ALIGN1 =
+ "\0"
+ "file not found\0"
+ "access violation\0"
+ "disk full\0"
+ "bad operation\0"
+ "unknown transfer id\0"
+ "file already exists\0"
+ "no such user\0"
+ "bad option";
+
+ const char *msg = "";
+
+ if (len > 4 && rbuf[4] != '\0') {
+ msg = &rbuf[4];
+ rbuf[io_bufsize - 1] = '\0'; /* paranoia */
+ } else if (recv_blk <= 8) {
+ msg = nth_string(errcode_str, recv_blk);
+ }
+ bb_error_msg("server error: (%u) %s", recv_blk, msg);
+ goto ret;
+ }
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ if (want_option_ack) {
+ want_option_ack = 0;
+ if (opcode == TFTP_OACK) {
+ /* server seems to support options */
+ char *res;
+
+ res = tftp_get_option("blksize", &rbuf[2], len - 2);
+ if (res) {
+ blksize = tftp_blksize_check(res, blksize);
+ if (blksize < 0) {
+ error_pkt_reason = ERR_BAD_OPT;
+ goto send_err_pkt;
+ }
+ io_bufsize = blksize + 4;
+ /* Send ACK for OACK ("block" no: 0) */
+ block_nr = 0;
+ continue;
+ }
+ /* rfc2347:
+ * "An option not acknowledged by the server
+ * must be ignored by the client and server
+ * as if it were never requested." */
+ }
+ bb_error_msg("server only supports blocksize of 512");
+ blksize = TFTP_BLKSIZE_DEFAULT;
+ io_bufsize = TFTP_BLKSIZE_DEFAULT + 4;
+ }
+#endif
+ /* block_nr is already advanced to next block# we expect
+ * to get / block# we are about to send next time */
+
+ if (CMD_GET(option_mask32) && (opcode == TFTP_DATA)) {
+ if (recv_blk == block_nr) {
+ int sz = full_write(local_fd, &rbuf[4], len - 4);
+ if (sz != len - 4) {
+ strcpy((char*)error_pkt_str, bb_msg_write_error);
+ error_pkt_reason = ERR_WRITE;
+ goto send_err_pkt;
+ }
+ if (sz != blksize) {
+ finished = 1;
+ }
+ continue; /* send ACK */
+ }
+ if (recv_blk == (block_nr - 1)) {
+ /* Server lost our TFTP_ACK. Resend it */
+ block_nr = recv_blk;
+ continue;
+ }
+ }
+
+ if (CMD_PUT(option_mask32) && (opcode == TFTP_ACK)) {
+ /* did peer ACK our last DATA pkt? */
+ if (recv_blk == (uint16_t) (block_nr - 1)) {
+ if (finished)
+ goto ret;
+ continue; /* send next block */
+ }
+ }
+ /* Awww... recv'd packet is not recognized! */
+ goto recv_again;
+ /* why recv_again? - rfc1123 says:
+ * "The sender (i.e., the side originating the DATA packets)
+ * must never resend the current DATA packet on receipt
+ * of a duplicate ACK".
+ * DATA pkts are resent ONLY on timeout.
+ * Thus "goto send_again" will ba a bad mistake above.
+ * See:
+ * http://en.wikipedia.org/wiki/Sorcerer's_Apprentice_Syndrome
+ */
+ } /* end of "while (1)" */
+ ret:
+ if (ENABLE_FEATURE_CLEAN_UP) {
+ close(local_fd);
+ close(socket_fd);
+ free(xbuf);
+ free(rbuf);
+ }
+ return finished == 0; /* returns 1 on failure */
+
+ send_read_err_pkt:
+ strcpy((char*)error_pkt_str, bb_msg_read_error);
+ send_err_pkt:
+ if (error_pkt_str[0])
+ bb_error_msg((char*)error_pkt_str);
+ error_pkt[1] = TFTP_ERROR;
+ xsendto(socket_fd, error_pkt, 4 + 1 + strlen((char*)error_pkt_str),
+ &peer_lsa->u.sa, peer_lsa->len);
+ return EXIT_FAILURE;
+#undef remote_file
+#undef tsize
+}
+
+#if ENABLE_TFTP
+
+int tftp_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tftp_main(int argc UNUSED_PARAM, char **argv)
+{
+ len_and_sockaddr *peer_lsa;
+ const char *local_file = NULL;
+ const char *remote_file = NULL;
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ const char *blksize_str = TFTP_BLKSIZE_DEFAULT_STR;
+ int blksize;
+#endif
+ int result;
+ int port;
+ USE_GETPUT(int opt;)
+
+ INIT_G();
+
+ /* -p or -g is mandatory, and they are mutually exclusive */
+ opt_complementary = "" USE_FEATURE_TFTP_GET("g:") USE_FEATURE_TFTP_PUT("p:")
+ USE_GETPUT("g--p:p--g:");
+
+ USE_GETPUT(opt =) getopt32(argv,
+ USE_FEATURE_TFTP_GET("g") USE_FEATURE_TFTP_PUT("p")
+ "l:r:" USE_FEATURE_TFTP_BLOCKSIZE("b:"),
+ &local_file, &remote_file
+ USE_FEATURE_TFTP_BLOCKSIZE(, &blksize_str));
+ argv += optind;
+
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ /* Check if the blksize is valid:
+ * RFC2348 says between 8 and 65464 */
+ blksize = tftp_blksize_check(blksize_str, 65564);
+ if (blksize < 0) {
+ //bb_error_msg("bad block size");
+ return EXIT_FAILURE;
+ }
+#endif
+
+ if (!local_file)
+ local_file = remote_file;
+ if (!remote_file)
+ remote_file = local_file;
+ /* Error if filename or host is not known */
+ if (!remote_file || !argv[0])
+ bb_show_usage();
+
+ port = bb_lookup_port(argv[1], "udp", 69);
+ peer_lsa = xhost2sockaddr(argv[0], port);
+
+#if ENABLE_TFTP_DEBUG
+ fprintf(stderr, "using server '%s', remote_file '%s', local_file '%s'\n",
+ xmalloc_sockaddr2dotted(&peer_lsa->u.sa),
+ remote_file, local_file);
+#endif
+
+ result = tftp_protocol(
+ NULL /*our_lsa*/, peer_lsa,
+ local_file, remote_file
+ USE_FEATURE_TFTP_BLOCKSIZE(USE_TFTPD(, NULL /*tsize*/))
+ USE_FEATURE_TFTP_BLOCKSIZE(, blksize)
+ );
+
+ if (result != EXIT_SUCCESS && NOT_LONE_DASH(local_file) && CMD_GET(opt)) {
+ unlink(local_file);
+ }
+ return result;
+}
+
+#endif /* ENABLE_TFTP */
+
+#if ENABLE_TFTPD
+
+/* TODO: libbb candidate? */
+static len_and_sockaddr *get_sock_lsa(int s)
+{
+ len_and_sockaddr *lsa;
+ socklen_t len = 0;
+
+ if (getsockname(s, NULL, &len) != 0)
+ return NULL;
+ lsa = xzalloc(LSA_LEN_SIZE + len);
+ lsa->len = len;
+ getsockname(s, &lsa->u.sa, &lsa->len);
+ return lsa;
+}
+
+int tftpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int tftpd_main(int argc UNUSED_PARAM, char **argv)
+{
+ len_and_sockaddr *our_lsa;
+ len_and_sockaddr *peer_lsa;
+ char *local_file, *mode;
+ const char *error_msg;
+ int opt, result, opcode;
+ USE_FEATURE_TFTP_BLOCKSIZE(int blksize = TFTP_BLKSIZE_DEFAULT;)
+ USE_FEATURE_TFTP_BLOCKSIZE(char *tsize = NULL;)
+
+ INIT_G();
+
+ our_lsa = get_sock_lsa(STDIN_FILENO);
+ if (!our_lsa) {
+ /* This is confusing:
+ *bb_error_msg_and_die("stdin is not a socket");
+ * Better: */
+ bb_show_usage();
+ /* Help text says that tftpd must be used as inetd service,
+ * which is by far the most usual cause of get_sock_lsa
+ * failure */
+ }
+ peer_lsa = xzalloc(LSA_LEN_SIZE + our_lsa->len);
+ peer_lsa->len = our_lsa->len;
+
+ /* Shifting to not collide with TFTP_OPTs */
+ opt = option_mask32 = TFTPD_OPT | (getopt32(argv, "rcu:", &user_opt) << 8);
+ argv += optind;
+ if (argv[0])
+ xchdir(argv[0]);
+
+ result = recv_from_to(STDIN_FILENO, block_buf, sizeof(block_buf),
+ 0 /* flags */,
+ &peer_lsa->u.sa, &our_lsa->u.sa, our_lsa->len);
+
+ error_msg = "malformed packet";
+ opcode = ntohs(*(uint16_t*)block_buf);
+ if (result < 4 || result >= sizeof(block_buf)
+ || block_buf[result-1] != '\0'
+ || (USE_FEATURE_TFTP_PUT(opcode != TFTP_RRQ) /* not download */
+ USE_GETPUT(&&)
+ USE_FEATURE_TFTP_GET(opcode != TFTP_WRQ) /* not upload */
+ )
+ ) {
+ goto err;
+ }
+ local_file = block_buf + 2;
+ if (local_file[0] == '.' || strstr(local_file, "/.")) {
+ error_msg = "dot in file name";
+ goto err;
+ }
+ mode = local_file + strlen(local_file) + 1;
+ if (mode >= block_buf + result || strcmp(mode, "octet") != 0) {
+ goto err;
+ }
+#if ENABLE_FEATURE_TFTP_BLOCKSIZE
+ {
+ char *res;
+ char *opt_str = mode + sizeof("octet");
+ int opt_len = block_buf + result - opt_str;
+ if (opt_len > 0) {
+ res = tftp_get_option("blksize", opt_str, opt_len);
+ if (res) {
+ blksize = tftp_blksize_check(res, 65564);
+ if (blksize < 0) {
+ error_pkt_reason = ERR_BAD_OPT;
+ /* will just send error pkt */
+ goto do_proto;
+ }
+ }
+ /* did client ask us about file size? */
+ tsize = tftp_get_option("tsize", opt_str, opt_len);
+ }
+ }
+#endif
+
+ if (!ENABLE_FEATURE_TFTP_PUT || opcode == TFTP_WRQ) {
+ if (opt & TFTPD_OPT_r) {
+ /* This would mean "disk full" - not true */
+ /*error_pkt_reason = ERR_WRITE;*/
+ error_msg = bb_msg_write_error;
+ goto err;
+ }
+ USE_GETPUT(option_mask32 |= TFTP_OPT_GET;) /* will receive file's data */
+ } else {
+ USE_GETPUT(option_mask32 |= TFTP_OPT_PUT;) /* will send file's data */
+ }
+
+ /* NB: if error_pkt_str or error_pkt_reason is set up,
+ * tftp_protocol() just sends one error pkt and returns */
+
+ do_proto:
+ close(STDIN_FILENO); /* close old, possibly wildcard socket */
+ /* tftp_protocol() will create new one, bound to particular local IP */
+ result = tftp_protocol(
+ our_lsa, peer_lsa,
+ local_file USE_TFTP(, NULL /*remote_file*/)
+ USE_FEATURE_TFTP_BLOCKSIZE(, tsize)
+ USE_FEATURE_TFTP_BLOCKSIZE(, blksize)
+ );
+
+ return result;
+ err:
+ strcpy((char*)error_pkt_str, error_msg);
+ goto do_proto;
+}
+
+#endif /* ENABLE_TFTPD */
+
+#endif /* ENABLE_FEATURE_TFTP_GET || ENABLE_FEATURE_TFTP_PUT */
diff --git a/networking/traceroute.c b/networking/traceroute.c
new file mode 100644
index 0000000..29cebfa
--- /dev/null
+++ b/networking/traceroute.c
@@ -0,0 +1,1349 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Copyright (c) 1988, 1989, 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Busybox port by Vladimir Oleynik (C) 2005 <dzo@simtreas.ru>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that: (1) source code distributions
+ * retain the above copyright notice and this paragraph in its entirety, (2)
+ * distributions including binary code include the above copyright notice and
+ * this paragraph in its entirety in the documentation or other materials
+ * provided with the distribution, and (3) all advertising materials mentioning
+ * features or use of this software display the following acknowledgement:
+ * ``This product includes software developed by the University of California,
+ * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
+ * the University nor the names of its contributors may be used to endorse
+ * or promote products derived from this software without specific prior
+ * written permission.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+/*
+ * traceroute host - trace the route ip packets follow going to "host".
+ *
+ * Attempt to trace the route an ip packet would follow to some
+ * internet host. We find out intermediate hops by launching probe
+ * packets with a small ttl (time to live) then listening for an
+ * icmp "time exceeded" reply from a gateway. We start our probes
+ * with a ttl of one and increase by one until we get an icmp "port
+ * unreachable" (which means we got to "host") or hit a max (which
+ * defaults to 30 hops & can be changed with the -m flag). Three
+ * probes (change with -q flag) are sent at each ttl setting and a
+ * line is printed showing the ttl, address of the gateway and
+ * round trip time of each probe. If the probe answers come from
+ * different gateways, the address of each responding system will
+ * be printed. If there is no response within a 5 sec. timeout
+ * interval (changed with the -w flag), a "*" is printed for that
+ * probe.
+ *
+ * Probe packets are UDP format. We don't want the destination
+ * host to process them so the destination port is set to an
+ * unlikely value (if some clod on the destination is using that
+ * value, it can be changed with the -p flag).
+ *
+ * A sample use might be:
+ *
+ * [yak 71]% traceroute nis.nsf.net.
+ * traceroute to nis.nsf.net (35.1.1.48), 30 hops max, 56 byte packet
+ * 1 helios.ee.lbl.gov (128.3.112.1) 19 ms 19 ms 0 ms
+ * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms
+ * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms
+ * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 39 ms
+ * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 39 ms 39 ms 39 ms
+ * 6 128.32.197.4 (128.32.197.4) 40 ms 59 ms 59 ms
+ * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 59 ms
+ * 8 129.140.70.13 (129.140.70.13) 99 ms 99 ms 80 ms
+ * 9 129.140.71.6 (129.140.71.6) 139 ms 239 ms 319 ms
+ * 10 129.140.81.7 (129.140.81.7) 220 ms 199 ms 199 ms
+ * 11 nic.merit.edu (35.1.1.48) 239 ms 239 ms 239 ms
+ *
+ * Note that lines 2 & 3 are the same. This is due to a buggy
+ * kernel on the 2nd hop system -- lbl-csam.arpa -- that forwards
+ * packets with a zero ttl.
+ *
+ * A more interesting example is:
+ *
+ * [yak 72]% traceroute allspice.lcs.mit.edu.
+ * traceroute to allspice.lcs.mit.edu (18.26.0.115), 30 hops max
+ * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms
+ * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 19 ms 19 ms
+ * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 19 ms
+ * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 19 ms 39 ms 39 ms
+ * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 20 ms 39 ms 39 ms
+ * 6 128.32.197.4 (128.32.197.4) 59 ms 119 ms 39 ms
+ * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 39 ms
+ * 8 129.140.70.13 (129.140.70.13) 80 ms 79 ms 99 ms
+ * 9 129.140.71.6 (129.140.71.6) 139 ms 139 ms 159 ms
+ * 10 129.140.81.7 (129.140.81.7) 199 ms 180 ms 300 ms
+ * 11 129.140.72.17 (129.140.72.17) 300 ms 239 ms 239 ms
+ * 12 * * *
+ * 13 128.121.54.72 (128.121.54.72) 259 ms 499 ms 279 ms
+ * 14 * * *
+ * 15 * * *
+ * 16 * * *
+ * 17 * * *
+ * 18 ALLSPICE.LCS.MIT.EDU (18.26.0.115) 339 ms 279 ms 279 ms
+ *
+ * (I start to see why I'm having so much trouble with mail to
+ * MIT.) Note that the gateways 12, 14, 15, 16 & 17 hops away
+ * either don't send ICMP "time exceeded" messages or send them
+ * with a ttl too small to reach us. 14 - 17 are running the
+ * MIT C Gateway code that doesn't send "time exceeded"s. God
+ * only knows what's going on with 12.
+ *
+ * The silent gateway 12 in the above may be the result of a bug in
+ * the 4.[23]BSD network code (and its derivatives): 4.x (x <= 3)
+ * sends an unreachable message using whatever ttl remains in the
+ * original datagram. Since, for gateways, the remaining ttl is
+ * zero, the icmp "time exceeded" is guaranteed to not make it back
+ * to us. The behavior of this bug is slightly more interesting
+ * when it appears on the destination system:
+ *
+ * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms
+ * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 39 ms
+ * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 39 ms 19 ms
+ * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 19 ms
+ * 5 ccn-nerif35.Berkeley.EDU (128.32.168.35) 39 ms 39 ms 39 ms
+ * 6 csgw.Berkeley.EDU (128.32.133.254) 39 ms 59 ms 39 ms
+ * 7 * * *
+ * 8 * * *
+ * 9 * * *
+ * 10 * * *
+ * 11 * * *
+ * 12 * * *
+ * 13 rip.Berkeley.EDU (128.32.131.22) 59 ms ! 39 ms ! 39 ms !
+ *
+ * Notice that there are 12 "gateways" (13 is the final
+ * destination) and exactly the last half of them are "missing".
+ * What's really happening is that rip (a Sun-3 running Sun OS3.5)
+ * is using the ttl from our arriving datagram as the ttl in its
+ * icmp reply. So, the reply will time out on the return path
+ * (with no notice sent to anyone since icmp's aren't sent for
+ * icmp's) until we probe with a ttl that's at least twice the path
+ * length. I.e., rip is really only 7 hops away. A reply that
+ * returns with a ttl of 1 is a clue this problem exists.
+ * Traceroute prints a "!" after the time if the ttl is <= 1.
+ * Since vendors ship a lot of obsolete (DEC's Ultrix, Sun 3.x) or
+ * non-standard (HPUX) software, expect to see this problem
+ * frequently and/or take care picking the target host of your
+ * probes.
+ *
+ * Other possible annotations after the time are !H, !N, !P (got a host,
+ * network or protocol unreachable, respectively), !S or !F (source
+ * route failed or fragmentation needed -- neither of these should
+ * ever occur and the associated gateway is busted if you see one). If
+ * almost all the probes result in some kind of unreachable, traceroute
+ * will give up and exit.
+ *
+ * Notes
+ * -----
+ * This program must be run by root or be setuid. (I suggest that
+ * you *don't* make it setuid -- casual use could result in a lot
+ * of unnecessary traffic on our poor, congested nets.)
+ *
+ * This program requires a kernel mod that does not appear in any
+ * system available from Berkeley: A raw ip socket using proto
+ * IPPROTO_RAW must interpret the data sent as an ip datagram (as
+ * opposed to data to be wrapped in a ip datagram). See the README
+ * file that came with the source to this program for a description
+ * of the mods I made to /sys/netinet/raw_ip.c. Your mileage may
+ * vary. But, again, ANY 4.x (x < 4) BSD KERNEL WILL HAVE TO BE
+ * MODIFIED TO RUN THIS PROGRAM.
+ *
+ * The udp port usage may appear bizarre (well, ok, it is bizarre).
+ * The problem is that an icmp message only contains 8 bytes of
+ * data from the original datagram. 8 bytes is the size of a udp
+ * header so, if we want to associate replies with the original
+ * datagram, the necessary information must be encoded into the
+ * udp header (the ip id could be used but there's no way to
+ * interlock with the kernel's assignment of ip id's and, anyway,
+ * it would have taken a lot more kernel hacking to allow this
+ * code to set the ip id). So, to allow two or more users to
+ * use traceroute simultaneously, we use this task's pid as the
+ * source port (the high bit is set to move the port number out
+ * of the "likely" range). To keep track of which probe is being
+ * replied to (so times and/or hop counts don't get confused by a
+ * reply that was delayed in transit), we increment the destination
+ * port number before each probe.
+ *
+ * Don't use this as a coding example. I was trying to find a
+ * routing problem and this code sort-of popped out after 48 hours
+ * without sleep. I was amazed it ever compiled, much less ran.
+ *
+ * I stole the idea for this program from Steve Deering. Since
+ * the first release, I've learned that had I attended the right
+ * IETF working group meetings, I also could have stolen it from Guy
+ * Almes or Matt Mathis. I don't know (or care) who came up with
+ * the idea first. I envy the originators' perspicacity and I'm
+ * glad they didn't keep the idea a secret.
+ *
+ * Tim Seaver, Ken Adelman and C. Philip Wood provided bug fixes and/or
+ * enhancements to the original distribution.
+ *
+ * I've hacked up a round-trip-route version of this that works by
+ * sending a loose-source-routed udp datagram through the destination
+ * back to yourself. Unfortunately, SO many gateways botch source
+ * routing, the thing is almost worthless. Maybe one day...
+ *
+ * -- Van Jacobson (van@ee.lbl.gov)
+ * Tue Dec 20 03:50:13 PST 1988
+ */
+
+#define TRACEROUTE_SO_DEBUG 0
+
+/* TODO: undefs were uncommented - ??! we have config system for that! */
+/* probably ok to remove altogether */
+//#undef CONFIG_FEATURE_TRACEROUTE_VERBOSE
+//#define CONFIG_FEATURE_TRACEROUTE_VERBOSE
+//#undef CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE
+//#define CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE
+//#undef CONFIG_FEATURE_TRACEROUTE_USE_ICMP
+//#define CONFIG_FEATURE_TRACEROUTE_USE_ICMP
+
+
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+
+#include "libbb.h"
+#include "inet_common.h"
+
+
+/*
+ * Definitions for internet protocol version 4.
+ * Per RFC 791, September 1981.
+ */
+#define IPVERSION 4
+
+#ifndef IPPROTO_ICMP
+/* Grrrr.... */
+#define IPPROTO_ICMP 1
+#endif
+#ifndef IPPROTO_IP
+#define IPPROTO_IP 0
+#endif
+
+/*
+ * Overlay for ip header used by other protocols (tcp, udp).
+ */
+struct ipovly {
+ unsigned char ih_x1[9]; /* (unused) */
+ unsigned char ih_pr; /* protocol */
+ short ih_len; /* protocol length */
+ struct in_addr ih_src; /* source internet address */
+ struct in_addr ih_dst; /* destination internet address */
+};
+
+/*
+ * UDP kernel structures and variables.
+ */
+struct udpiphdr {
+ struct ipovly ui_i; /* overlaid ip structure */
+ struct udphdr ui_u; /* udp header */
+};
+#define ui_next ui_i.ih_next
+#define ui_prev ui_i.ih_prev
+#define ui_x1 ui_i.ih_x1
+#define ui_pr ui_i.ih_pr
+#define ui_len ui_i.ih_len
+#define ui_src ui_i.ih_src
+#define ui_dst ui_i.ih_dst
+#define ui_sport ui_u.uh_sport
+#define ui_dport ui_u.uh_dport
+#define ui_ulen ui_u.uh_ulen
+#define ui_sum ui_u.uh_sum
+
+
+/* Host name and address list */
+struct hostinfo {
+ char *name;
+ int n;
+ uint32_t *addrs;
+};
+
+/* Data section of the probe packet */
+typedef struct outdata {
+ unsigned char seq; /* sequence number of this packet */
+ unsigned char ttl; /* ttl packet left with */
+// UNUSED. Retaining to have the same packet size.
+ struct timeval tv_UNUSED PACKED; /* time packet left */
+} outdata_t;
+
+struct IFADDRLIST {
+ uint32_t addr;
+ char device[sizeof(struct ifreq)];
+};
+
+
+/* Keep in sync with getopt32 call! */
+#define OPT_DONT_FRAGMNT (1<<0) /* F */
+#define OPT_USE_ICMP (1<<1) /* I */
+#define OPT_TTL_FLAG (1<<2) /* l */
+#define OPT_ADDR_NUM (1<<3) /* n */
+#define OPT_BYPASS_ROUTE (1<<4) /* r */
+#define OPT_DEBUG (1<<5) /* d */
+#define OPT_VERBOSE (1<<6) /* v */
+#define OPT_IP_CHKSUM (1<<7) /* x */
+#define OPT_TOS (1<<8) /* t */
+#define OPT_DEVICE (1<<9) /* i */
+#define OPT_MAX_TTL (1<<10) /* m */
+#define OPT_PORT (1<<11) /* p */
+#define OPT_NPROBES (1<<12) /* q */
+#define OPT_SOURCE (1<<13) /* s */
+#define OPT_WAITTIME (1<<14) /* w */
+#define OPT_PAUSE_MS (1<<15) /* z */
+#define OPT_FIRST_TTL (1<<16) /* f */
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+/* use icmp echo instead of udp packets */
+#define useicmp (option_mask32 & OPT_USE_ICMP)
+#endif
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+#define verbose (option_mask32 & OPT_VERBOSE)
+#endif
+#define nflag (option_mask32 & OPT_ADDR_NUM)
+
+
+struct globals {
+ struct ip *outip; /* last output (udp) packet */
+ struct udphdr *outudp; /* last output (udp) packet */
+ struct outdata *outdata; /* last output (udp) packet */
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+ struct icmp *outicmp; /* last output (icmp) packet */
+#endif
+
+ int rcvsock; /* receive (icmp) socket file descriptor */
+ int sndsock; /* send (udp/icmp) socket file descriptor */
+
+ int packlen; /* total length of packet */
+ int minpacket; /* min ip packet size */
+ int maxpacket; // 32 * 1024; /* max ip packet size */
+ int pmtu; /* Path MTU Discovery (RFC1191) */
+
+ char *hostname;
+
+ uint16_t ident;
+ uint16_t port; // 32768 + 666; /* start udp dest port # for probe packets */
+
+ int waittime; // 5; /* time to wait for response (in seconds) */
+ int doipcksum; // 1; /* calculate ip checksums by default */
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ int optlen; /* length of ip options */
+#else
+#define optlen 0
+#endif
+
+ struct sockaddr_storage whereto; /* Who to try to reach */
+ struct sockaddr_storage wherefrom; /* Who we are */
+ /* last inbound (icmp) packet */
+ unsigned char packet[512];
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ /* Maximum number of gateways (include room for one noop) */
+#define NGATEWAYS ((int)((MAX_IPOPTLEN - IPOPT_MINOFF - 1) / sizeof(uint32_t)))
+ /* loose source route gateway list (including room for final destination) */
+ uint32_t gwlist[NGATEWAYS + 1];
+#endif
+};
+
+#define G (*ptr_to_globals)
+#define outip (G.outip )
+#define outudp (G.outudp )
+#define outdata (G.outdata )
+#define outicmp (G.outicmp )
+#define rcvsock (G.rcvsock )
+#define sndsock (G.sndsock )
+#define packlen (G.packlen )
+#define minpacket (G.minpacket)
+#define maxpacket (G.maxpacket)
+#define pmtu (G.pmtu )
+#define hostname (G.hostname )
+#define ident (G.ident )
+#define port (G.port )
+#define waittime (G.waittime )
+#define doipcksum (G.doipcksum)
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+#define optlen (G.optlen )
+#endif
+#define packet (G.packet )
+#define whereto (G.whereto )
+#define wherefrom (G.wherefrom)
+#define gwlist (G.gwlist )
+#define INIT_G() do { \
+ SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
+ maxpacket = 32 * 1024; \
+ port = 32768 + 666; \
+ waittime = 5; \
+ doipcksum = 1; \
+} while (0)
+
+
+/*
+ * Return the interface list
+ */
+static int
+ifaddrlist(struct IFADDRLIST **ipaddrp)
+{
+ enum { IFREQ_BUFSIZE = (32 * 1024) / sizeof(struct ifreq) };
+
+ int fd, nipaddr;
+#ifdef HAVE_SOCKADDR_SA_LEN
+ int n;
+#endif
+ struct ifreq *ifrp, *ifend, *ifnext;
+ struct sockaddr_in *addr_sin;
+ struct IFADDRLIST *al;
+ struct ifconf ifc;
+ struct ifreq ifr;
+ /* Was on stack, but 32k is a bit too much: */
+ struct ifreq *ibuf = xmalloc(IFREQ_BUFSIZE * sizeof(ibuf[0]));
+ struct IFADDRLIST *st_ifaddrlist;
+
+ fd = xsocket(AF_INET, SOCK_DGRAM, 0);
+
+ ifc.ifc_len = IFREQ_BUFSIZE * sizeof(ibuf[0]);
+ ifc.ifc_buf = (caddr_t)ibuf;
+
+ if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0
+ || ifc.ifc_len < (int)sizeof(struct ifreq)
+ ) {
+ if (errno == EINVAL)
+ bb_error_msg_and_die(
+ "SIOCGIFCONF: ifreq struct too small (%u bytes)",
+ (unsigned)(IFREQ_BUFSIZE * sizeof(ibuf[0])));
+ bb_perror_msg_and_die("SIOCGIFCONF");
+ }
+ ifrp = ibuf;
+ ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len);
+
+ nipaddr = 1 + (ifc.ifc_len / sizeof(struct ifreq));
+ st_ifaddrlist = xzalloc(nipaddr * sizeof(struct IFADDRLIST));
+ al = st_ifaddrlist;
+ nipaddr = 0;
+
+ for (; ifrp < ifend; ifrp = ifnext) {
+#ifdef HAVE_SOCKADDR_SA_LEN
+ n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name);
+ if (n < sizeof(*ifrp))
+ ifnext = ifrp + 1;
+ else
+ ifnext = (struct ifreq *)((char *)ifrp + n);
+ if (ifrp->ifr_addr.sa_family != AF_INET)
+ continue;
+#else
+ ifnext = ifrp + 1;
+#endif
+ /*
+ * Need a template to preserve address info that is
+ * used below to locate the next entry. (Otherwise,
+ * SIOCGIFFLAGS stomps over it because the requests
+ * are returned in a union.)
+ */
+ strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name));
+ if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0) {
+ if (errno == ENXIO)
+ continue;
+ bb_perror_msg_and_die("SIOCGIFFLAGS: %.*s",
+ (int)sizeof(ifr.ifr_name), ifr.ifr_name);
+ }
+
+ /* Must be up */
+ if ((ifr.ifr_flags & IFF_UP) == 0)
+ continue;
+
+ safe_strncpy(al->device, ifr.ifr_name, sizeof(ifr.ifr_name) + 1);
+#ifdef sun
+ /* Ignore sun virtual interfaces */
+ if (strchr(al->device, ':') != NULL)
+ continue;
+#endif
+ ioctl_or_perror_and_die(fd, SIOCGIFADDR, (char *)&ifr,
+ "SIOCGIFADDR: %s", al->device);
+
+ addr_sin = (struct sockaddr_in *)&ifr.ifr_addr;
+ al->addr = addr_sin->sin_addr.s_addr;
+ ++al;
+ ++nipaddr;
+ }
+ if (nipaddr == 0)
+ bb_error_msg_and_die("can't find any network interfaces");
+
+ free(ibuf);
+ close(fd);
+ *ipaddrp = st_ifaddrlist;
+ return nipaddr;
+}
+
+
+static void
+setsin(struct sockaddr_in *addr_sin, uint32_t addr)
+{
+ memset(addr_sin, 0, sizeof(*addr_sin));
+#ifdef HAVE_SOCKADDR_SA_LEN
+ addr_sin->sin_len = sizeof(*addr_sin);
+#endif
+ addr_sin->sin_family = AF_INET;
+ addr_sin->sin_addr.s_addr = addr;
+}
+
+
+/*
+ * Return the source address for the given destination address
+ */
+static void
+findsaddr(const struct sockaddr_in *to, struct sockaddr_in *from)
+{
+ int i, n;
+ FILE *f;
+ uint32_t mask;
+ uint32_t dest, tmask;
+ struct IFADDRLIST *al;
+ char buf[256], tdevice[256], device[256];
+
+ f = xfopen_for_read("/proc/net/route");
+
+ /* Find the appropriate interface */
+ n = 0;
+ mask = 0;
+ device[0] = '\0';
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+ ++n;
+ if (n == 1 && strncmp(buf, "Iface", 5) == 0)
+ continue;
+ i = sscanf(buf, "%255s %x %*s %*s %*s %*s %*s %x",
+ tdevice, &dest, &tmask);
+ if (i != 3)
+ bb_error_msg_and_die("junk in buffer");
+ if ((to->sin_addr.s_addr & tmask) == dest
+ && (tmask > mask || mask == 0)
+ ) {
+ mask = tmask;
+ strcpy(device, tdevice);
+ }
+ }
+ fclose(f);
+
+ if (device[0] == '\0')
+ bb_error_msg_and_die("can't find interface");
+
+ /* Get the interface address list */
+ n = ifaddrlist(&al);
+
+ /* Find our appropriate source address */
+ for (i = n; i > 0; --i, ++al)
+ if (strcmp(device, al->device) == 0)
+ break;
+ if (i <= 0)
+ bb_error_msg_and_die("can't find interface %s", device);
+
+ setsin(from, al->addr);
+}
+
+/*
+"Usage: %s [-dFIlnrvx] [-g gateway] [-i iface] [-f first_ttl]\n"
+"\t[-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] [-t tos]\n"
+"\t[-w waittime] [-z pausemsecs] host [packetlen]"
+
+*/
+
+static int
+wait_for_reply(int sock, struct sockaddr_in *fromp)
+{
+ struct pollfd pfd[1];
+ int cc = 0;
+ socklen_t fromlen = sizeof(*fromp);
+
+ pfd[0].fd = sock;
+ pfd[0].events = POLLIN;
+ if (safe_poll(pfd, 1, waittime * 1000) > 0)
+ cc = recvfrom(sock, packet, sizeof(packet), 0,
+ (struct sockaddr *)fromp, &fromlen);
+ return cc;
+}
+
+/*
+ * Checksum routine for Internet Protocol family headers (C Version)
+ */
+static uint16_t
+in_cksum(uint16_t *addr, int len)
+{
+ int nleft = len;
+ uint16_t *w = addr;
+ uint16_t answer;
+ int sum = 0;
+
+ /*
+ * Our algorithm is simple, using a 32 bit accumulator (sum),
+ * we add sequential 16 bit words to it, and at the end, fold
+ * back all the carry bits from the top 16 bits into the lower
+ * 16 bits.
+ */
+ while (nleft > 1) {
+ sum += *w++;
+ nleft -= 2;
+ }
+
+ /* mop up an odd byte, if necessary */
+ if (nleft == 1)
+ sum += *(unsigned char *)w;
+
+ /*
+ * add back carry outs from top 16 bits to low 16 bits
+ */
+ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
+ sum += (sum >> 16); /* add carry */
+ answer = ~sum; /* truncate to 16 bits */
+ return answer;
+}
+
+
+static void
+send_probe(int seq, int ttl)
+{
+ int cc;
+ struct udpiphdr *ui, *oui;
+ struct ip tip;
+
+ outip->ip_ttl = ttl;
+ outip->ip_id = htons(ident + seq);
+
+ /*
+ * In most cases, the kernel will recalculate the ip checksum.
+ * But we must do it anyway so that the udp checksum comes out
+ * right.
+ */
+ if (doipcksum) {
+ outip->ip_sum =
+ in_cksum((uint16_t *)outip, sizeof(*outip) + optlen);
+ if (outip->ip_sum == 0)
+ outip->ip_sum = 0xffff;
+ }
+
+ /* Payload */
+ outdata->seq = seq;
+ outdata->ttl = ttl;
+// UNUSED: was storing gettimeofday's result there, but never ever checked it
+ /*memcpy(&outdata->tv, tp, sizeof(outdata->tv));*/
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+ if (useicmp)
+ outicmp->icmp_seq = htons(seq);
+ else
+#endif
+ outudp->dest = htons(port + seq);
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+ if (useicmp) {
+ /* Always calculate checksum for icmp packets */
+ outicmp->icmp_cksum = 0;
+ outicmp->icmp_cksum = in_cksum((uint16_t *)outicmp,
+ packlen - (sizeof(*outip) + optlen));
+ if (outicmp->icmp_cksum == 0)
+ outicmp->icmp_cksum = 0xffff;
+ } else
+#endif
+ if (doipcksum) {
+ /* Checksum (we must save and restore ip header) */
+ tip = *outip;
+ ui = (struct udpiphdr *)outip;
+ oui = (struct udpiphdr *)&tip;
+ /* Easier to zero and put back things that are ok */
+ memset((char *)ui, 0, sizeof(ui->ui_i));
+ ui->ui_src = oui->ui_src;
+ ui->ui_dst = oui->ui_dst;
+ ui->ui_pr = oui->ui_pr;
+ ui->ui_len = outudp->len;
+ outudp->check = 0;
+ outudp->check = in_cksum((uint16_t *)ui, packlen);
+ if (outudp->check == 0)
+ outudp->check = 0xffff;
+ *outip = tip;
+ }
+
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+ /* XXX undocumented debugging hack */
+ if (verbose > 1) {
+ const uint16_t *sp;
+ int nshorts, i;
+
+ sp = (uint16_t *)outip;
+ nshorts = (unsigned)packlen / sizeof(uint16_t);
+ i = 0;
+ printf("[ %d bytes", packlen);
+ while (--nshorts >= 0) {
+ if ((i++ % 8) == 0)
+ printf("\n\t");
+ printf(" %04x", ntohs(*sp));
+ sp++;
+ }
+ if (packlen & 1) {
+ if ((i % 8) == 0)
+ printf("\n\t");
+ printf(" %02x", *(unsigned char *)sp);
+ }
+ printf("]\n");
+ }
+#endif
+
+#if !defined(IP_HDRINCL) && defined(IP_TTL)
+ if (setsockopt(sndsock, IPPROTO_IP, IP_TTL,
+ (char *)&ttl, sizeof(ttl)) < 0) {
+ bb_perror_msg_and_die("setsockopt ttl %d", ttl);
+ }
+#endif
+
+ cc = xsendto(sndsock, (char *)outip,
+ packlen, (struct sockaddr *)&whereto, sizeof(whereto));
+ if (cc != packlen) {
+ bb_info_msg("wrote %s %d chars, ret=%d", hostname, packlen, cc);
+ }
+}
+
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+/*
+ * Convert an ICMP "type" field to a printable string.
+ */
+static inline const char *
+pr_type(unsigned char t)
+{
+ static const char *const ttab[] = {
+ "Echo Reply", "ICMP 1", "ICMP 2", "Dest Unreachable",
+ "Source Quench", "Redirect", "ICMP 6", "ICMP 7",
+ "Echo", "Router Advert", "Router Solicit", "Time Exceeded",
+ "Param Problem", "Timestamp", "Timestamp Reply", "Info Request",
+ "Info Reply", "Mask Request", "Mask Reply"
+ };
+
+ if (t > 18)
+ return "OUT-OF-RANGE";
+
+ return ttab[t];
+}
+#endif
+
+#if !ENABLE_FEATURE_TRACEROUTE_VERBOSE
+#define packet_ok(buf, cc, from, seq) \
+ packet_ok(buf, cc, seq)
+#endif
+static int
+packet_ok(unsigned char *buf, int cc, struct sockaddr_in *from, int seq)
+{
+ struct icmp *icp;
+ unsigned char type, code;
+ int hlen;
+ struct ip *ip;
+
+ ip = (struct ip *) buf;
+ hlen = ip->ip_hl << 2;
+ if (cc < hlen + ICMP_MINLEN) {
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+ if (verbose)
+ printf("packet too short (%d bytes) from %s\n", cc,
+ inet_ntoa(from->sin_addr));
+#endif
+ return 0;
+ }
+ cc -= hlen;
+ icp = (struct icmp *)(buf + hlen);
+ type = icp->icmp_type;
+ code = icp->icmp_code;
+ /* Path MTU Discovery (RFC1191) */
+ if (code != ICMP_UNREACH_NEEDFRAG)
+ pmtu = 0;
+ else {
+ pmtu = ntohs(icp->icmp_nextmtu);
+ }
+ if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS) ||
+ type == ICMP_UNREACH || type == ICMP_ECHOREPLY) {
+ struct ip *hip;
+ struct udphdr *up;
+
+ hip = &icp->icmp_ip;
+ hlen = hip->ip_hl << 2;
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+ if (useicmp) {
+ struct icmp *hicmp;
+
+ /* XXX */
+ if (type == ICMP_ECHOREPLY &&
+ icp->icmp_id == htons(ident) &&
+ icp->icmp_seq == htons(seq))
+ return -2;
+
+ hicmp = (struct icmp *)((unsigned char *)hip + hlen);
+ /* XXX 8 is a magic number */
+ if (hlen + 8 <= cc &&
+ hip->ip_p == IPPROTO_ICMP &&
+ hicmp->icmp_id == htons(ident) &&
+ hicmp->icmp_seq == htons(seq))
+ return (type == ICMP_TIMXCEED ? -1 : code + 1);
+ } else
+#endif
+ {
+ up = (struct udphdr *)((unsigned char *)hip + hlen);
+ /* XXX 8 is a magic number */
+ if (hlen + 12 <= cc &&
+ hip->ip_p == IPPROTO_UDP &&
+ up->source == htons(ident) &&
+ up->dest == htons(port + seq))
+ return (type == ICMP_TIMXCEED ? -1 : code + 1);
+ }
+ }
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+ if (verbose) {
+ int i;
+ uint32_t *lp = (uint32_t *)&icp->icmp_ip;
+
+ printf("\n%d bytes from %s to "
+ "%s: icmp type %d (%s) code %d\n",
+ cc, inet_ntoa(from->sin_addr),
+ inet_ntoa(ip->ip_dst), type, pr_type(type), icp->icmp_code);
+ for (i = 4; i < cc; i += sizeof(*lp))
+ printf("%2d: x%8.8x\n", i, *lp++);
+ }
+#endif
+ return 0;
+}
+
+
+/*
+ * Construct an Internet address representation.
+ * If the nflag has been supplied, give
+ * numeric value, otherwise try for symbolic name.
+ */
+static void
+print_inetname(struct sockaddr_in *from)
+{
+ const char *ina;
+
+ ina = inet_ntoa(from->sin_addr);
+ if (nflag)
+ printf(" %s", ina);
+ else {
+ char *n = NULL;
+ if (from->sin_addr.s_addr != INADDR_ANY)
+ n = xmalloc_sockaddr2host_noport((struct sockaddr*)from);
+ printf(" %s (%s)", (n ? n : ina), ina);
+ free(n);
+ }
+}
+
+static void
+print(unsigned char *buf, int cc, struct sockaddr_in *from)
+{
+ struct ip *ip;
+ int hlen;
+
+ ip = (struct ip *) buf;
+ hlen = ip->ip_hl << 2;
+ cc -= hlen;
+
+ print_inetname(from);
+#if ENABLE_FEATURE_TRACEROUTE_VERBOSE
+ if (verbose)
+ printf(" %d bytes to %s", cc, inet_ntoa(ip->ip_dst));
+#endif
+}
+
+
+static struct hostinfo *
+gethostinfo(const char *host)
+{
+ int n;
+ struct hostent *hp;
+ struct hostinfo *hi;
+ char **p;
+ uint32_t addr, *ap;
+
+ hi = xzalloc(sizeof(*hi));
+ addr = inet_addr(host);
+ if (addr != 0xffffffff) {
+ hi->name = xstrdup(host);
+ hi->n = 1;
+ hi->addrs = xzalloc(sizeof(hi->addrs[0]));
+ hi->addrs[0] = addr;
+ return hi;
+ }
+
+ hp = xgethostbyname(host);
+ if (hp->h_addrtype != AF_INET || hp->h_length != 4)
+ bb_perror_msg_and_die("bad host %s", host);
+ hi->name = xstrdup(hp->h_name);
+ for (n = 0, p = hp->h_addr_list; *p != NULL; ++n, ++p)
+ continue;
+ hi->n = n;
+ hi->addrs = xzalloc(n * sizeof(hi->addrs[0]));
+ for (ap = hi->addrs, p = hp->h_addr_list; *p != NULL; ++ap, ++p)
+ memcpy(ap, *p, sizeof(*ap));
+ return hi;
+}
+
+static void
+freehostinfo(struct hostinfo *hi)
+{
+ free(hi->name);
+ free(hi->addrs);
+ free(hi);
+}
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+static void
+getaddr(uint32_t *ap, const char *host)
+{
+ struct hostinfo *hi;
+
+ hi = gethostinfo(host);
+ *ap = hi->addrs[0];
+ freehostinfo(hi);
+}
+#endif
+
+static void
+print_delta_ms(unsigned t1p, unsigned t2p)
+{
+ unsigned tt = t2p - t1p;
+ printf(" %u.%03u ms", tt/1000, tt%1000);
+}
+
+int traceroute_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int traceroute_main(int argc, char **argv)
+{
+ int code, n;
+ unsigned char *outp;
+ uint32_t *ap;
+ struct sockaddr_in *from;
+ struct sockaddr_in *to;
+ struct hostinfo *hi;
+ int ttl, probe, i;
+ int seq = 0;
+ int tos = 0;
+ char *tos_str;
+ char *source;
+ unsigned op;
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ int lsrr = 0;
+#endif
+ uint16_t off = 0;
+ struct IFADDRLIST *al;
+ char *device;
+ int max_ttl = 30;
+ char *max_ttl_str;
+ char *port_str;
+ int nprobes = 3;
+ char *nprobes_str;
+ char *waittime_str;
+ unsigned pausemsecs = 0;
+ char *pausemsecs_str;
+ int first_ttl = 1;
+ char *first_ttl_str;
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ llist_t *source_route_list = NULL;
+#endif
+
+ INIT_G();
+ from = (struct sockaddr_in *)&wherefrom;
+ to = (struct sockaddr_in *)&whereto;
+
+ //opterr = 0;
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ opt_complementary = "x-x:g::";
+#else
+ opt_complementary = "x-x";
+#endif
+
+ op = getopt32(argv, "FIlnrdvxt:i:m:p:q:s:w:z:f:"
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ "g:"
+#endif
+ , &tos_str, &device, &max_ttl_str, &port_str, &nprobes_str
+ , &source, &waittime_str, &pausemsecs_str, &first_ttl_str
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ , &source_route_list
+#endif
+ );
+
+ if (op & OPT_DONT_FRAGMNT)
+ off = IP_DF;
+ if (op & OPT_IP_CHKSUM) {
+ doipcksum = 0;
+ bb_error_msg("warning: ip checksums disabled");
+ }
+ if (op & OPT_TOS)
+ tos = xatou_range(tos_str, 0, 255);
+ if (op & OPT_MAX_TTL)
+ max_ttl = xatou_range(max_ttl_str, 1, 255);
+ if (op & OPT_PORT)
+ port = xatou16(port_str);
+ if (op & OPT_NPROBES)
+ nprobes = xatou_range(nprobes_str, 1, INT_MAX);
+ if (op & OPT_SOURCE) {
+ /*
+ * set the ip source address of the outbound
+ * probe (e.g., on a multi-homed host).
+ */
+ if (getuid())
+ bb_error_msg_and_die("-s %s: permission denied", source);
+ }
+ if (op & OPT_WAITTIME)
+ waittime = xatou_range(waittime_str, 2, 24 * 60 * 60);
+ if (op & OPT_PAUSE_MS)
+ pausemsecs = xatou_range(pausemsecs_str, 0, 60 * 60 * 1000);
+ if (op & OPT_FIRST_TTL)
+ first_ttl = xatou_range(first_ttl_str, 1, 255);
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+ if (source_route_list) {
+ while (source_route_list) {
+ if (lsrr >= NGATEWAYS)
+ bb_error_msg_and_die("no more than %d gateways", NGATEWAYS);
+ getaddr(gwlist + lsrr, llist_pop(&source_route_list));
+ ++lsrr;
+ }
+ optlen = (lsrr + 1) * sizeof(gwlist[0]);
+ }
+#endif
+
+ if (first_ttl > max_ttl) {
+ bb_error_msg_and_die(
+ "first ttl (%d) may not be greater than max ttl (%d)",
+ first_ttl, max_ttl);
+ }
+
+ minpacket = sizeof(*outip) + sizeof(*outdata) + optlen;
+
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+ if (useicmp)
+ minpacket += 8; /* XXX magic number */
+ else
+#endif
+ minpacket += sizeof(*outudp);
+ packlen = minpacket; /* minimum sized packet */
+
+ /* Process destination and optional packet size */
+ switch (argc - optind) {
+
+ case 2:
+ packlen = xatoul_range(argv[optind + 1], minpacket, maxpacket);
+ /* Fall through */
+
+ case 1:
+ hostname = argv[optind];
+ hi = gethostinfo(hostname);
+ setsin(to, hi->addrs[0]);
+ if (hi->n > 1)
+ bb_error_msg("warning: %s has multiple addresses; using %s",
+ hostname, inet_ntoa(to->sin_addr));
+ hostname = hi->name;
+ hi->name = NULL;
+ freehostinfo(hi);
+ break;
+
+ default:
+ bb_show_usage();
+ }
+
+ /* Ensure the socket fds won't be 0, 1 or 2 */
+ bb_sanitize_stdio();
+
+ rcvsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
+
+#if TRACEROUTE_SO_DEBUG
+ if (op & OPT_DEBUG)
+ setsockopt(rcvsock, SOL_SOCKET, SO_DEBUG,
+ &const_int_1, sizeof(const_int_1));
+#endif
+ if (op & OPT_BYPASS_ROUTE)
+ setsockopt(rcvsock, SOL_SOCKET, SO_DONTROUTE,
+ &const_int_1, sizeof(const_int_1));
+
+ sndsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+
+#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE
+#if defined(IP_OPTIONS)
+ if (lsrr > 0) {
+ unsigned char optlist[MAX_IPOPTLEN];
+
+ /* final hop */
+ gwlist[lsrr] = to->sin_addr.s_addr;
+ ++lsrr;
+
+ /* force 4 byte alignment */
+ optlist[0] = IPOPT_NOP;
+ /* loose source route option */
+ optlist[1] = IPOPT_LSRR;
+ i = lsrr * sizeof(gwlist[0]);
+ optlist[2] = i + 3;
+ /* Pointer to LSRR addresses */
+ optlist[3] = IPOPT_MINOFF;
+ memcpy(optlist + 4, gwlist, i);
+
+ if ((setsockopt(sndsock, IPPROTO_IP, IP_OPTIONS,
+ (char *)optlist, i + sizeof(gwlist[0]))) < 0) {
+ bb_perror_msg_and_die("IP_OPTIONS");
+ }
+ }
+#endif /* IP_OPTIONS */
+#endif /* CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE */
+
+#ifdef SO_SNDBUF
+ if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, &packlen, sizeof(packlen)) < 0) {
+ bb_perror_msg_and_die("SO_SNDBUF");
+ }
+#endif
+#ifdef IP_HDRINCL
+ if (setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, &const_int_1, sizeof(const_int_1)) < 0
+ && errno != ENOPROTOOPT
+ ) {
+ bb_perror_msg_and_die("IP_HDRINCL");
+ }
+#else
+#ifdef IP_TOS
+ if (tos_str && setsockopt(sndsock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) {
+ bb_perror_msg_and_die("setsockopt tos %d", tos);
+ }
+#endif
+#endif
+#if TRACEROUTE_SO_DEBUG
+ if (op & OPT_DEBUG)
+ setsockopt(sndsock, SOL_SOCKET, SO_DEBUG,
+ &const_int_1, sizeof(const_int_1));
+#endif
+ if (op & OPT_BYPASS_ROUTE)
+ setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE,
+ &const_int_1, sizeof(const_int_1));
+
+ /* Revert to non-privileged user after opening sockets */
+ xsetgid(getgid());
+ xsetuid(getuid());
+
+ outip = xzalloc(packlen);
+
+ outip->ip_v = IPVERSION;
+ if (tos_str)
+ outip->ip_tos = tos;
+ outip->ip_len = htons(packlen);
+ outip->ip_off = htons(off);
+ outp = (unsigned char *)(outip + 1);
+ outip->ip_dst = to->sin_addr;
+
+ outip->ip_hl = (outp - (unsigned char *)outip) >> 2;
+ ident = (getpid() & 0xffff) | 0x8000;
+#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP
+ if (useicmp) {
+ outip->ip_p = IPPROTO_ICMP;
+ outicmp = (struct icmp *)outp;
+ outicmp->icmp_type = ICMP_ECHO;
+ outicmp->icmp_id = htons(ident);
+ outdata = (outdata_t *)(outp + 8); /* XXX magic number */
+ } else
+#endif
+ {
+ outip->ip_p = IPPROTO_UDP;
+ outudp = (struct udphdr *)outp;
+ outudp->source = htons(ident);
+ outudp->len = htons((uint16_t)(packlen - (sizeof(*outip) + optlen)));
+ outdata = (outdata_t *)(outudp + 1);
+ }
+
+ /* Get the interface address list */
+ n = ifaddrlist(&al);
+
+ /* Look for a specific device */
+ if (op & OPT_DEVICE) {
+ for (i = n; i > 0; --i, ++al)
+ if (strcmp(device, al->device) == 0)
+ goto found_dev;
+ bb_error_msg_and_die("can't find interface %s", device);
+ }
+ found_dev:
+
+ /* Determine our source address */
+ if (!(op & OPT_SOURCE)) {
+ /*
+ * If a device was specified, use the interface address.
+ * Otherwise, try to determine our source address.
+ */
+ if (op & OPT_DEVICE)
+ setsin(from, al->addr);
+ findsaddr(to, from);
+ } else {
+ hi = gethostinfo(source);
+ source = hi->name;
+ hi->name = NULL;
+ /*
+ * If the device was specified make sure it
+ * corresponds to the source address specified.
+ * Otherwise, use the first address (and warn if
+ * there are more than one).
+ */
+ if (op & OPT_DEVICE) {
+ for (i = hi->n, ap = hi->addrs; i > 0; --i, ++ap)
+ if (*ap == al->addr)
+ goto found_dev2;
+ bb_error_msg_and_die("%s is not on interface %s",
+ source, device);
+ found_dev2:
+ setsin(from, *ap);
+ } else {
+ setsin(from, hi->addrs[0]);
+ if (hi->n > 1)
+ bb_error_msg(
+ "warning: %s has multiple addresses; using %s",
+ source, inet_ntoa(from->sin_addr));
+ }
+ freehostinfo(hi);
+ }
+
+ outip->ip_src = from->sin_addr;
+#ifndef IP_HDRINCL
+ xbind(sndsock, (struct sockaddr *)from, sizeof(*from));
+#endif
+
+ printf("traceroute to %s (%s)", hostname, inet_ntoa(to->sin_addr));
+ if (op & OPT_SOURCE)
+ printf(" from %s", source);
+ printf(", %d hops max, %d byte packets\n", max_ttl, packlen);
+ fflush(stdout);
+
+ for (ttl = first_ttl; ttl <= max_ttl; ++ttl) {
+ uint32_t lastaddr = 0;
+ int gotlastaddr = 0;
+ int got_there = 0;
+ int unreachable = 0;
+ int sentfirst = 0;
+
+ printf("%2d ", ttl);
+ for (probe = 0; probe < nprobes; ++probe) {
+ int cc;
+ unsigned t1;
+ unsigned t2;
+ struct ip *ip;
+
+ if (sentfirst && pausemsecs > 0)
+ usleep(pausemsecs * 1000);
+ t1 = monotonic_us();
+ send_probe(++seq, ttl);
+ ++sentfirst;
+ while ((cc = wait_for_reply(rcvsock, from)) != 0) {
+ t2 = monotonic_us();
+ i = packet_ok(packet, cc, from, seq);
+ /* Skip short packet */
+ if (i == 0)
+ continue;
+ if (!gotlastaddr ||
+ from->sin_addr.s_addr != lastaddr) {
+ print(packet, cc, from);
+ lastaddr = from->sin_addr.s_addr;
+ ++gotlastaddr;
+ }
+ print_delta_ms(t1, t2);
+ ip = (struct ip *)packet;
+ if (op & OPT_TTL_FLAG)
+ printf(" (%d)", ip->ip_ttl);
+ if (i == -2) {
+ if (ip->ip_ttl <= 1)
+ printf(" !");
+ ++got_there;
+ break;
+ }
+ /* time exceeded in transit */
+ if (i == -1)
+ break;
+ code = i - 1;
+ switch (code) {
+
+ case ICMP_UNREACH_PORT:
+ if (ip->ip_ttl <= 1)
+ printf(" !");
+ ++got_there;
+ break;
+
+ case ICMP_UNREACH_NET:
+ ++unreachable;
+ printf(" !N");
+ break;
+
+ case ICMP_UNREACH_HOST:
+ ++unreachable;
+ printf(" !H");
+ break;
+
+ case ICMP_UNREACH_PROTOCOL:
+ ++got_there;
+ printf(" !P");
+ break;
+
+ case ICMP_UNREACH_NEEDFRAG:
+ ++unreachable;
+ printf(" !F-%d", pmtu);
+ break;
+
+ case ICMP_UNREACH_SRCFAIL:
+ ++unreachable;
+ printf(" !S");
+ break;
+
+ case ICMP_UNREACH_FILTER_PROHIB:
+ case ICMP_UNREACH_NET_PROHIB: /* misuse */
+ ++unreachable;
+ printf(" !A");
+ break;
+
+ case ICMP_UNREACH_HOST_PROHIB:
+ ++unreachable;
+ printf(" !C");
+ break;
+
+ case ICMP_UNREACH_HOST_PRECEDENCE:
+ ++unreachable;
+ printf(" !V");
+ break;
+
+ case ICMP_UNREACH_PRECEDENCE_CUTOFF:
+ ++unreachable;
+ printf(" !C");
+ break;
+
+ case ICMP_UNREACH_NET_UNKNOWN:
+ case ICMP_UNREACH_HOST_UNKNOWN:
+ ++unreachable;
+ printf(" !U");
+ break;
+
+ case ICMP_UNREACH_ISOLATED:
+ ++unreachable;
+ printf(" !I");
+ break;
+
+ case ICMP_UNREACH_TOSNET:
+ case ICMP_UNREACH_TOSHOST:
+ ++unreachable;
+ printf(" !T");
+ break;
+
+ default:
+ ++unreachable;
+ printf(" !<%d>", code);
+ break;
+ }
+ break;
+ }
+ if (cc == 0)
+ printf(" *");
+ (void)fflush(stdout);
+ }
+ bb_putchar('\n');
+ if (got_there ||
+ (unreachable > 0 && unreachable >= nprobes - 1))
+ break;
+ }
+ return 0;
+}
diff --git a/networking/udhcp/Config.in b/networking/udhcp/Config.in
new file mode 100644
index 0000000..d4b76e1
--- /dev/null
+++ b/networking/udhcp/Config.in
@@ -0,0 +1,122 @@
+#
+# For a description of the syntax of this configuration file,
+# see scripts/kbuild/config-language.txt.
+#
+
+config APP_UDHCPD
+ bool "udhcp server (udhcpd)"
+ default n
+ help
+ udhcpd is a DHCP server geared primarily toward embedded systems,
+ while striving to be fully functional and RFC compliant.
+
+config APP_DHCPRELAY
+ bool "dhcprelay"
+ default n
+ depends on APP_UDHCPD
+ help
+ dhcprelay listens for dhcp requests on one or more interfaces
+ and forwards these requests to a different interface or dhcp
+ server.
+
+config APP_DUMPLEASES
+ bool "Lease display utility (dumpleases)"
+ default n
+ depends on APP_UDHCPD
+ help
+ dumpleases displays the leases written out by the udhcpd server.
+ Lease times are stored in the file by time remaining in lease, or
+ by the absolute time that it expires in seconds from epoch.
+
+config FEATURE_UDHCPD_WRITE_LEASES_EARLY
+ bool "Rewrite the lease file at every new acknowledge"
+ default n
+ depends on APP_UDHCPD
+ help
+ If selected, udhcpd will write a new file with leases every
+ time a new lease has been accepted, thus eliminating the need
+ to send SIGUSR1 for the initial writing or updating. Any timed
+ rewriting remains undisturbed
+
+config DHCPD_LEASES_FILE
+ string "Absolute path to lease file"
+ default "/var/lib/misc/udhcpd.leases"
+ depends on APP_UDHCPD
+ help
+ udhcpd stores addresses in a lease file. This is the absolute path
+ of the file. Normally it is safe to leave it untouched.
+
+config APP_UDHCPC
+ bool "udhcp client (udhcpc)"
+ default n
+ help
+ udhcpc is a DHCP client geared primarily toward embedded systems,
+ while striving to be fully functional and RFC compliant.
+
+ The udhcp client negotiates a lease with the DHCP server and
+ runs a script when a lease is obtained or lost.
+
+config FEATURE_UDHCPC_ARPING
+ bool "Verify that the offered address is free, using ARP ping"
+ default y
+ depends on APP_UDHCPC
+ help
+ If selected, udhcpc will send ARP probes and make sure
+ the offered address is really not in use by anyone. The client
+ will DHCPDECLINE the offer if the address is in use,
+ and restart the discover process.
+
+config FEATURE_UDHCP_PORT
+ bool "Enable '-P port' option for udhcpd and udhcpc"
+ default n
+ depends on APP_UDHCPD || APP_UDHCPC
+ help
+ At the cost of ~300 bytes, enables -P port option.
+ This feature is typically not needed.
+
+config UDHCP_DEBUG
+ bool "Compile udhcp with noisy debugging messages"
+ default n
+ depends on APP_UDHCPD || APP_UDHCPC
+ help
+ If selected, udhcpd will output extra debugging output.
+
+config FEATURE_UDHCP_RFC3397
+ bool "Support for RFC3397 domain search (experimental)"
+ default n
+ depends on APP_UDHCPD || APP_UDHCPC
+ help
+ If selected, both client and server will support passing of domain
+ search lists via option 119, specified in RFC3397.
+
+config UDHCPC_DEFAULT_SCRIPT
+ string "Absolute path to config script"
+ default "/usr/share/udhcpc/default.script"
+ depends on APP_UDHCPC
+ help
+ This script is called after udhcpc receives an answer. See
+ examples/udhcp for a working example. Normally it is safe
+ to leave this untouched.
+
+config UDHCPC_SLACK_FOR_BUGGY_SERVERS
+ int "DHCP options slack buffer size"
+ default 80
+ range 0 924
+ depends on APP_UDHCPD || APP_UDHCPC
+ help
+ Some buggy DHCP servers send DHCP offer packets with option
+ field larger than we expect (which might also be considered a
+ buffer overflow attempt). These packets are normally discarded.
+ If circumstances beyond your control force you to support such
+ servers, this may help. The upper limit (924) makes dhcpc accept
+ even 1500 byte packets (maximum-sized ethernet packets).
+
+ This option does not make dhcp[cd] emit non-standard
+ sized packets.
+
+ Known buggy DHCP servers:
+ 3Com OfficeConnect Remote 812 ADSL Router:
+ seems to confuse maximum allowed UDP packet size with
+ maximum size of entire IP packet, and sends packets which are
+ 28 bytes too large.
+ Seednet (ISP) VDSL: sends packets 2 bytes too large.
diff --git a/networking/udhcp/Kbuild b/networking/udhcp/Kbuild
new file mode 100644
index 0000000..e938076
--- /dev/null
+++ b/networking/udhcp/Kbuild
@@ -0,0 +1,25 @@
+# Makefile for busybox
+#
+# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
+#
+# Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+#
+
+lib-y:=
+lib-$(CONFIG_APP_UDHCPC) += common.o options.o packet.o \
+ signalpipe.o socket.o
+lib-$(CONFIG_APP_UDHCPD) += common.o options.o packet.o \
+ signalpipe.o socket.o
+
+lib-$(CONFIG_APP_UDHCPC) += dhcpc.o clientpacket.o clientsocket.o \
+ script.o
+
+UDHCPC_NEEDS_ARPING-$(CONFIG_FEATURE_UDHCPC_ARPING) = y
+lib-$(UDHCPC_NEEDS_ARPING-y) += arpping.o
+
+lib-$(CONFIG_APP_UDHCPD) += dhcpd.o arpping.o files.o leases.o \
+ serverpacket.o static_leases.o
+
+lib-$(CONFIG_APP_DUMPLEASES) += dumpleases.o
+lib-$(CONFIG_APP_DHCPRELAY) += dhcprelay.o
+lib-$(CONFIG_FEATURE_UDHCP_RFC3397) += domain_codec.o
diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c
new file mode 100644
index 0000000..e0710dc
--- /dev/null
+++ b/networking/udhcp/arpping.c
@@ -0,0 +1,116 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * arpping.c
+ *
+ * Mostly stolen from: dhcpcd - DHCP client daemon
+ * by Yoichi Hariguchi <yoichi@fore.com>
+ */
+
+#include <netinet/if_ether.h>
+#include <net/if_arp.h>
+
+#include "common.h"
+#include "dhcpd.h"
+
+
+struct arpMsg {
+ /* Ethernet header */
+ uint8_t h_dest[6]; /* 00 destination ether addr */
+ uint8_t h_source[6]; /* 06 source ether addr */
+ uint16_t h_proto; /* 0c packet type ID field */
+
+ /* ARP packet */
+ uint16_t htype; /* 0e hardware type (must be ARPHRD_ETHER) */
+ uint16_t ptype; /* 10 protocol type (must be ETH_P_IP) */
+ uint8_t hlen; /* 12 hardware address length (must be 6) */
+ uint8_t plen; /* 13 protocol address length (must be 4) */
+ uint16_t operation; /* 14 ARP opcode */
+ uint8_t sHaddr[6]; /* 16 sender's hardware address */
+ uint8_t sInaddr[4]; /* 1c sender's IP address */
+ uint8_t tHaddr[6]; /* 20 target's hardware address */
+ uint8_t tInaddr[4]; /* 26 target's IP address */
+ uint8_t pad[18]; /* 2a pad for min. ethernet payload (60 bytes) */
+} PACKED;
+
+enum {
+ ARP_MSG_SIZE = 0x2a
+};
+
+
+/* Returns 1 if no reply received */
+
+int FAST_FUNC arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface)
+{
+ int timeout_ms;
+ struct pollfd pfd[1];
+#define s (pfd[0].fd) /* socket */
+ int rv = 1; /* "no reply received" yet */
+ struct sockaddr addr; /* for interface name */
+ struct arpMsg arp;
+
+ s = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP));
+ if (s == -1) {
+ bb_perror_msg(bb_msg_can_not_create_raw_socket);
+ return -1;
+ }
+
+ if (setsockopt_broadcast(s) == -1) {
+ bb_perror_msg("cannot enable bcast on raw socket");
+ goto ret;
+ }
+
+ /* send arp request */
+ memset(&arp, 0, sizeof(arp));
+ memset(arp.h_dest, 0xff, 6); /* MAC DA */
+ memcpy(arp.h_source, from_mac, 6); /* MAC SA */
+ arp.h_proto = htons(ETH_P_ARP); /* protocol type (Ethernet) */
+ arp.htype = htons(ARPHRD_ETHER); /* hardware type */
+ arp.ptype = htons(ETH_P_IP); /* protocol type (ARP message) */
+ arp.hlen = 6; /* hardware address length */
+ arp.plen = 4; /* protocol address length */
+ arp.operation = htons(ARPOP_REQUEST); /* ARP op code */
+ memcpy(arp.sHaddr, from_mac, 6); /* source hardware address */
+ memcpy(arp.sInaddr, &from_ip, sizeof(from_ip)); /* source IP address */
+ /* tHaddr is zero-fiiled */ /* target hardware address */
+ memcpy(arp.tInaddr, &test_ip, sizeof(test_ip)); /* target IP address */
+
+ memset(&addr, 0, sizeof(addr));
+ safe_strncpy(addr.sa_data, interface, sizeof(addr.sa_data));
+ if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) {
+ // TODO: error message? caller didn't expect us to fail,
+ // just returning 1 "no reply received" misleads it.
+ goto ret;
+ }
+
+ /* wait for arp reply, and check it */
+ timeout_ms = 2000;
+ do {
+ int r;
+ unsigned prevTime = monotonic_us();
+
+ pfd[0].events = POLLIN;
+ r = safe_poll(pfd, 1, timeout_ms);
+ if (r < 0)
+ break;
+ if (r) {
+ r = read(s, &arp, sizeof(arp));
+ if (r < 0)
+ break;
+ if (r >= ARP_MSG_SIZE
+ && arp.operation == htons(ARPOP_REPLY)
+ /* don't check it: Linux doesn't return proper tHaddr (fixed in 2.6.24?) */
+ /* && memcmp(arp.tHaddr, from_mac, 6) == 0 */
+ && *((uint32_t *) arp.sInaddr) == test_ip
+ ) {
+ rv = 0;
+ break;
+ }
+ }
+ timeout_ms -= ((unsigned)monotonic_us() - prevTime) / 1000;
+ } while (timeout_ms > 0);
+
+ ret:
+ close(s);
+ DEBUG("%srp reply received for this address", rv ? "No a" : "A");
+ return rv;
+}
diff --git a/networking/udhcp/clientpacket.c b/networking/udhcp/clientpacket.c
new file mode 100644
index 0000000..3f9522f
--- /dev/null
+++ b/networking/udhcp/clientpacket.c
@@ -0,0 +1,271 @@
+/* vi: set sw=4 ts=4: */
+/* clientpacket.c
+ *
+ * Packet generation and dispatching functions for the DHCP client.
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <features.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <asm/types.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+
+#include "common.h"
+#include "dhcpd.h"
+#include "dhcpc.h"
+#include "options.h"
+
+
+/* Create a random xid */
+uint32_t FAST_FUNC random_xid(void)
+{
+ static smallint initialized;
+
+ if (!initialized) {
+ srand(monotonic_us());
+ initialized = 1;
+ }
+ return rand();
+}
+
+
+/* initialize a packet with the proper defaults */
+static void init_packet(struct dhcpMessage *packet, char type)
+{
+ udhcp_init_header(packet, type);
+ memcpy(packet->chaddr, client_config.arp, 6);
+ if (client_config.clientid)
+ add_option_string(packet->options, client_config.clientid);
+ if (client_config.hostname)
+ add_option_string(packet->options, client_config.hostname);
+ if (client_config.fqdn)
+ add_option_string(packet->options, client_config.fqdn);
+ if ((type != DHCPDECLINE) && (type != DHCPRELEASE))
+ add_option_string(packet->options, client_config.vendorclass);
+}
+
+
+/* Add a parameter request list for stubborn DHCP servers. Pull the data
+ * from the struct in options.c. Don't do bounds checking here because it
+ * goes towards the head of the packet. */
+static void add_param_req_option(struct dhcpMessage *packet)
+{
+ uint8_t c;
+ int end = end_option(packet->options);
+ int i, len = 0;
+
+ for (i = 0; (c = dhcp_options[i].code) != 0; i++) {
+ if (((dhcp_options[i].flags & OPTION_REQ)
+ && !client_config.no_default_options)
+ || (client_config.opt_mask[c >> 3] & (1 << (c & 7)))
+ ) {
+ packet->options[end + OPT_DATA + len] = c;
+ len++;
+ }
+ }
+ if (len) {
+ packet->options[end + OPT_CODE] = DHCP_PARAM_REQ;
+ packet->options[end + OPT_LEN] = len;
+ packet->options[end + OPT_DATA + len] = DHCP_END;
+ }
+}
+
+/* RFC 2131
+ * 4.4.4 Use of broadcast and unicast
+ *
+ * The DHCP client broadcasts DHCPDISCOVER, DHCPREQUEST and DHCPINFORM
+ * messages, unless the client knows the address of a DHCP server.
+ * The client unicasts DHCPRELEASE messages to the server. Because
+ * the client is declining the use of the IP address supplied by the server,
+ * the client broadcasts DHCPDECLINE messages.
+ *
+ * When the DHCP client knows the address of a DHCP server, in either
+ * INIT or REBOOTING state, the client may use that address
+ * in the DHCPDISCOVER or DHCPREQUEST rather than the IP broadcast address.
+ * The client may also use unicast to send DHCPINFORM messages
+ * to a known DHCP server. If the client receives no response to DHCP
+ * messages sent to the IP address of a known DHCP server, the DHCP
+ * client reverts to using the IP broadcast address.
+ */
+
+static int raw_bcast_from_client_config_ifindex(struct dhcpMessage *packet)
+{
+ return udhcp_send_raw_packet(packet,
+ /*src*/ INADDR_ANY, CLIENT_PORT,
+ /*dst*/ INADDR_BROADCAST, SERVER_PORT, MAC_BCAST_ADDR,
+ client_config.ifindex);
+}
+
+
+#if ENABLE_FEATURE_UDHCPC_ARPING
+/* Broadcast a DHCP decline message */
+int FAST_FUNC send_decline(uint32_t xid, uint32_t server, uint32_t requested)
+{
+ struct dhcpMessage packet;
+
+ init_packet(&packet, DHCPDECLINE);
+ packet.xid = xid;
+ add_simple_option(packet.options, DHCP_REQUESTED_IP, requested);
+ add_simple_option(packet.options, DHCP_SERVER_ID, server);
+
+ bb_info_msg("Sending decline...");
+
+ return raw_bcast_from_client_config_ifindex(&packet);
+}
+#endif
+
+/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */
+int FAST_FUNC send_discover(uint32_t xid, uint32_t requested)
+{
+ struct dhcpMessage packet;
+
+ init_packet(&packet, DHCPDISCOVER);
+ packet.xid = xid;
+ if (requested)
+ add_simple_option(packet.options, DHCP_REQUESTED_IP, requested);
+
+ /* Explicitly saying that we want RFC-compliant packets helps
+ * some buggy DHCP servers to NOT send bigger packets */
+ add_simple_option(packet.options, DHCP_MAX_SIZE, htons(576));
+
+ add_param_req_option(&packet);
+
+ bb_info_msg("Sending discover...");
+ return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+
+/* Broadcasts a DHCP request message */
+/* RFC 2131 3.1 paragraph 3:
+ * "The client _broadcasts_ a DHCPREQUEST message..."
+ */
+int FAST_FUNC send_select(uint32_t xid, uint32_t server, uint32_t requested)
+{
+ struct dhcpMessage packet;
+ struct in_addr addr;
+
+ init_packet(&packet, DHCPREQUEST);
+ packet.xid = xid;
+
+ add_simple_option(packet.options, DHCP_REQUESTED_IP, requested);
+ add_simple_option(packet.options, DHCP_SERVER_ID, server);
+ add_param_req_option(&packet);
+
+ addr.s_addr = requested;
+ bb_info_msg("Sending select for %s...", inet_ntoa(addr));
+ return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+
+/* Unicasts or broadcasts a DHCP renew message */
+int FAST_FUNC send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr)
+{
+ struct dhcpMessage packet;
+
+ init_packet(&packet, DHCPREQUEST);
+ packet.xid = xid;
+ packet.ciaddr = ciaddr;
+
+ add_param_req_option(&packet);
+ bb_info_msg("Sending renew...");
+ if (server)
+ return udhcp_send_kernel_packet(&packet,
+ ciaddr, CLIENT_PORT,
+ server, SERVER_PORT);
+
+ return raw_bcast_from_client_config_ifindex(&packet);
+}
+
+
+/* Unicasts a DHCP release message */
+int FAST_FUNC send_release(uint32_t server, uint32_t ciaddr)
+{
+ struct dhcpMessage packet;
+
+ init_packet(&packet, DHCPRELEASE);
+ packet.xid = random_xid();
+ packet.ciaddr = ciaddr;
+
+ add_simple_option(packet.options, DHCP_SERVER_ID, server);
+
+ bb_info_msg("Sending release...");
+ return udhcp_send_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT);
+}
+
+
+/* Returns -1 on errors that are fatal for the socket, -2 for those that aren't */
+int FAST_FUNC udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd)
+{
+ int bytes;
+ struct udp_dhcp_packet packet;
+ uint16_t check;
+
+ memset(&packet, 0, sizeof(packet));
+ bytes = safe_read(fd, &packet, sizeof(packet));
+ if (bytes < 0) {
+ DEBUG("Cannot read on raw listening socket - ignoring");
+ /* NB: possible down interface, etc. Caller should pause. */
+ return bytes; /* returns -1 */
+ }
+
+ if (bytes < (int) (sizeof(packet.ip) + sizeof(packet.udp))) {
+ DEBUG("Packet is too short, ignoring");
+ return -2;
+ }
+
+ if (bytes < ntohs(packet.ip.tot_len)) {
+ /* packet is bigger than sizeof(packet), we did partial read */
+ DEBUG("Oversized packet, ignoring");
+ return -2;
+ }
+
+ /* ignore any extra garbage bytes */
+ bytes = ntohs(packet.ip.tot_len);
+
+ /* make sure its the right packet for us, and that it passes sanity checks */
+ if (packet.ip.protocol != IPPROTO_UDP || packet.ip.version != IPVERSION
+ || packet.ip.ihl != (sizeof(packet.ip) >> 2)
+ || packet.udp.dest != htons(CLIENT_PORT)
+ /* || bytes > (int) sizeof(packet) - can't happen */
+ || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip))
+ ) {
+ DEBUG("Unrelated/bogus packet");
+ return -2;
+ }
+
+ /* verify IP checksum */
+ check = packet.ip.check;
+ packet.ip.check = 0;
+ if (check != udhcp_checksum(&packet.ip, sizeof(packet.ip))) {
+ DEBUG("Bad IP header checksum, ignoring");
+ return -2;
+ }
+
+ /* verify UDP checksum. IP header has to be modified for this */
+ memset(&packet.ip, 0, offsetof(struct iphdr, protocol));
+ /* ip.xx fields which are not memset: protocol, check, saddr, daddr */
+ packet.ip.tot_len = packet.udp.len; /* yes, this is needed */
+ check = packet.udp.check;
+ packet.udp.check = 0;
+ if (check && check != udhcp_checksum(&packet, bytes)) {
+ bb_error_msg("packet with bad UDP checksum received, ignoring");
+ return -2;
+ }
+
+ memcpy(payload, &packet.data, bytes - (sizeof(packet.ip) + sizeof(packet.udp)));
+
+ if (payload->cookie != htonl(DHCP_MAGIC)) {
+ bb_error_msg("received bogus message (bad magic), ignoring");
+ return -2;
+ }
+ DEBUG("Got valid DHCP packet");
+ return bytes - (sizeof(packet.ip) + sizeof(packet.udp));
+}
diff --git a/networking/udhcp/clientsocket.c b/networking/udhcp/clientsocket.c
new file mode 100644
index 0000000..1dcc105
--- /dev/null
+++ b/networking/udhcp/clientsocket.c
@@ -0,0 +1,108 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * clientsocket.c -- DHCP client socket creation
+ *
+ * udhcp client
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <features.h>
+#include <asm/types.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION)
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+#include <linux/filter.h>
+
+#include "common.h"
+#include "dhcpd.h"
+#include "dhcpc.h"
+
+int FAST_FUNC udhcp_raw_socket(int ifindex)
+{
+ int fd;
+ struct sockaddr_ll sock;
+
+ /*
+ * Comment:
+ *
+ * I've selected not to see LL header, so BPF doesn't see it, too.
+ * The filter may also pass non-IP and non-ARP packets, but we do
+ * a more complete check when receiving the message in userspace.
+ *
+ * and filter shamelessly stolen from:
+ *
+ * http://www.flamewarmaster.de/software/dhcpclient/
+ *
+ * There are a few other interesting ideas on that page (look under
+ * "Motivation"). Use of netlink events is most interesting. Think
+ * of various network servers listening for events and reconfiguring.
+ * That would obsolete sending HUP signals and/or make use of restarts.
+ *
+ * Copyright: 2006, 2007 Stefan Rompf <sux@loplof.de>.
+ * License: GPL v2.
+ *
+ * TODO: make conditional?
+ */
+#define SERVER_AND_CLIENT_PORTS ((67 << 16) + 68)
+ static const struct sock_filter filter_instr[] = {
+ /* check for udp */
+ BPF_STMT(BPF_LD|BPF_B|BPF_ABS, 9),
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, IPPROTO_UDP, 2, 0), /* L5, L1, is UDP? */
+ /* ugly check for arp on ethernet-like and IPv4 */
+ BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2), /* L1: */
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 0x08000604, 3, 4), /* L3, L4 */
+ /* skip IP header */
+ BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0), /* L5: */
+ /* check udp source and destination ports */
+ BPF_STMT(BPF_LD|BPF_W|BPF_IND, 0),
+ BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, SERVER_AND_CLIENT_PORTS, 0, 1), /* L3, L4 */
+ /* returns */
+ BPF_STMT(BPF_RET|BPF_K, 0x0fffffff ), /* L3: pass */
+ BPF_STMT(BPF_RET|BPF_K, 0), /* L4: reject */
+ };
+ static const struct sock_fprog filter_prog = {
+ .len = sizeof(filter_instr) / sizeof(filter_instr[0]),
+ /* casting const away: */
+ .filter = (struct sock_filter *) filter_instr,
+ };
+
+ DEBUG("opening raw socket on ifindex %d", ifindex);
+
+ fd = xsocket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+ DEBUG("got raw socket fd %d", fd);
+
+ if (SERVER_PORT == 67 && CLIENT_PORT == 68) {
+ /* Use only if standard ports are in use */
+ /* Ignoring error (kernel may lack support for this) */
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog,
+ sizeof(filter_prog)) >= 0)
+ DEBUG("attached filter to raw socket fd %d", fd);
+ }
+
+ sock.sll_family = AF_PACKET;
+ sock.sll_protocol = htons(ETH_P_IP);
+ sock.sll_ifindex = ifindex;
+ xbind(fd, (struct sockaddr *) &sock, sizeof(sock));
+ DEBUG("bound to raw socket fd %d", fd);
+
+ return fd;
+}
diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c
new file mode 100644
index 0000000..a47bbaf
--- /dev/null
+++ b/networking/udhcp/common.c
@@ -0,0 +1,11 @@
+/* vi: set sw=4 ts=4: */
+/* common.c
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "common.h"
+
+const uint8_t MAC_BCAST_ADDR[6] ALIGN2 = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
new file mode 100644
index 0000000..15f0d9a
--- /dev/null
+++ b/networking/udhcp/common.h
@@ -0,0 +1,110 @@
+/* vi: set sw=4 ts=4: */
+/* common.h
+ *
+ * Russ Dill <Russ.Dill@asu.edu> September 2001
+ * Rewritten by Vladimir Oleynik <dzo@simtreas.ru> (C) 2003
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#include "libbb.h"
+#include <netinet/udp.h>
+#include <netinet/ip.h>
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+#define DEFAULT_SCRIPT CONFIG_UDHCPC_DEFAULT_SCRIPT
+
+extern const uint8_t MAC_BCAST_ADDR[6]; /* six all-ones */
+
+/*** packet.h ***/
+
+#define DHCP_OPTIONS_BUFSIZE 308
+
+struct dhcpMessage {
+ uint8_t op; /* 1 = BOOTREQUEST, 2 = BOOTREPLY */
+ uint8_t htype; /* hardware address type. 1 = 10mb ethernet */
+ uint8_t hlen; /* hardware address length */
+ uint8_t hops; /* used by relay agents only */
+ uint32_t xid; /* unique id */
+ uint16_t secs; /* elapsed since client began acquisition/renewal */
+ uint16_t flags; /* only one flag so far: */
+#define BROADCAST_FLAG 0x8000 /* "I need broadcast replies" */
+ uint32_t ciaddr; /* client IP (if client is in BOUND, RENEW or REBINDING state) */
+ uint32_t yiaddr; /* 'your' (client) IP address */
+ uint32_t siaddr; /* IP address of next server to use in bootstrap,
+ * returned in DHCPOFFER, DHCPACK by server */
+ uint32_t giaddr; /* relay agent IP address */
+ uint8_t chaddr[16];/* link-layer client hardware address (MAC) */
+ uint8_t sname[64]; /* server host name (ASCIZ) */
+ uint8_t file[128]; /* boot file name (ASCIZ) */
+ uint32_t cookie; /* fixed first four option bytes (99,130,83,99 dec) */
+ uint8_t options[DHCP_OPTIONS_BUFSIZE + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS];
+} PACKED;
+
+struct udp_dhcp_packet {
+ struct iphdr ip;
+ struct udphdr udp;
+ struct dhcpMessage data;
+} PACKED;
+
+/* Let's see whether compiler understood us right */
+struct BUG_bad_sizeof_struct_udp_dhcp_packet {
+ char BUG_bad_sizeof_struct_udp_dhcp_packet
+ [(sizeof(struct udp_dhcp_packet) != 576 + CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS) ? -1 : 1];
+};
+
+uint16_t udhcp_checksum(void *addr, int count) FAST_FUNC;
+
+void udhcp_init_header(struct dhcpMessage *packet, char type) FAST_FUNC;
+
+/*int udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd); - in dhcpc.h */
+int udhcp_recv_kernel_packet(struct dhcpMessage *packet, int fd) FAST_FUNC;
+
+int udhcp_send_raw_packet(struct dhcpMessage *payload,
+ uint32_t source_ip, int source_port,
+ uint32_t dest_ip, int dest_port, const uint8_t *dest_arp,
+ int ifindex) FAST_FUNC;
+
+int udhcp_send_kernel_packet(struct dhcpMessage *payload,
+ uint32_t source_ip, int source_port,
+ uint32_t dest_ip, int dest_port) FAST_FUNC;
+
+
+/**/
+
+void udhcp_run_script(struct dhcpMessage *packet, const char *name) FAST_FUNC;
+
+// Still need to clean these up...
+
+/* from options.h */
+#define get_option udhcp_get_option
+#define end_option udhcp_end_option
+#define add_option_string udhcp_add_option_string
+#define add_simple_option udhcp_add_simple_option
+
+void udhcp_sp_setup(void) FAST_FUNC;
+int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) FAST_FUNC;
+int udhcp_sp_read(const fd_set *rfds) FAST_FUNC;
+int udhcp_read_interface(const char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) FAST_FUNC;
+int udhcp_raw_socket(int ifindex) FAST_FUNC;
+int udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf) FAST_FUNC;
+/* Returns 1 if no reply received */
+int arpping(uint32_t test_ip, uint32_t from_ip, uint8_t *from_mac, const char *interface) FAST_FUNC;
+
+#if ENABLE_UDHCP_DEBUG
+# define DEBUG(str, args...) bb_info_msg("### " str, ## args)
+#else
+# define DEBUG(str, args...) do {;} while (0)
+#endif
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
new file mode 100644
index 0000000..2d48980
--- /dev/null
+++ b/networking/udhcp/dhcpc.c
@@ -0,0 +1,646 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpc.c
+ *
+ * udhcp DHCP client
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include <syslog.h>
+
+/* Override ENABLE_FEATURE_PIDFILE - ifupdown needs our pidfile to always exist */
+#define WANT_PIDFILE 1
+#include "common.h"
+#include "dhcpd.h"
+#include "dhcpc.h"
+#include "options.h"
+
+
+static int sockfd = -1;
+
+#define LISTEN_NONE 0
+#define LISTEN_KERNEL 1
+#define LISTEN_RAW 2
+static smallint listen_mode;
+
+#define INIT_SELECTING 0
+#define REQUESTING 1
+#define BOUND 2
+#define RENEWING 3
+#define REBINDING 4
+#define INIT_REBOOT 5
+#define RENEW_REQUESTED 6
+#define RELEASED 7
+static smallint state;
+
+/* struct client_config_t client_config is in bb_common_bufsiz1 */
+
+
+/* just a little helper */
+static void change_listen_mode(int new_mode)
+{
+ DEBUG("entering %s listen mode",
+ new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none");
+ if (sockfd >= 0) {
+ close(sockfd);
+ sockfd = -1;
+ }
+ listen_mode = new_mode;
+}
+
+
+/* perform a renew */
+static void perform_renew(void)
+{
+ bb_info_msg("Performing a DHCP renew");
+ switch (state) {
+ case BOUND:
+ change_listen_mode(LISTEN_KERNEL);
+ case RENEWING:
+ case REBINDING:
+ state = RENEW_REQUESTED;
+ break;
+ case RENEW_REQUESTED: /* impatient are we? fine, square 1 */
+ udhcp_run_script(NULL, "deconfig");
+ case REQUESTING:
+ case RELEASED:
+ change_listen_mode(LISTEN_RAW);
+ state = INIT_SELECTING;
+ break;
+ case INIT_SELECTING:
+ break;
+ }
+}
+
+
+/* perform a release */
+static void perform_release(uint32_t requested_ip, uint32_t server_addr)
+{
+ char buffer[sizeof("255.255.255.255")];
+ struct in_addr temp_addr;
+
+ /* send release packet */
+ if (state == BOUND || state == RENEWING || state == REBINDING) {
+ temp_addr.s_addr = server_addr;
+ strcpy(buffer, inet_ntoa(temp_addr));
+ temp_addr.s_addr = requested_ip;
+ bb_info_msg("Unicasting a release of %s to %s",
+ inet_ntoa(temp_addr), buffer);
+ send_release(server_addr, requested_ip); /* unicast */
+ udhcp_run_script(NULL, "deconfig");
+ }
+ bb_info_msg("Entering released state");
+
+ change_listen_mode(LISTEN_NONE);
+ state = RELEASED;
+}
+
+
+#if BB_MMU
+static void client_background(void)
+{
+ bb_daemonize(0);
+ logmode &= ~LOGMODE_STDIO;
+ /* rewrite pidfile, as our pid is different now */
+ write_pidfile(client_config.pidfile);
+}
+#endif
+
+
+static uint8_t* alloc_dhcp_option(int code, const char *str, int extra)
+{
+ uint8_t *storage;
+ int len = strlen(str);
+ if (len > 255) len = 255;
+ storage = xzalloc(len + extra + OPT_DATA);
+ storage[OPT_CODE] = code;
+ storage[OPT_LEN] = len + extra;
+ memcpy(storage + extra + OPT_DATA, str, len);
+ return storage;
+}
+
+
+int udhcpc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int udhcpc_main(int argc UNUSED_PARAM, char **argv)
+{
+ uint8_t *temp, *message;
+ char *str_c, *str_V, *str_h, *str_F, *str_r;
+ USE_FEATURE_UDHCP_PORT(char *str_P;)
+ llist_t *list_O = NULL;
+ int tryagain_timeout = 20;
+ int discover_timeout = 3;
+ int discover_retries = 3;
+ uint32_t server_addr = server_addr; /* for compiler */
+ uint32_t requested_ip = 0;
+ uint32_t xid = 0;
+ uint32_t lease_seconds = 0; /* can be given as 32-bit quantity */
+ int packet_num;
+ int timeout; /* must be signed */
+ unsigned already_waited_sec;
+ unsigned opt;
+ int max_fd;
+ int retval;
+ struct timeval tv;
+ struct dhcpMessage packet;
+ fd_set rfds;
+
+#if ENABLE_GETOPT_LONG
+ static const char udhcpc_longopts[] ALIGN1 =
+ "clientid\0" Required_argument "c"
+ "clientid-none\0" No_argument "C"
+ "vendorclass\0" Required_argument "V"
+ "hostname\0" Required_argument "H"
+ "fqdn\0" Required_argument "F"
+ "interface\0" Required_argument "i"
+ "now\0" No_argument "n"
+ "pidfile\0" Required_argument "p"
+ "quit\0" No_argument "q"
+ "release\0" No_argument "R"
+ "request\0" Required_argument "r"
+ "script\0" Required_argument "s"
+ "timeout\0" Required_argument "T"
+ "version\0" No_argument "v"
+ "retries\0" Required_argument "t"
+ "tryagain\0" Required_argument "A"
+ "syslog\0" No_argument "S"
+ "request-option\0" Required_argument "O"
+ "no-default-options\0" No_argument "o"
+ "foreground\0" No_argument "f"
+ "background\0" No_argument "b"
+ USE_FEATURE_UDHCPC_ARPING("arping\0" No_argument "a")
+ USE_FEATURE_UDHCP_PORT("client-port\0" Required_argument "P")
+ ;
+#endif
+ enum {
+ OPT_c = 1 << 0,
+ OPT_C = 1 << 1,
+ OPT_V = 1 << 2,
+ OPT_H = 1 << 3,
+ OPT_h = 1 << 4,
+ OPT_F = 1 << 5,
+ OPT_i = 1 << 6,
+ OPT_n = 1 << 7,
+ OPT_p = 1 << 8,
+ OPT_q = 1 << 9,
+ OPT_R = 1 << 10,
+ OPT_r = 1 << 11,
+ OPT_s = 1 << 12,
+ OPT_T = 1 << 13,
+ OPT_t = 1 << 14,
+ OPT_v = 1 << 15,
+ OPT_S = 1 << 16,
+ OPT_A = 1 << 17,
+ OPT_O = 1 << 18,
+ OPT_o = 1 << 19,
+ OPT_f = 1 << 20,
+/* The rest has variable bit positions, need to be clever */
+ OPTBIT_f = 20,
+ USE_FOR_MMU( OPTBIT_b,)
+ USE_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
+ USE_FEATURE_UDHCP_PORT( OPTBIT_P,)
+ USE_FOR_MMU( OPT_b = 1 << OPTBIT_b,)
+ USE_FEATURE_UDHCPC_ARPING(OPT_a = 1 << OPTBIT_a,)
+ USE_FEATURE_UDHCP_PORT( OPT_P = 1 << OPTBIT_P,)
+ };
+
+ /* Default options. */
+ USE_FEATURE_UDHCP_PORT(SERVER_PORT = 67;)
+ USE_FEATURE_UDHCP_PORT(CLIENT_PORT = 68;)
+ client_config.interface = "eth0";
+ client_config.script = DEFAULT_SCRIPT;
+
+ /* Parse command line */
+ /* Cc: mutually exclusive; O: list; -T,-t,-A take numeric param */
+ opt_complementary = "c--C:C--c:O::T+:t+:A+";
+ USE_GETOPT_LONG(applet_long_options = udhcpc_longopts;)
+ opt = getopt32(argv, "c:CV:H:h:F:i:np:qRr:s:T:t:vSA:O:of"
+ USE_FOR_MMU("b")
+ USE_FEATURE_UDHCPC_ARPING("a")
+ USE_FEATURE_UDHCP_PORT("P:")
+ , &str_c, &str_V, &str_h, &str_h, &str_F
+ , &client_config.interface, &client_config.pidfile, &str_r /* i,p */
+ , &client_config.script /* s */
+ , &discover_timeout, &discover_retries, &tryagain_timeout /* T,t,A */
+ , &list_O
+ USE_FEATURE_UDHCP_PORT(, &str_P)
+ );
+ if (opt & OPT_c)
+ client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0);
+ if (opt & OPT_V)
+ client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0);
+ if (opt & (OPT_h|OPT_H))
+ client_config.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0);
+ if (opt & OPT_F) {
+ client_config.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3);
+ /* Flags: 0000NEOS
+ S: 1 => Client requests Server to update A RR in DNS as well as PTR
+ O: 1 => Server indicates to client that DNS has been updated regardless
+ E: 1 => Name data is DNS format, i.e. <4>host<6>domain<3>com<0> not "host.domain.com"
+ N: 1 => Client requests Server to not update DNS
+ */
+ client_config.fqdn[OPT_DATA + 0] = 0x1;
+ /* client_config.fqdn[OPT_DATA + 1] = 0; - redundant */
+ /* client_config.fqdn[OPT_DATA + 2] = 0; - redundant */
+ }
+ if (opt & OPT_r)
+ requested_ip = inet_addr(str_r);
+ if (opt & OPT_v) {
+ puts("version "BB_VER);
+ return 0;
+ }
+#if ENABLE_FEATURE_UDHCP_PORT
+ if (opt & OPT_P) {
+ CLIENT_PORT = xatou16(str_P);
+ SERVER_PORT = CLIENT_PORT - 1;
+ }
+#endif
+ if (opt & OPT_o)
+ client_config.no_default_options = 1;
+ while (list_O) {
+ char *optstr = llist_pop(&list_O);
+ int n = index_in_strings(dhcp_option_strings, optstr);
+ if (n < 0)
+ bb_error_msg_and_die("unknown option '%s'", optstr);
+ n = dhcp_options[n].code;
+ client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+ }
+
+ if (udhcp_read_interface(client_config.interface, &client_config.ifindex,
+ NULL, client_config.arp))
+ return 1;
+#if !BB_MMU
+ /* on NOMMU reexec (i.e., background) early */
+ if (!(opt & OPT_f)) {
+ bb_daemonize_or_rexec(0 /* flags */, argv);
+ logmode = 0;
+ }
+#endif
+ if (opt & OPT_S) {
+ openlog(applet_name, LOG_PID, LOG_LOCAL0);
+ logmode |= LOGMODE_SYSLOG;
+ }
+
+ /* Make sure fd 0,1,2 are open */
+ bb_sanitize_stdio();
+ /* Equivalent of doing a fflush after every \n */
+ setlinebuf(stdout);
+
+ /* Create pidfile */
+ write_pidfile(client_config.pidfile);
+
+ /* Goes to stdout (unless NOMMU) and possibly syslog */
+ bb_info_msg("%s (v"BB_VER") started", applet_name);
+
+ /* if not set, and not suppressed, setup the default client ID */
+ if (!client_config.clientid && !(opt & OPT_C)) {
+ client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7);
+ client_config.clientid[OPT_DATA] = 1;
+ memcpy(client_config.clientid + OPT_DATA+1, client_config.arp, 6);
+ }
+
+ if (!client_config.vendorclass)
+ client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, "udhcp "BB_VER, 0);
+
+ /* setup the signal pipe */
+ udhcp_sp_setup();
+
+ state = INIT_SELECTING;
+ udhcp_run_script(NULL, "deconfig");
+ change_listen_mode(LISTEN_RAW);
+ packet_num = 0;
+ timeout = 0;
+ already_waited_sec = 0;
+
+ /* Main event loop. select() waits on signal pipe and possibly
+ * on sockfd.
+ * "continue" statements in code below jump to the top of the loop.
+ */
+ for (;;) {
+ unsigned timestamp_before_wait;
+
+ if (listen_mode != LISTEN_NONE && sockfd < 0) {
+ if (listen_mode == LISTEN_KERNEL)
+ sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface);
+ else
+ sockfd = udhcp_raw_socket(client_config.ifindex);
+ }
+ max_fd = udhcp_sp_fd_set(&rfds, sockfd);
+
+ tv.tv_sec = timeout - already_waited_sec;
+ tv.tv_usec = 0;
+ retval = 0; /* If we already timed out, fall through, else... */
+ if (tv.tv_sec > 0) {
+ timestamp_before_wait = (unsigned)monotonic_sec();
+ DEBUG("Waiting on select...");
+ retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);
+ if (retval < 0) {
+ /* EINTR? A signal was caught, don't panic */
+ if (errno == EINTR)
+ continue;
+ /* Else: an error occured, panic! */
+ bb_perror_msg_and_die("select");
+ }
+ }
+
+ /* If timeout dropped to zero, time to become active:
+ * resend discover/renew/whatever
+ */
+ if (retval == 0) {
+ /* We will restart the wait in any case */
+ already_waited_sec = 0;
+
+ switch (state) {
+ case INIT_SELECTING:
+ if (packet_num < discover_retries) {
+ if (packet_num == 0)
+ xid = random_xid();
+
+ send_discover(xid, requested_ip); /* broadcast */
+
+ timeout = discover_timeout;
+ packet_num++;
+ continue;
+ }
+ leasefail:
+ udhcp_run_script(NULL, "leasefail");
+#if BB_MMU /* -b is not supported on NOMMU */
+ if (opt & OPT_b) { /* background if no lease */
+ bb_info_msg("No lease, forking to background");
+ client_background();
+ /* do not background again! */
+ opt = ((opt & ~OPT_b) | OPT_f);
+ } else
+#endif
+ if (opt & OPT_n) { /* abort if no lease */
+ bb_info_msg("No lease, failing");
+ retval = 1;
+ goto ret;
+ }
+ /* wait before trying again */
+ timeout = tryagain_timeout;
+ packet_num = 0;
+ continue;
+ case RENEW_REQUESTED:
+ case REQUESTING:
+ if (packet_num < discover_retries) {
+ /* send request packet */
+ if (state == RENEW_REQUESTED) /* unicast */
+ send_renew(xid, server_addr, requested_ip);
+ else /* broadcast */
+ send_select(xid, server_addr, requested_ip);
+
+ timeout = discover_timeout;
+ packet_num++;
+ continue;
+ }
+ /* timed out, go back to init state */
+ if (state == RENEW_REQUESTED)
+ udhcp_run_script(NULL, "deconfig");
+ change_listen_mode(LISTEN_RAW);
+ /* "discover...select...discover..." loops
+ * were seen in the wild. Treat them similarly
+ * to "no response to discover" case */
+ if (state == REQUESTING) {
+ state = INIT_SELECTING;
+ goto leasefail;
+ }
+ state = INIT_SELECTING;
+ timeout = 0;
+ packet_num = 0;
+ continue;
+ case BOUND:
+ /* Half of the lease passed, time to enter renewing state */
+ change_listen_mode(LISTEN_KERNEL);
+ DEBUG("Entering renew state");
+ state = RENEWING;
+ /* fall right through */
+ case RENEWING:
+ if (timeout > 60) {
+ /* send a request packet */
+ send_renew(xid, server_addr, requested_ip); /* unicast */
+ timeout >>= 1;
+ continue;
+ }
+ /* Timed out, enter rebinding state */
+ DEBUG("Entering rebinding state");
+ state = REBINDING;
+ /* fall right through */
+ case REBINDING:
+ /* Lease is *really* about to run out,
+ * try to find DHCP server using broadcast */
+ if (timeout > 0) {
+ /* send a request packet */
+ send_renew(xid, 0 /* INADDR_ANY*/, requested_ip); /* broadcast */
+ timeout >>= 1;
+ continue;
+ }
+ /* Timed out, enter init state */
+ bb_info_msg("Lease lost, entering init state");
+ udhcp_run_script(NULL, "deconfig");
+ change_listen_mode(LISTEN_RAW);
+ state = INIT_SELECTING;
+ /*timeout = 0; - already is */
+ packet_num = 0;
+ continue;
+ /* case RELEASED: */
+ }
+ /* yah, I know, *you* say it would never happen */
+ timeout = INT_MAX;
+ continue; /* back to main loop */
+ }
+
+ /* select() didn't timeout, something did happen. */
+ /* Is it a packet? */
+ if (listen_mode != LISTEN_NONE && FD_ISSET(sockfd, &rfds)) {
+ int len;
+ /* A packet is ready, read it */
+
+ if (listen_mode == LISTEN_KERNEL)
+ len = udhcp_recv_kernel_packet(&packet, sockfd);
+ else
+ len = udhcp_recv_raw_packet(&packet, sockfd);
+ if (len == -1) { /* error is severe, reopen socket */
+ DEBUG("error on read, %s, reopening socket", strerror(errno));
+ sleep(discover_timeout); /* 3 seconds by default */
+ change_listen_mode(listen_mode); /* just close and reopen */
+ }
+ /* If this packet will turn out to be unrelated/bogus,
+ * we will go back and wait for next one.
+ * Be sure timeout is properly decreased. */
+ already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait;
+ if (len < 0)
+ continue;
+
+ if (packet.xid != xid) {
+ DEBUG("Ignoring xid %x (our xid is %x)",
+ (unsigned)packet.xid, (unsigned)xid);
+ continue;
+ }
+
+ /* Ignore packets that aren't for us */
+ if (memcmp(packet.chaddr, client_config.arp, 6)) {
+ DEBUG("Packet does not have our chaddr - ignoring");
+ continue;
+ }
+
+ message = get_option(&packet, DHCP_MESSAGE_TYPE);
+ if (message == NULL) {
+ bb_error_msg("cannot get message type from packet - ignoring");
+ continue;
+ }
+
+ switch (state) {
+ case INIT_SELECTING:
+ /* Must be a DHCPOFFER to one of our xid's */
+ if (*message == DHCPOFFER) {
+ /* TODO: why we don't just fetch server's IP from IP header? */
+ temp = get_option(&packet, DHCP_SERVER_ID);
+ if (!temp) {
+ bb_error_msg("no server ID in message");
+ continue;
+ /* still selecting - this server looks bad */
+ }
+ /* it IS unaligned sometimes, don't "optimize" */
+ server_addr = get_unaligned_u32p((uint32_t*)temp);
+ xid = packet.xid;
+ requested_ip = packet.yiaddr;
+
+ /* enter requesting state */
+ state = REQUESTING;
+ timeout = 0;
+ packet_num = 0;
+ already_waited_sec = 0;
+ }
+ continue;
+ case RENEW_REQUESTED:
+ case REQUESTING:
+ case RENEWING:
+ case REBINDING:
+ if (*message == DHCPACK) {
+ temp = get_option(&packet, DHCP_LEASE_TIME);
+ if (!temp) {
+ bb_error_msg("no lease time with ACK, using 1 hour lease");
+ lease_seconds = 60 * 60;
+ } else {
+ /* it IS unaligned sometimes, don't "optimize" */
+ lease_seconds = get_unaligned_u32p((uint32_t*)temp);
+ lease_seconds = ntohl(lease_seconds);
+ lease_seconds &= 0x0fffffff; /* paranoia: must not be prone to overflows */
+ if (lease_seconds < 10) /* and not too small */
+ lease_seconds = 10;
+ }
+#if ENABLE_FEATURE_UDHCPC_ARPING
+ if (opt & OPT_a) {
+/* RFC 2131 3.1 paragraph 5:
+ * "The client receives the DHCPACK message with configuration
+ * parameters. The client SHOULD perform a final check on the
+ * parameters (e.g., ARP for allocated network address), and notes
+ * the duration of the lease specified in the DHCPACK message. At this
+ * point, the client is configured. If the client detects that the
+ * address is already in use (e.g., through the use of ARP),
+ * the client MUST send a DHCPDECLINE message to the server and restarts
+ * the configuration process..." */
+ if (!arpping(packet.yiaddr,
+ (uint32_t) 0,
+ client_config.arp,
+ client_config.interface)
+ ) {
+ bb_info_msg("offered address is in use "
+ "(got ARP reply), declining");
+ send_decline(xid, server_addr, packet.yiaddr);
+
+ if (state != REQUESTING)
+ udhcp_run_script(NULL, "deconfig");
+ change_listen_mode(LISTEN_RAW);
+ state = INIT_SELECTING;
+ requested_ip = 0;
+ timeout = tryagain_timeout;
+ packet_num = 0;
+ already_waited_sec = 0;
+ continue; /* back to main loop */
+ }
+ }
+#endif
+ /* enter bound state */
+ timeout = lease_seconds / 2;
+ {
+ struct in_addr temp_addr;
+ temp_addr.s_addr = packet.yiaddr;
+ bb_info_msg("Lease of %s obtained, lease time %u",
+ inet_ntoa(temp_addr), (unsigned)lease_seconds);
+ }
+ requested_ip = packet.yiaddr;
+ udhcp_run_script(&packet,
+ ((state == RENEWING || state == REBINDING) ? "renew" : "bound"));
+
+ state = BOUND;
+ change_listen_mode(LISTEN_NONE);
+ if (opt & OPT_q) { /* quit after lease */
+ if (opt & OPT_R) /* release on quit */
+ perform_release(requested_ip, server_addr);
+ goto ret0;
+ }
+#if BB_MMU /* NOMMU case backgrounded earlier */
+ if (!(opt & OPT_f)) {
+ client_background();
+ /* do not background again! */
+ opt = ((opt & ~OPT_b) | OPT_f);
+ }
+#endif
+ already_waited_sec = 0;
+ continue; /* back to main loop */
+ }
+ if (*message == DHCPNAK) {
+ /* return to init state */
+ bb_info_msg("Received DHCP NAK");
+ udhcp_run_script(&packet, "nak");
+ if (state != REQUESTING)
+ udhcp_run_script(NULL, "deconfig");
+ change_listen_mode(LISTEN_RAW);
+ sleep(3); /* avoid excessive network traffic */
+ state = INIT_SELECTING;
+ requested_ip = 0;
+ timeout = 0;
+ packet_num = 0;
+ already_waited_sec = 0;
+ }
+ continue;
+ /* case BOUND, RELEASED: - ignore all packets */
+ }
+ continue; /* back to main loop */
+ }
+
+ /* select() didn't timeout, something did happen.
+ * But it wasn't a packet. It's a signal pipe then. */
+ {
+ int signo = udhcp_sp_read(&rfds);
+ switch (signo) {
+ case SIGUSR1:
+ perform_renew();
+ /* start things over */
+ packet_num = 0;
+ /* Kill any timeouts because the user wants this to hurry along */
+ timeout = 0;
+ break;
+ case SIGUSR2:
+ perform_release(requested_ip, server_addr);
+ timeout = INT_MAX;
+ break;
+ case SIGTERM:
+ bb_info_msg("Received SIGTERM");
+ if (opt & OPT_R) /* release on quit */
+ perform_release(requested_ip, server_addr);
+ goto ret0;
+ }
+ }
+ } /* for (;;) - main loop ends */
+
+ ret0:
+ retval = 0;
+ ret:
+ /*if (client_config.pidfile) - remove_pidfile has its own check */
+ remove_pidfile(client_config.pidfile);
+ return retval;
+}
diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h
new file mode 100644
index 0000000..a934849
--- /dev/null
+++ b/networking/udhcp/dhcpc.h
@@ -0,0 +1,56 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpc.h */
+
+#ifndef _DHCPC_H
+#define _DHCPC_H
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+struct client_config_t {
+ uint8_t arp[6]; /* Our arp address */
+ /* TODO: combine flag fields into single "unsigned opt" */
+ /* (can be set directly to the result of getopt32) */
+ char no_default_options; /* Do not include default optins in request */
+ USE_FEATURE_UDHCP_PORT(uint16_t port;)
+ int ifindex; /* Index number of the interface to use */
+ uint8_t opt_mask[256 / 8]; /* Bitmask of options to send (-O option) */
+ const char *interface; /* The name of the interface to use */
+ char *pidfile; /* Optionally store the process ID */
+ const char *script; /* User script to run at dhcp events */
+ uint8_t *clientid; /* Optional client id to use */
+ uint8_t *vendorclass; /* Optional vendor class-id to use */
+ uint8_t *hostname; /* Optional hostname to use */
+ uint8_t *fqdn; /* Optional fully qualified domain name to use */
+};
+
+/* server_config sits in 1st half of bb_common_bufsiz1 */
+#define client_config (*(struct client_config_t*)(&bb_common_bufsiz1[COMMON_BUFSIZE / 2]))
+
+#if ENABLE_FEATURE_UDHCP_PORT
+#define CLIENT_PORT (client_config.port)
+#else
+#define CLIENT_PORT 68
+#endif
+
+
+/*** clientpacket.h ***/
+
+uint32_t random_xid(void) FAST_FUNC;
+int send_discover(uint32_t xid, uint32_t requested) FAST_FUNC;
+int send_select(uint32_t xid, uint32_t server, uint32_t requested) FAST_FUNC;
+#if ENABLE_FEATURE_UDHCPC_ARPING
+int send_decline(uint32_t xid, uint32_t server, uint32_t requested) FAST_FUNC;
+#endif
+int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) FAST_FUNC;
+int send_renew(uint32_t xid, uint32_t server, uint32_t ciaddr) FAST_FUNC;
+int send_release(uint32_t server, uint32_t ciaddr) FAST_FUNC;
+
+int udhcp_recv_raw_packet(struct dhcpMessage *payload, int fd) FAST_FUNC;
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif
diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c
new file mode 100644
index 0000000..b512c45
--- /dev/null
+++ b/networking/udhcp/dhcpd.c
@@ -0,0 +1,272 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpd.c
+ *
+ * udhcp Server
+ * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au>
+ * Chris Trew <ctrew@moreton.com.au>
+ *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include <syslog.h>
+#include "common.h"
+#include "dhcpc.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+/* globals */
+struct dhcpOfferedAddr *leases;
+/* struct server_config_t server_config is in bb_common_bufsiz1 */
+
+
+int udhcpd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int udhcpd_main(int argc UNUSED_PARAM, char **argv)
+{
+ fd_set rfds;
+ struct timeval tv;
+ int server_socket = -1, bytes, retval, max_sock;
+ struct dhcpMessage packet;
+ uint8_t *state, *server_id, *requested;
+ uint32_t server_id_align, requested_align, static_lease_ip;
+ unsigned timeout_end;
+ unsigned num_ips;
+ unsigned opt;
+ struct option_set *option;
+ struct dhcpOfferedAddr *lease, static_lease;
+ USE_FEATURE_UDHCP_PORT(char *str_P;)
+
+#if ENABLE_FEATURE_UDHCP_PORT
+ SERVER_PORT = 67;
+ CLIENT_PORT = 68;
+#endif
+
+ opt = getopt32(argv, "fS" USE_FEATURE_UDHCP_PORT("P:", &str_P));
+ argv += optind;
+
+ if (!(opt & 1)) { /* no -f */
+ bb_daemonize_or_rexec(0, argv);
+ logmode &= ~LOGMODE_STDIO;
+ }
+
+ if (opt & 2) { /* -S */
+ openlog(applet_name, LOG_PID, LOG_LOCAL0);
+ logmode |= LOGMODE_SYSLOG;
+ }
+#if ENABLE_FEATURE_UDHCP_PORT
+ if (opt & 4) { /* -P */
+ SERVER_PORT = xatou16(str_P);
+ CLIENT_PORT = SERVER_PORT + 1;
+ }
+#endif
+ /* Would rather not do read_config before daemonization -
+ * otherwise NOMMU machines will parse config twice */
+ read_config(argv[0] ? argv[0] : DHCPD_CONF_FILE);
+
+ /* Make sure fd 0,1,2 are open */
+ bb_sanitize_stdio();
+ /* Equivalent of doing a fflush after every \n */
+ setlinebuf(stdout);
+
+ /* Create pidfile */
+ write_pidfile(server_config.pidfile);
+ /* if (!..) bb_perror_msg("cannot create pidfile %s", pidfile); */
+
+ bb_info_msg("%s (v"BB_VER") started", applet_name);
+
+ option = find_option(server_config.options, DHCP_LEASE_TIME);
+ server_config.lease = LEASE_TIME;
+ if (option) {
+ memcpy(&server_config.lease, option->data + 2, 4);
+ server_config.lease = ntohl(server_config.lease);
+ }
+
+ /* Sanity check */
+ num_ips = server_config.end_ip - server_config.start_ip + 1;
+ if (server_config.max_leases > num_ips) {
+ bb_error_msg("max_leases=%u is too big, setting to %u",
+ (unsigned)server_config.max_leases, num_ips);
+ server_config.max_leases = num_ips;
+ }
+
+ leases = xzalloc(server_config.max_leases * sizeof(*leases));
+ read_leases(server_config.lease_file);
+
+ if (udhcp_read_interface(server_config.interface, &server_config.ifindex,
+ &server_config.server, server_config.arp)) {
+ retval = 1;
+ goto ret;
+ }
+
+ /* Setup the signal pipe */
+ udhcp_sp_setup();
+
+ timeout_end = monotonic_sec() + server_config.auto_time;
+ while (1) { /* loop until universe collapses */
+
+ if (server_socket < 0) {
+ server_socket = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT,
+ server_config.interface);
+ }
+
+ max_sock = udhcp_sp_fd_set(&rfds, server_socket);
+ if (server_config.auto_time) {
+ tv.tv_sec = timeout_end - monotonic_sec();
+ tv.tv_usec = 0;
+ }
+ retval = 0;
+ if (!server_config.auto_time || tv.tv_sec > 0) {
+ retval = select(max_sock + 1, &rfds, NULL, NULL,
+ server_config.auto_time ? &tv : NULL);
+ }
+ if (retval == 0) {
+ write_leases();
+ timeout_end = monotonic_sec() + server_config.auto_time;
+ continue;
+ }
+ if (retval < 0 && errno != EINTR) {
+ DEBUG("error on select");
+ continue;
+ }
+
+ switch (udhcp_sp_read(&rfds)) {
+ case SIGUSR1:
+ bb_info_msg("Received a SIGUSR1");
+ write_leases();
+ /* why not just reset the timeout, eh */
+ timeout_end = monotonic_sec() + server_config.auto_time;
+ continue;
+ case SIGTERM:
+ bb_info_msg("Received a SIGTERM");
+ goto ret0;
+ case 0: break; /* no signal */
+ default: continue; /* signal or error (probably EINTR) */
+ }
+
+ bytes = udhcp_recv_kernel_packet(&packet, server_socket); /* this waits for a packet - idle */
+ if (bytes < 0) {
+ if (bytes == -1 && errno != EINTR) {
+ DEBUG("error on read, %s, reopening socket", strerror(errno));
+ close(server_socket);
+ server_socket = -1;
+ }
+ continue;
+ }
+
+ state = get_option(&packet, DHCP_MESSAGE_TYPE);
+ if (state == NULL) {
+ bb_error_msg("cannot get option from packet, ignoring");
+ continue;
+ }
+
+ /* Look for a static lease */
+ static_lease_ip = getIpByMac(server_config.static_leases, &packet.chaddr);
+
+ if (static_lease_ip) {
+ bb_info_msg("Found static lease: %x", static_lease_ip);
+
+ memcpy(&static_lease.chaddr, &packet.chaddr, 16);
+ static_lease.yiaddr = static_lease_ip;
+ static_lease.expires = 0;
+
+ lease = &static_lease;
+ } else {
+ lease = find_lease_by_chaddr(packet.chaddr);
+ }
+
+ switch (state[0]) {
+ case DHCPDISCOVER:
+ DEBUG("Received DISCOVER");
+
+ if (send_offer(&packet) < 0) {
+ bb_error_msg("send OFFER failed");
+ }
+ break;
+ case DHCPREQUEST:
+ DEBUG("received REQUEST");
+
+ requested = get_option(&packet, DHCP_REQUESTED_IP);
+ server_id = get_option(&packet, DHCP_SERVER_ID);
+
+ if (requested) memcpy(&requested_align, requested, 4);
+ if (server_id) memcpy(&server_id_align, server_id, 4);
+
+ if (lease) {
+ if (server_id) {
+ /* SELECTING State */
+ DEBUG("server_id = %08x", ntohl(server_id_align));
+ if (server_id_align == server_config.server && requested
+ && requested_align == lease->yiaddr
+ ) {
+ send_ACK(&packet, lease->yiaddr);
+ }
+ } else if (requested) {
+ /* INIT-REBOOT State */
+ if (lease->yiaddr == requested_align)
+ send_ACK(&packet, lease->yiaddr);
+ else
+ send_NAK(&packet);
+ } else if (lease->yiaddr == packet.ciaddr) {
+ /* RENEWING or REBINDING State */
+ send_ACK(&packet, lease->yiaddr);
+ } else { /* don't know what to do!!!! */
+ send_NAK(&packet);
+ }
+
+ /* what to do if we have no record of the client */
+ } else if (server_id) {
+ /* SELECTING State */
+
+ } else if (requested) {
+ /* INIT-REBOOT State */
+ lease = find_lease_by_yiaddr(requested_align);
+ if (lease) {
+ if (lease_expired(lease)) {
+ /* probably best if we drop this lease */
+ memset(lease->chaddr, 0, 16);
+ /* make some contention for this address */
+ } else
+ send_NAK(&packet);
+ } else {
+ uint32_t r = ntohl(requested_align);
+ if (r < server_config.start_ip
+ || r > server_config.end_ip
+ ) {
+ send_NAK(&packet);
+ }
+ /* else remain silent */
+ }
+
+ } else {
+ /* RENEWING or REBINDING State */
+ }
+ break;
+ case DHCPDECLINE:
+ DEBUG("Received DECLINE");
+ if (lease) {
+ memset(lease->chaddr, 0, 16);
+ lease->expires = time(0) + server_config.decline_time;
+ }
+ break;
+ case DHCPRELEASE:
+ DEBUG("Received RELEASE");
+ if (lease)
+ lease->expires = time(0);
+ break;
+ case DHCPINFORM:
+ DEBUG("Received INFORM");
+ send_inform(&packet);
+ break;
+ default:
+ bb_info_msg("Unsupported DHCP message (%02x) - ignoring", state[0]);
+ }
+ }
+ ret0:
+ retval = 0;
+ ret:
+ /*if (server_config.pidfile) - server_config.pidfile is never NULL */
+ remove_pidfile(server_config.pidfile);
+ return retval;
+}
diff --git a/networking/udhcp/dhcpd.h b/networking/udhcp/dhcpd.h
new file mode 100644
index 0000000..2d97528
--- /dev/null
+++ b/networking/udhcp/dhcpd.h
@@ -0,0 +1,125 @@
+/* vi: set sw=4 ts=4: */
+/* dhcpd.h */
+
+#ifndef _DHCPD_H
+#define _DHCPD_H
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+/************************************/
+/* Defaults _you_ may want to tweak */
+/************************************/
+
+/* the period of time the client is allowed to use that address */
+#define LEASE_TIME (60*60*24*10) /* 10 days of seconds */
+#define LEASES_FILE CONFIG_DHCPD_LEASES_FILE
+
+/* where to find the DHCP server configuration file */
+#define DHCPD_CONF_FILE "/etc/udhcpd.conf"
+
+struct option_set {
+ uint8_t *data;
+ struct option_set *next;
+};
+
+struct static_lease {
+ struct static_lease *next;
+ uint8_t *mac;
+ uint32_t *ip;
+};
+
+struct server_config_t {
+ uint32_t server; /* Our IP, in network order */
+#if ENABLE_FEATURE_UDHCP_PORT
+ uint16_t port;
+#endif
+ /* start,end are in host order: we need to compare start <= ip <= end */
+ uint32_t start_ip; /* Start address of leases, in host order */
+ uint32_t end_ip; /* End of leases, in host order */
+ struct option_set *options; /* List of DHCP options loaded from the config file */
+ char *interface; /* The name of the interface to use */
+ int ifindex; /* Index number of the interface to use */
+ uint8_t arp[6]; /* Our arp address */
+ char remaining; /* should the lease file be interpreted as lease time remaining, or
+ * as the time the lease expires */
+ uint32_t lease; /* lease time in seconds (host order) */
+ uint32_t max_leases; /* maximum number of leases (including reserved address) */
+ uint32_t auto_time; /* how long should udhcpd wait before writing a config file.
+ * if this is zero, it will only write one on SIGUSR1 */
+ uint32_t decline_time; /* how long an address is reserved if a client returns a
+ * decline message */
+ uint32_t conflict_time; /* how long an arp conflict offender is leased for */
+ uint32_t offer_time; /* how long an offered address is reserved */
+ uint32_t min_lease; /* minimum lease a client can request */
+ char *lease_file;
+ char *pidfile;
+ char *notify_file; /* What to run whenever leases are written */
+ uint32_t siaddr; /* next server bootp option */
+ char *sname; /* bootp server name */
+ char *boot_file; /* bootp boot file option */
+ struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */
+};
+
+#define server_config (*(struct server_config_t*)&bb_common_bufsiz1)
+/* client_config sits in 2nd half of bb_common_bufsiz1 */
+
+#if ENABLE_FEATURE_UDHCP_PORT
+#define SERVER_PORT (server_config.port)
+#else
+#define SERVER_PORT 67
+#endif
+
+extern struct dhcpOfferedAddr *leases;
+
+
+/*** leases.h ***/
+
+struct dhcpOfferedAddr {
+ uint8_t chaddr[16];
+ uint32_t yiaddr; /* network order */
+ uint32_t expires; /* host order */
+};
+
+struct dhcpOfferedAddr *add_lease(const uint8_t *chaddr, uint32_t yiaddr, unsigned long lease) FAST_FUNC;
+int lease_expired(struct dhcpOfferedAddr *lease) FAST_FUNC;
+struct dhcpOfferedAddr *find_lease_by_chaddr(const uint8_t *chaddr) FAST_FUNC;
+struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr) FAST_FUNC;
+uint32_t find_address(int check_expired) FAST_FUNC;
+
+
+/*** static_leases.h ***/
+
+/* Config file will pass static lease info to this function which will add it
+ * to a data structure that can be searched later */
+int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip) FAST_FUNC;
+/* Check to see if a mac has an associated static lease */
+uint32_t getIpByMac(struct static_lease *lease_struct, void *arg) FAST_FUNC;
+/* Check to see if an ip is reserved as a static ip */
+uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip) FAST_FUNC;
+/* Print out static leases just to check what's going on (debug code) */
+void printStaticLeases(struct static_lease **lease_struct) FAST_FUNC;
+
+
+/*** serverpacket.h ***/
+
+int send_offer(struct dhcpMessage *oldpacket) FAST_FUNC;
+int send_NAK(struct dhcpMessage *oldpacket) FAST_FUNC;
+int send_ACK(struct dhcpMessage *oldpacket, uint32_t yiaddr) FAST_FUNC;
+int send_inform(struct dhcpMessage *oldpacket) FAST_FUNC;
+
+
+/*** files.h ***/
+
+void read_config(const char *file) FAST_FUNC;
+void write_leases(void) FAST_FUNC;
+void read_leases(const char *file) FAST_FUNC;
+struct option_set *find_option(struct option_set *opt_list, uint8_t code) FAST_FUNC;
+
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif
diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c
new file mode 100644
index 0000000..f3b2855
--- /dev/null
+++ b/networking/udhcp/dhcprelay.c
@@ -0,0 +1,314 @@
+/* vi: set sw=4 ts=4: */
+/* Port to Busybox Copyright (C) 2006 Jesse Dutton <jessedutton@gmail.com>
+ *
+ * Licensed under GPL v2, see file LICENSE in this tarball for details.
+ *
+ * DHCP Relay for 'DHCPv4 Configuration of IPSec Tunnel Mode' support
+ * Copyright (C) 2002 Mario Strasser <mast@gmx.net>,
+ * Zuercher Hochschule Winterthur,
+ * Netbeat AG
+ * Upstream has GPL v2 or later
+ */
+
+#include "common.h"
+#include "options.h"
+
+/* constants */
+#define SERVER_PORT 67
+#define SELECT_TIMEOUT 5 /* select timeout in sec. */
+#define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */
+
+/* This list holds information about clients. The xid_* functions manipulate this list. */
+struct xid_item {
+ unsigned timestamp;
+ int client;
+ uint32_t xid;
+ struct sockaddr_in ip;
+ struct xid_item *next;
+};
+
+#define dhcprelay_xid_list (*(struct xid_item*)&bb_common_bufsiz1)
+
+static struct xid_item *xid_add(uint32_t xid, struct sockaddr_in *ip, int client)
+{
+ struct xid_item *item;
+
+ /* create new xid entry */
+ item = xmalloc(sizeof(struct xid_item));
+
+ /* add xid entry */
+ item->ip = *ip;
+ item->xid = xid;
+ item->client = client;
+ item->timestamp = monotonic_sec();
+ item->next = dhcprelay_xid_list.next;
+ dhcprelay_xid_list.next = item;
+
+ return item;
+}
+
+static void xid_expire(void)
+{
+ struct xid_item *item = dhcprelay_xid_list.next;
+ struct xid_item *last = &dhcprelay_xid_list;
+ unsigned current_time = monotonic_sec();
+
+ while (item != NULL) {
+ if ((current_time - item->timestamp) > MAX_LIFETIME) {
+ last->next = item->next;
+ free(item);
+ item = last->next;
+ } else {
+ last = item;
+ item = item->next;
+ }
+ }
+}
+
+static struct xid_item *xid_find(uint32_t xid)
+{
+ struct xid_item *item = dhcprelay_xid_list.next;
+ while (item != NULL) {
+ if (item->xid == xid) {
+ return item;
+ }
+ item = item->next;
+ }
+ return NULL;
+}
+
+static void xid_del(uint32_t xid)
+{
+ struct xid_item *item = dhcprelay_xid_list.next;
+ struct xid_item *last = &dhcprelay_xid_list;
+ while (item != NULL) {
+ if (item->xid == xid) {
+ last->next = item->next;
+ free(item);
+ item = last->next;
+ } else {
+ last = item;
+ item = item->next;
+ }
+ }
+}
+
+/**
+ * get_dhcp_packet_type - gets the message type of a dhcp packet
+ * p - pointer to the dhcp packet
+ * returns the message type on success, -1 otherwise
+ */
+static int get_dhcp_packet_type(struct dhcpMessage *p)
+{
+ uint8_t *op;
+
+ /* it must be either a BOOTREQUEST or a BOOTREPLY */
+ if (p->op != BOOTREQUEST && p->op != BOOTREPLY)
+ return -1;
+ /* get message type option */
+ op = get_option(p, DHCP_MESSAGE_TYPE);
+ if (op != NULL)
+ return op[0];
+ return -1;
+}
+
+/**
+ * get_client_devices - parses the devices list
+ * dev_list - comma separated list of devices
+ * returns array
+ */
+static char **get_client_devices(char *dev_list, int *client_number)
+{
+ char *s, **client_dev;
+ int i, cn;
+
+ /* copy list */
+ dev_list = xstrdup(dev_list);
+
+ /* get number of items, replace ',' with NULs */
+ s = dev_list;
+ cn = 1;
+ while (*s) {
+ if (*s == ',') {
+ *s = '\0';
+ cn++;
+ }
+ s++;
+ }
+ *client_number = cn;
+
+ /* create vector of pointers */
+ client_dev = xzalloc(cn * sizeof(*client_dev));
+ client_dev[0] = dev_list;
+ i = 1;
+ while (i != cn) {
+ client_dev[i] = client_dev[i - 1] + strlen(client_dev[i - 1]) + 1;
+ i++;
+ }
+ return client_dev;
+}
+
+
+/* Creates listen sockets (in fds) and returns numerically max fd. */
+static int init_sockets(char **client, int num_clients,
+ char *server, int *fds)
+{
+ int i, n;
+
+ /* talk to real server on bootps */
+ fds[0] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, server);
+ n = fds[0];
+
+ for (i = 1; i < num_clients; i++) {
+ /* listen for clients on bootps */
+ fds[i] = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT, client[i-1]);
+ if (fds[i] > n)
+ n = fds[i];
+ }
+ return n;
+}
+
+
+/**
+ * pass_on() - forwards dhcp packets from client to server
+ * p - packet to send
+ * client - number of the client
+ */
+static void pass_on(struct dhcpMessage *p, int packet_len, int client, int *fds,
+ struct sockaddr_in *client_addr, struct sockaddr_in *server_addr)
+{
+ int res, type;
+ struct xid_item *item;
+
+ /* check packet_type */
+ type = get_dhcp_packet_type(p);
+ if (type != DHCPDISCOVER && type != DHCPREQUEST
+ && type != DHCPDECLINE && type != DHCPRELEASE
+ && type != DHCPINFORM
+ ) {
+ return;
+ }
+
+ /* create new xid entry */
+ item = xid_add(p->xid, client_addr, client);
+
+ /* forward request to LAN (server) */
+ res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr,
+ sizeof(struct sockaddr_in));
+ if (res != packet_len) {
+ bb_perror_msg("pass_on");
+ return;
+ }
+}
+
+/**
+ * pass_back() - forwards dhcp packets from server to client
+ * p - packet to send
+ */
+static void pass_back(struct dhcpMessage *p, int packet_len, int *fds)
+{
+ int res, type;
+ struct xid_item *item;
+
+ /* check xid */
+ item = xid_find(p->xid);
+ if (!item) {
+ return;
+ }
+
+ /* check packet type */
+ type = get_dhcp_packet_type(p);
+ if (type != DHCPOFFER && type != DHCPACK && type != DHCPNAK) {
+ return;
+ }
+
+ if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY))
+ item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+ res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*)(&item->ip),
+ sizeof(item->ip));
+ if (res != packet_len) {
+ bb_perror_msg("pass_back");
+ return;
+ }
+
+ /* remove xid entry */
+ xid_del(p->xid);
+}
+
+static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients,
+ struct sockaddr_in *server_addr, uint32_t gw_ip) NORETURN;
+static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients,
+ struct sockaddr_in *server_addr, uint32_t gw_ip)
+{
+ struct dhcpMessage dhcp_msg;
+ fd_set rfds;
+ size_t packlen;
+ socklen_t addr_size;
+ struct sockaddr_in client_addr;
+ struct timeval tv;
+ int i;
+
+ while (1) {
+ FD_ZERO(&rfds);
+ for (i = 0; i < num_sockets; i++)
+ FD_SET(fds[i], &rfds);
+ tv.tv_sec = SELECT_TIMEOUT;
+ tv.tv_usec = 0;
+ if (select(max_socket + 1, &rfds, NULL, NULL, &tv) > 0) {
+ /* server */
+ if (FD_ISSET(fds[0], &rfds)) {
+ packlen = udhcp_recv_kernel_packet(&dhcp_msg, fds[0]);
+ if (packlen > 0) {
+ pass_back(&dhcp_msg, packlen, fds);
+ }
+ }
+ for (i = 1; i < num_sockets; i++) {
+ /* clients */
+ if (!FD_ISSET(fds[i], &rfds))
+ continue;
+ addr_size = sizeof(struct sockaddr_in);
+ packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0,
+ (struct sockaddr *)(&client_addr), &addr_size);
+ if (packlen <= 0)
+ continue;
+ if (udhcp_read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL))
+ dhcp_msg.giaddr = gw_ip;
+ pass_on(&dhcp_msg, packlen, i, fds, &client_addr, server_addr);
+ }
+ }
+ xid_expire();
+ }
+}
+
+int dhcprelay_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dhcprelay_main(int argc, char **argv)
+{
+ int num_sockets, max_socket;
+ int *fds;
+ uint32_t gw_ip;
+ char **clients;
+ struct sockaddr_in server_addr;
+
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_port = htons(SERVER_PORT);
+ if (argc == 4) {
+ if (!inet_aton(argv[3], &server_addr.sin_addr))
+ bb_perror_msg_and_die("didn't grok server");
+ } else if (argc == 3) {
+ server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
+ } else {
+ bb_show_usage();
+ }
+
+ clients = get_client_devices(argv[1], &num_sockets);
+ num_sockets++; /* for server socket at fds[0] */
+ fds = xmalloc(num_sockets * sizeof(fds[0]));
+ max_socket = init_sockets(clients, num_sockets, argv[2], fds);
+
+ if (udhcp_read_interface(argv[2], NULL, &gw_ip, NULL))
+ return 1;
+
+ /* doesn't return */
+ dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip);
+ /* return 0; - not reached */
+}
diff --git a/networking/udhcp/domain_codec.c b/networking/udhcp/domain_codec.c
new file mode 100644
index 0000000..6f051c4
--- /dev/null
+++ b/networking/udhcp/domain_codec.c
@@ -0,0 +1,205 @@
+/* vi: set sw=4 ts=4: */
+
+/* RFC1035 domain compression routines (C) 2007 Gabriel Somlo <somlo at cmu.edu>
+ *
+ * Loosely based on the isc-dhcpd implementation by dhankins@isc.org
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#if ENABLE_FEATURE_UDHCP_RFC3397
+
+#include "common.h"
+#include "options.h"
+
+#define NS_MAXDNAME 1025 /* max domain name length */
+#define NS_MAXCDNAME 255 /* max compressed domain name length */
+#define NS_MAXLABEL 63 /* max label length */
+#define NS_MAXDNSRCH 6 /* max domains in search path */
+#define NS_CMPRSFLGS 0xc0 /* name compression pointer flag */
+
+
+/* expand a RFC1035-compressed list of domain names "cstr", of length "clen";
+ * returns a newly allocated string containing the space-separated domains,
+ * prefixed with the contents of string pre, or NULL if an error occurs.
+ */
+char* FAST_FUNC dname_dec(const uint8_t *cstr, int clen, const char *pre)
+{
+ const uint8_t *c;
+ int crtpos, retpos, depth, plen = 0, len = 0;
+ char *dst = NULL;
+
+ if (!cstr)
+ return NULL;
+
+ if (pre)
+ plen = strlen(pre);
+
+ /* We make two passes over the cstr string. First, we compute
+ * how long the resulting string would be. Then we allocate a
+ * new buffer of the required length, and fill it in with the
+ * expanded content. The advantage of this approach is not
+ * having to deal with requiring callers to supply their own
+ * buffer, then having to check if it's sufficiently large, etc.
+ */
+
+ while (!dst) {
+
+ if (len > 0) { /* second pass? allocate dst buffer and copy pre */
+ dst = xmalloc(len + plen);
+ memcpy(dst, pre, plen);
+ }
+
+ crtpos = retpos = depth = len = 0;
+
+ while (crtpos < clen) {
+ c = cstr + crtpos;
+
+ if ((*c & NS_CMPRSFLGS) != 0) { /* pointer */
+ if (crtpos + 2 > clen) /* no offset to jump to? abort */
+ return NULL;
+ if (retpos == 0) /* toplevel? save return spot */
+ retpos = crtpos + 2;
+ depth++;
+ crtpos = ((*c & 0x3f) << 8) | (*(c + 1) & 0xff); /* jump */
+ } else if (*c) { /* label */
+ if (crtpos + *c + 1 > clen) /* label too long? abort */
+ return NULL;
+ if (dst)
+ memcpy(dst + plen + len, c + 1, *c);
+ len += *c + 1;
+ crtpos += *c + 1;
+ if (dst)
+ *(dst + plen + len - 1) = '.';
+ } else { /* null: end of current domain name */
+ if (retpos == 0) { /* toplevel? keep going */
+ crtpos++;
+ } else { /* return to toplevel saved spot */
+ crtpos = retpos;
+ retpos = depth = 0;
+ }
+ if (dst)
+ *(dst + plen + len - 1) = ' ';
+ }
+
+ if (depth > NS_MAXDNSRCH || /* too many jumps? abort, it's a loop */
+ len > NS_MAXDNAME * NS_MAXDNSRCH) /* result too long? abort */
+ return NULL;
+ }
+
+ if (!len) /* expanded string has 0 length? abort */
+ return NULL;
+
+ if (dst)
+ *(dst + plen + len - 1) = '\0';
+ }
+
+ return dst;
+}
+
+/* Convert a domain name (src) from human-readable "foo.blah.com" format into
+ * RFC1035 encoding "\003foo\004blah\003com\000". Return allocated string, or
+ * NULL if an error occurs.
+ */
+static uint8_t *convert_dname(const char *src)
+{
+ uint8_t c, *res, *lp, *rp;
+ int len;
+
+ res = xmalloc(strlen(src) + 2);
+ rp = lp = res;
+ rp++;
+
+ for (;;) {
+ c = (uint8_t)*src++;
+ if (c == '.' || c == '\0') { /* end of label */
+ len = rp - lp - 1;
+ /* label too long, too short, or two '.'s in a row? abort */
+ if (len > NS_MAXLABEL || len == 0 || (c == '.' && *src == '.')) {
+ free(res);
+ return NULL;
+ }
+ *lp = len;
+ lp = rp++;
+ if (c == '\0' || *src == '\0') /* end of dname */
+ break;
+ } else {
+ if (c >= 0x41 && c <= 0x5A) /* uppercase? convert to lower */
+ c += 0x20;
+ *rp++ = c;
+ }
+ }
+
+ *lp = 0;
+ if (rp - res > NS_MAXCDNAME) { /* dname too long? abort */
+ free(res);
+ return NULL;
+ }
+ return res;
+}
+
+/* returns the offset within cstr at which dname can be found, or -1
+ */
+static int find_offset(const uint8_t *cstr, int clen, const uint8_t *dname)
+{
+ const uint8_t *c, *d;
+ int off, inc;
+
+ /* find all labels in cstr */
+ off = 0;
+ while (off < clen) {
+ c = cstr + off;
+
+ if ((*c & NS_CMPRSFLGS) != 0) { /* pointer, skip */
+ off += 2;
+ } else if (*c) { /* label, try matching dname */
+ inc = *c + 1;
+ d = dname;
+ while (*c == *d && memcmp(c + 1, d + 1, *c) == 0) {
+ if (*c == 0) /* match, return offset */
+ return off;
+ d += *c + 1;
+ c += *c + 1;
+ if ((*c & NS_CMPRSFLGS) != 0) /* pointer, jump */
+ c = cstr + (((*c & 0x3f) << 8) | (*(c + 1) & 0xff));
+ }
+ off += inc;
+ } else { /* null, skip */
+ off++;
+ }
+ }
+
+ return -1;
+}
+
+/* computes string to be appended to cstr so that src would be added to
+ * the compression (best case, it's a 2-byte pointer to some offset within
+ * cstr; worst case, it's all of src, converted to rfc3011 format).
+ * The computed string is returned directly; its length is returned via retlen;
+ * NULL and 0, respectively, are returned if an error occurs.
+ */
+uint8_t* FAST_FUNC dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen)
+{
+ uint8_t *d, *dname;
+ int off;
+
+ dname = convert_dname(src);
+ if (dname == NULL) {
+ *retlen = 0;
+ return NULL;
+ }
+
+ for (d = dname; *d != 0; d += *d + 1) {
+ off = find_offset(cstr, clen, d);
+ if (off >= 0) { /* found a match, add pointer and terminate string */
+ *d++ = NS_CMPRSFLGS;
+ *d = off;
+ break;
+ }
+ }
+
+ *retlen = d - dname + 1;
+ return dname;
+}
+
+#endif /* ENABLE_FEATURE_UDHCP_RFC3397 */
diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c
new file mode 100644
index 0000000..3e19390
--- /dev/null
+++ b/networking/udhcp/dumpleases.c
@@ -0,0 +1,66 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+
+int dumpleases_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int dumpleases_main(int argc UNUSED_PARAM, char **argv)
+{
+ int fd;
+ int i;
+ unsigned opt;
+ time_t expires;
+ const char *file = LEASES_FILE;
+ struct dhcpOfferedAddr lease;
+ struct in_addr addr;
+
+ enum {
+ OPT_a = 0x1, // -a
+ OPT_r = 0x2, // -r
+ OPT_f = 0x4, // -f
+ };
+#if ENABLE_GETOPT_LONG
+ static const char dumpleases_longopts[] ALIGN1 =
+ "absolute\0" No_argument "a"
+ "remaining\0" No_argument "r"
+ "file\0" Required_argument "f"
+ ;
+
+ applet_long_options = dumpleases_longopts;
+#endif
+ opt_complementary = "=0:a--r:r--a";
+ opt = getopt32(argv, "arf:", &file);
+
+ fd = xopen(file, O_RDONLY);
+
+ printf("Mac Address IP-Address Expires %s\n", (opt & OPT_a) ? "at" : "in");
+ /* "00:00:00:00:00:00 255.255.255.255 Wed Jun 30 21:49:08 1993" */
+ while (full_read(fd, &lease, sizeof(lease)) == sizeof(lease)) {
+ printf(":%02x"+1, lease.chaddr[0]);
+ for (i = 1; i < 6; i++) {
+ printf(":%02x", lease.chaddr[i]);
+ }
+ addr.s_addr = lease.yiaddr;
+ printf(" %-15s ", inet_ntoa(addr));
+ expires = ntohl(lease.expires);
+ if (!(opt & OPT_a)) { /* no -a */
+ if (!expires)
+ puts("expired");
+ else {
+ unsigned d, h, m;
+ d = expires / (24*60*60); expires %= (24*60*60);
+ h = expires / (60*60); expires %= (60*60);
+ m = expires / 60; expires %= 60;
+ if (d) printf("%u days ", d);
+ printf("%02u:%02u:%02u\n", h, m, (unsigned)expires);
+ }
+ } else /* -a */
+ fputs(ctime(&expires), stdout);
+ }
+ /* close(fd); */
+
+ return 0;
+}
diff --git a/networking/udhcp/files.c b/networking/udhcp/files.c
new file mode 100644
index 0000000..0b97d76
--- /dev/null
+++ b/networking/udhcp/files.c
@@ -0,0 +1,413 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * files.c -- DHCP server file manipulation *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ */
+
+#include <netinet/ether.h>
+
+#include "common.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+/* on these functions, make sure your datatype matches */
+static int read_ip(const char *line, void *arg)
+{
+ len_and_sockaddr *lsa;
+
+ lsa = host_and_af2sockaddr(line, 0, AF_INET);
+ if (!lsa)
+ return 0;
+ *(uint32_t*)arg = lsa->u.sin.sin_addr.s_addr;
+ free(lsa);
+ return 1;
+}
+
+static int read_mac(const char *line, void *arg)
+{
+ struct ether_addr *temp_ether_addr;
+
+ temp_ether_addr = ether_aton_r(line, (struct ether_addr *)arg);
+ if (temp_ether_addr == NULL)
+ return 0;
+ return 1;
+}
+
+
+static int read_str(const char *line, void *arg)
+{
+ char **dest = arg;
+
+ free(*dest);
+ *dest = xstrdup(line);
+ return 1;
+}
+
+
+static int read_u32(const char *line, void *arg)
+{
+ *(uint32_t*)arg = bb_strtou32(line, NULL, 10);
+ return errno == 0;
+}
+
+
+static int read_yn(const char *line, void *arg)
+{
+ char *dest = arg;
+
+ if (!strcasecmp("yes", line)) {
+ *dest = 1;
+ return 1;
+ }
+ if (!strcasecmp("no", line)) {
+ *dest = 0;
+ return 1;
+ }
+ return 0;
+}
+
+
+/* find option 'code' in opt_list */
+struct option_set* FAST_FUNC find_option(struct option_set *opt_list, uint8_t code)
+{
+ while (opt_list && opt_list->data[OPT_CODE] < code)
+ opt_list = opt_list->next;
+
+ if (opt_list && opt_list->data[OPT_CODE] == code)
+ return opt_list;
+ return NULL;
+}
+
+
+/* add an option to the opt_list */
+static void attach_option(struct option_set **opt_list,
+ const struct dhcp_option *option, char *buffer, int length)
+{
+ struct option_set *existing, *new, **curr;
+
+ existing = find_option(*opt_list, option->code);
+ if (!existing) {
+ DEBUG("Attaching option %02x to list", option->code);
+
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ if ((option->flags & TYPE_MASK) == OPTION_STR1035)
+ /* reuse buffer and length for RFC1035-formatted string */
+ buffer = (char *)dname_enc(NULL, 0, buffer, &length);
+#endif
+
+ /* make a new option */
+ new = xmalloc(sizeof(*new));
+ new->data = xmalloc(length + 2);
+ new->data[OPT_CODE] = option->code;
+ new->data[OPT_LEN] = length;
+ memcpy(new->data + 2, buffer, length);
+
+ curr = opt_list;
+ while (*curr && (*curr)->data[OPT_CODE] < option->code)
+ curr = &(*curr)->next;
+
+ new->next = *curr;
+ *curr = new;
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL)
+ free(buffer);
+#endif
+ return;
+ }
+
+ /* add it to an existing option */
+ DEBUG("Attaching option %02x to existing member of list", option->code);
+ if (option->flags & OPTION_LIST) {
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ if ((option->flags & TYPE_MASK) == OPTION_STR1035)
+ /* reuse buffer and length for RFC1035-formatted string */
+ buffer = (char *)dname_enc(existing->data + 2,
+ existing->data[OPT_LEN], buffer, &length);
+#endif
+ if (existing->data[OPT_LEN] + length <= 255) {
+ existing->data = xrealloc(existing->data,
+ existing->data[OPT_LEN] + length + 3);
+ if ((option->flags & TYPE_MASK) == OPTION_STRING) {
+ /* ' ' can bring us to 256 - bad */
+ if (existing->data[OPT_LEN] + length >= 255)
+ return;
+ /* add space separator between STRING options in a list */
+ existing->data[existing->data[OPT_LEN] + 2] = ' ';
+ existing->data[OPT_LEN]++;
+ }
+ memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length);
+ existing->data[OPT_LEN] += length;
+ } /* else, ignore the data, we could put this in a second option in the future */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ if ((option->flags & TYPE_MASK) == OPTION_STR1035 && buffer != NULL)
+ free(buffer);
+#endif
+ } /* else, ignore the new data */
+}
+
+
+/* read a dhcp option and add it to opt_list */
+static int read_opt(const char *const_line, void *arg)
+{
+ struct option_set **opt_list = arg;
+ char *opt, *val, *endptr;
+ char *line;
+ const struct dhcp_option *option;
+ int retval, length, idx;
+ char buffer[8] ALIGNED(4);
+ uint16_t *result_u16 = (uint16_t *) buffer;
+ uint32_t *result_u32 = (uint32_t *) buffer;
+
+ /* Cheat, the only const line we'll actually get is "" */
+ line = (char *) const_line;
+ opt = strtok(line, " \t=");
+ if (!opt)
+ return 0;
+
+ idx = index_in_strings(dhcp_option_strings, opt); /* NB: was strcasecmp! */
+ if (idx < 0)
+ return 0;
+ option = &dhcp_options[idx];
+
+ retval = 0;
+ do {
+ val = strtok(NULL, ", \t");
+ if (!val) break;
+ length = dhcp_option_lengths[option->flags & TYPE_MASK];
+ retval = 0;
+ opt = buffer; /* new meaning for variable opt */
+ switch (option->flags & TYPE_MASK) {
+ case OPTION_IP:
+ retval = read_ip(val, buffer);
+ break;
+ case OPTION_IP_PAIR:
+ retval = read_ip(val, buffer);
+ val = strtok(NULL, ", \t/-");
+ if (!val)
+ retval = 0;
+ if (retval)
+ retval = read_ip(val, buffer + 4);
+ break;
+ case OPTION_STRING:
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ case OPTION_STR1035:
+#endif
+ length = strlen(val);
+ if (length > 0) {
+ if (length > 254) length = 254;
+ opt = val;
+ retval = 1;
+ }
+ break;
+ case OPTION_BOOLEAN:
+ retval = read_yn(val, buffer);
+ break;
+ case OPTION_U8:
+ buffer[0] = strtoul(val, &endptr, 0);
+ retval = (endptr[0] == '\0');
+ break;
+ /* htonX are macros in older libc's, using temp var
+ * in code below for safety */
+ /* TODO: use bb_strtoX? */
+ case OPTION_U16: {
+ unsigned long tmp = strtoul(val, &endptr, 0);
+ *result_u16 = htons(tmp);
+ retval = (endptr[0] == '\0' /*&& tmp < 0x10000*/);
+ break;
+ }
+ case OPTION_S16: {
+ long tmp = strtol(val, &endptr, 0);
+ *result_u16 = htons(tmp);
+ retval = (endptr[0] == '\0');
+ break;
+ }
+ case OPTION_U32: {
+ unsigned long tmp = strtoul(val, &endptr, 0);
+ *result_u32 = htonl(tmp);
+ retval = (endptr[0] == '\0');
+ break;
+ }
+ case OPTION_S32: {
+ long tmp = strtol(val, &endptr, 0);
+ *result_u32 = htonl(tmp);
+ retval = (endptr[0] == '\0');
+ break;
+ }
+ default:
+ break;
+ }
+ if (retval)
+ attach_option(opt_list, option, opt, length);
+ } while (retval && option->flags & OPTION_LIST);
+ return retval;
+}
+
+static int read_staticlease(const char *const_line, void *arg)
+{
+ char *line;
+ char *mac_string;
+ char *ip_string;
+ uint8_t *mac_bytes;
+ uint32_t *ip;
+
+ /* Allocate memory for addresses */
+ mac_bytes = xmalloc(sizeof(unsigned char) * 8);
+ ip = xmalloc(sizeof(uint32_t));
+
+ /* Read mac */
+ line = (char *) const_line;
+ mac_string = strtok(line, " \t");
+ read_mac(mac_string, mac_bytes);
+
+ /* Read ip */
+ ip_string = strtok(NULL, " \t");
+ read_ip(ip_string, ip);
+
+ addStaticLease(arg, mac_bytes, ip);
+
+ if (ENABLE_UDHCP_DEBUG) printStaticLeases(arg);
+
+ return 1;
+}
+
+
+struct config_keyword {
+ const char *keyword;
+ int (*handler)(const char *line, void *var);
+ void *var;
+ const char *def;
+};
+
+static const struct config_keyword keywords[] = {
+ /* keyword handler variable address default */
+ {"start", read_ip, &(server_config.start_ip), "192.168.0.20"},
+ {"end", read_ip, &(server_config.end_ip), "192.168.0.254"},
+ {"interface", read_str, &(server_config.interface), "eth0"},
+ /* Avoid "max_leases value not sane" warning by setting default
+ * to default_end_ip - default_start_ip + 1: */
+ {"max_leases", read_u32, &(server_config.max_leases), "235"},
+ {"remaining", read_yn, &(server_config.remaining), "yes"},
+ {"auto_time", read_u32, &(server_config.auto_time), "7200"},
+ {"decline_time", read_u32, &(server_config.decline_time), "3600"},
+ {"conflict_time",read_u32, &(server_config.conflict_time),"3600"},
+ {"offer_time", read_u32, &(server_config.offer_time), "60"},
+ {"min_lease", read_u32, &(server_config.min_lease), "60"},
+ {"lease_file", read_str, &(server_config.lease_file), LEASES_FILE},
+ {"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"},
+ {"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"},
+ /* keywords with no defaults must be last! */
+ {"option", read_opt, &(server_config.options), ""},
+ {"opt", read_opt, &(server_config.options), ""},
+ {"notify_file", read_str, &(server_config.notify_file), ""},
+ {"sname", read_str, &(server_config.sname), ""},
+ {"boot_file", read_str, &(server_config.boot_file), ""},
+ {"static_lease", read_staticlease, &(server_config.static_leases), ""},
+ /* ADDME: static lease */
+};
+enum { KWS_WITH_DEFAULTS = ARRAY_SIZE(keywords) - 6 };
+
+void FAST_FUNC read_config(const char *file)
+{
+ parser_t *parser;
+ const struct config_keyword *k;
+ unsigned i;
+ char *token[2];
+
+ for (i = 0; i < KWS_WITH_DEFAULTS; i++)
+ keywords[i].handler(keywords[i].def, keywords[i].var);
+
+ parser = config_open(file);
+ while (config_read(parser, token, 2, 2, "# \t", PARSE_NORMAL)) {
+ for (k = keywords, i = 0; i < ARRAY_SIZE(keywords); k++, i++) {
+ if (!strcasecmp(token[0], k->keyword)) {
+ if (!k->handler(token[1], k->var)) {
+ bb_error_msg("can't parse line %u in %s",
+ parser->lineno, file);
+ /* reset back to the default value */
+ k->handler(k->def, k->var);
+ }
+ break;
+ }
+ }
+ }
+ config_close(parser);
+
+ server_config.start_ip = ntohl(server_config.start_ip);
+ server_config.end_ip = ntohl(server_config.end_ip);
+}
+
+
+void FAST_FUNC write_leases(void)
+{
+ int fp;
+ unsigned i;
+ time_t curr = time(0);
+ unsigned long tmp_time;
+
+ fp = open_or_warn(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC);
+ if (fp < 0) {
+ return;
+ }
+
+ for (i = 0; i < server_config.max_leases; i++) {
+ if (leases[i].yiaddr != 0) {
+
+ /* screw with the time in the struct, for easier writing */
+ tmp_time = leases[i].expires;
+
+ if (server_config.remaining) {
+ if (lease_expired(&(leases[i])))
+ leases[i].expires = 0;
+ else leases[i].expires -= curr;
+ } /* else stick with the time we got */
+ leases[i].expires = htonl(leases[i].expires);
+ // FIXME: error check??
+ full_write(fp, &leases[i], sizeof(leases[i]));
+
+ /* then restore it when done */
+ leases[i].expires = tmp_time;
+ }
+ }
+ close(fp);
+
+ if (server_config.notify_file) {
+// TODO: vfork-based child creation
+ char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file);
+ system(cmd);
+ free(cmd);
+ }
+}
+
+
+void FAST_FUNC read_leases(const char *file)
+{
+ int fp;
+ unsigned i;
+ struct dhcpOfferedAddr lease;
+
+ fp = open_or_warn(file, O_RDONLY);
+ if (fp < 0) {
+ return;
+ }
+
+ i = 0;
+ while (i < server_config.max_leases
+ && full_read(fp, &lease, sizeof(lease)) == sizeof(lease)
+ ) {
+ /* ADDME: is it a static lease */
+ uint32_t y = ntohl(lease.yiaddr);
+ if (y >= server_config.start_ip && y <= server_config.end_ip) {
+ lease.expires = ntohl(lease.expires);
+ if (!server_config.remaining)
+ lease.expires -= time(NULL);
+ if (!(add_lease(lease.chaddr, lease.yiaddr, lease.expires))) {
+ bb_error_msg("too many leases while loading %s", file);
+ break;
+ }
+ i++;
+ }
+ }
+ DEBUG("Read %d leases", i);
+ close(fp);
+}
diff --git a/networking/udhcp/leases.c b/networking/udhcp/leases.c
new file mode 100644
index 0000000..ff52da9
--- /dev/null
+++ b/networking/udhcp/leases.c
@@ -0,0 +1,149 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * leases.c -- tools to manage DHCP leases
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+
+
+/* Find the oldest expired lease, NULL if there are no expired leases */
+static struct dhcpOfferedAddr *oldest_expired_lease(void)
+{
+ struct dhcpOfferedAddr *oldest = NULL;
+// TODO: use monotonic_sec()
+ unsigned long oldest_lease = time(0);
+ unsigned i;
+
+ for (i = 0; i < server_config.max_leases; i++)
+ if (oldest_lease > leases[i].expires) {
+ oldest_lease = leases[i].expires;
+ oldest = &(leases[i]);
+ }
+ return oldest;
+}
+
+
+/* clear every lease out that chaddr OR yiaddr matches and is nonzero */
+static void clear_lease(const uint8_t *chaddr, uint32_t yiaddr)
+{
+ unsigned i, j;
+
+ for (j = 0; j < 16 && !chaddr[j]; j++)
+ continue;
+
+ for (i = 0; i < server_config.max_leases; i++)
+ if ((j != 16 && memcmp(leases[i].chaddr, chaddr, 16) == 0)
+ || (yiaddr && leases[i].yiaddr == yiaddr)
+ ) {
+ memset(&(leases[i]), 0, sizeof(leases[i]));
+ }
+}
+
+
+/* add a lease into the table, clearing out any old ones */
+struct dhcpOfferedAddr* FAST_FUNC add_lease(const uint8_t *chaddr, uint32_t yiaddr, unsigned long lease)
+{
+ struct dhcpOfferedAddr *oldest;
+
+ /* clean out any old ones */
+ clear_lease(chaddr, yiaddr);
+
+ oldest = oldest_expired_lease();
+
+ if (oldest) {
+ memcpy(oldest->chaddr, chaddr, 16);
+ oldest->yiaddr = yiaddr;
+ oldest->expires = time(0) + lease;
+ }
+
+ return oldest;
+}
+
+
+/* true if a lease has expired */
+int FAST_FUNC lease_expired(struct dhcpOfferedAddr *lease)
+{
+ return (lease->expires < (unsigned long) time(0));
+}
+
+
+/* Find the first lease that matches chaddr, NULL if no match */
+struct dhcpOfferedAddr* FAST_FUNC find_lease_by_chaddr(const uint8_t *chaddr)
+{
+ unsigned i;
+
+ for (i = 0; i < server_config.max_leases; i++)
+ if (!memcmp(leases[i].chaddr, chaddr, 16))
+ return &(leases[i]);
+
+ return NULL;
+}
+
+
+/* Find the first lease that matches yiaddr, NULL is no match */
+struct dhcpOfferedAddr* FAST_FUNC find_lease_by_yiaddr(uint32_t yiaddr)
+{
+ unsigned i;
+
+ for (i = 0; i < server_config.max_leases; i++)
+ if (leases[i].yiaddr == yiaddr)
+ return &(leases[i]);
+
+ return NULL;
+}
+
+
+/* check is an IP is taken, if it is, add it to the lease table */
+static int nobody_responds_to_arp(uint32_t addr)
+{
+ /* 16 zero bytes */
+ static const uint8_t blank_chaddr[16] = { 0 };
+ /* = { 0 } helps gcc to put it in rodata, not bss */
+
+ struct in_addr temp;
+ int r;
+
+ r = arpping(addr, server_config.server, server_config.arp, server_config.interface);
+ if (r)
+ return r;
+
+ temp.s_addr = addr;
+ bb_info_msg("%s belongs to someone, reserving it for %u seconds",
+ inet_ntoa(temp), (unsigned)server_config.conflict_time);
+ add_lease(blank_chaddr, addr, server_config.conflict_time);
+ return 0;
+}
+
+
+/* find an assignable address, if check_expired is true, we check all the expired leases as well.
+ * Maybe this should try expired leases by age... */
+uint32_t FAST_FUNC find_address(int check_expired)
+{
+ uint32_t addr, ret;
+ struct dhcpOfferedAddr *lease = NULL;
+
+ addr = server_config.start_ip; /* addr is in host order here */
+ for (; addr <= server_config.end_ip; addr++) {
+ /* ie, 192.168.55.0 */
+ if (!(addr & 0xFF))
+ continue;
+ /* ie, 192.168.55.255 */
+ if ((addr & 0xFF) == 0xFF)
+ continue;
+ /* Only do if it isn't assigned as a static lease */
+ ret = htonl(addr);
+ if (!reservedIp(server_config.static_leases, ret)) {
+ /* lease is not taken */
+ lease = find_lease_by_yiaddr(ret);
+ /* no lease or it expired and we are checking for expired leases */
+ if ((!lease || (check_expired && lease_expired(lease)))
+ && nobody_responds_to_arp(ret) /* it isn't used on the network */
+ ) {
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/networking/udhcp/options.c b/networking/udhcp/options.c
new file mode 100644
index 0000000..6bf99e2
--- /dev/null
+++ b/networking/udhcp/options.c
@@ -0,0 +1,234 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * options.c -- DHCP server option packet tools
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+/* Supported options are easily added here */
+const struct dhcp_option dhcp_options[] = {
+ /* flags code */
+ { OPTION_IP | OPTION_REQ, 0x01 }, /* DHCP_SUBNET */
+ { OPTION_S32 , 0x02 }, /* DHCP_TIME_OFFSET */
+ { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03 }, /* DHCP_ROUTER */
+ { OPTION_IP | OPTION_LIST , 0x04 }, /* DHCP_TIME_SERVER */
+ { OPTION_IP | OPTION_LIST , 0x05 }, /* DHCP_NAME_SERVER */
+ { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06 }, /* DHCP_DNS_SERVER */
+ { OPTION_IP | OPTION_LIST , 0x07 }, /* DHCP_LOG_SERVER */
+ { OPTION_IP | OPTION_LIST , 0x08 }, /* DHCP_COOKIE_SERVER */
+ { OPTION_IP | OPTION_LIST , 0x09 }, /* DHCP_LPR_SERVER */
+ { OPTION_STRING | OPTION_REQ, 0x0c }, /* DHCP_HOST_NAME */
+ { OPTION_U16 , 0x0d }, /* DHCP_BOOT_SIZE */
+ { OPTION_STRING | OPTION_LIST | OPTION_REQ, 0x0f }, /* DHCP_DOMAIN_NAME */
+ { OPTION_IP , 0x10 }, /* DHCP_SWAP_SERVER */
+ { OPTION_STRING , 0x11 }, /* DHCP_ROOT_PATH */
+ { OPTION_U8 , 0x17 }, /* DHCP_IP_TTL */
+ { OPTION_U16 , 0x1a }, /* DHCP_MTU */
+ { OPTION_IP | OPTION_REQ, 0x1c }, /* DHCP_BROADCAST */
+ { OPTION_STRING , 0x28 }, /* nisdomain */
+ { OPTION_IP | OPTION_LIST , 0x29 }, /* nissrv */
+ { OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a }, /* DHCP_NTP_SERVER */
+ { OPTION_IP | OPTION_LIST , 0x2c }, /* DHCP_WINS_SERVER */
+ { OPTION_IP , 0x32 }, /* DHCP_REQUESTED_IP */
+ { OPTION_U32 , 0x33 }, /* DHCP_LEASE_TIME */
+ { OPTION_U8 , 0x35 }, /* dhcptype */
+ { OPTION_IP , 0x36 }, /* DHCP_SERVER_ID */
+ { OPTION_STRING , 0x38 }, /* DHCP_MESSAGE */
+ { OPTION_STRING , 0x3C }, /* DHCP_VENDOR */
+ { OPTION_STRING , 0x3D }, /* DHCP_CLIENT_ID */
+ { OPTION_STRING , 0x42 }, /* tftp */
+ { OPTION_STRING , 0x43 }, /* bootfile */
+ { OPTION_STRING , 0x4D }, /* userclass */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ { OPTION_STR1035 | OPTION_LIST , 0x77 }, /* search */
+#endif
+ /* MSIE's "Web Proxy Autodiscovery Protocol" support */
+ { OPTION_STRING , 0xfc }, /* wpad */
+
+ /* Options below have no match in dhcp_option_strings[],
+ * are not passed to dhcpc scripts, and cannot be specified
+ * with "option XXX YYY" syntax in dhcpd config file. */
+
+ { OPTION_U16 , 0x39 }, /* DHCP_MAX_SIZE */
+ { } /* zeroed terminating entry */
+};
+
+/* Used for converting options from incoming packets to env variables
+ * for udhcpc stript */
+/* Must match dhcp_options[] order */
+const char dhcp_option_strings[] ALIGN1 =
+ "subnet" "\0" /* DHCP_SUBNET */
+ "timezone" "\0" /* DHCP_TIME_OFFSET */
+ "router" "\0" /* DHCP_ROUTER */
+ "timesrv" "\0" /* DHCP_TIME_SERVER */
+ "namesrv" "\0" /* DHCP_NAME_SERVER */
+ "dns" "\0" /* DHCP_DNS_SERVER */
+ "logsrv" "\0" /* DHCP_LOG_SERVER */
+ "cookiesrv" "\0" /* DHCP_COOKIE_SERVER */
+ "lprsrv" "\0" /* DHCP_LPR_SERVER */
+ "hostname" "\0" /* DHCP_HOST_NAME */
+ "bootsize" "\0" /* DHCP_BOOT_SIZE */
+ "domain" "\0" /* DHCP_DOMAIN_NAME */
+ "swapsrv" "\0" /* DHCP_SWAP_SERVER */
+ "rootpath" "\0" /* DHCP_ROOT_PATH */
+ "ipttl" "\0" /* DHCP_IP_TTL */
+ "mtu" "\0" /* DHCP_MTU */
+ "broadcast" "\0" /* DHCP_BROADCAST */
+ "nisdomain" "\0" /* */
+ "nissrv" "\0" /* */
+ "ntpsrv" "\0" /* DHCP_NTP_SERVER */
+ "wins" "\0" /* DHCP_WINS_SERVER */
+ "requestip" "\0" /* DHCP_REQUESTED_IP */
+ "lease" "\0" /* DHCP_LEASE_TIME */
+ "dhcptype" "\0" /* */
+ "serverid" "\0" /* DHCP_SERVER_ID */
+ "message" "\0" /* DHCP_MESSAGE */
+ "vendorclass" "\0" /* DHCP_VENDOR */
+ "clientid" "\0" /* DHCP_CLIENT_ID */
+ "tftp" "\0"
+ "bootfile" "\0"
+ "userclass" "\0"
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ "search" "\0"
+#endif
+ /* MSIE's "Web Proxy Autodiscovery Protocol" support */
+ "wpad" "\0"
+ ;
+
+
+/* Lengths of the different option types */
+const uint8_t dhcp_option_lengths[] ALIGN1 = {
+ [OPTION_IP] = 4,
+ [OPTION_IP_PAIR] = 8,
+ [OPTION_BOOLEAN] = 1,
+ [OPTION_STRING] = 1,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ [OPTION_STR1035] = 1,
+#endif
+ [OPTION_U8] = 1,
+ [OPTION_U16] = 2,
+ [OPTION_S16] = 2,
+ [OPTION_U32] = 4,
+ [OPTION_S32] = 4
+};
+
+
+/* get an option with bounds checking (warning, not aligned). */
+uint8_t* FAST_FUNC get_option(struct dhcpMessage *packet, int code)
+{
+ int i, length;
+ uint8_t *optionptr;
+ int over = 0;
+ int curr = OPTION_FIELD;
+
+ optionptr = packet->options;
+ i = 0;
+ length = sizeof(packet->options);
+ while (1) {
+ if (i >= length) {
+ bb_error_msg("bogus packet, option fields too long");
+ return NULL;
+ }
+ if (optionptr[i + OPT_CODE] == code) {
+ if (i + 1 + optionptr[i + OPT_LEN] >= length) {
+ bb_error_msg("bogus packet, option fields too long");
+ return NULL;
+ }
+ return optionptr + i + 2;
+ }
+ switch (optionptr[i + OPT_CODE]) {
+ case DHCP_PADDING:
+ i++;
+ break;
+ case DHCP_OPTION_OVER:
+ if (i + 1 + optionptr[i + OPT_LEN] >= length) {
+ bb_error_msg("bogus packet, option fields too long");
+ return NULL;
+ }
+ over = optionptr[i + 3];
+ i += optionptr[OPT_LEN] + 2;
+ break;
+ case DHCP_END:
+ if (curr == OPTION_FIELD && (over & FILE_FIELD)) {
+ optionptr = packet->file;
+ i = 0;
+ length = sizeof(packet->file);
+ curr = FILE_FIELD;
+ } else if (curr == FILE_FIELD && (over & SNAME_FIELD)) {
+ optionptr = packet->sname;
+ i = 0;
+ length = sizeof(packet->sname);
+ curr = SNAME_FIELD;
+ } else
+ return NULL;
+ break;
+ default:
+ i += optionptr[OPT_LEN + i] + 2;
+ }
+ }
+ return NULL;
+}
+
+
+/* return the position of the 'end' option (no bounds checking) */
+int FAST_FUNC end_option(uint8_t *optionptr)
+{
+ int i = 0;
+
+ while (optionptr[i] != DHCP_END) {
+ if (optionptr[i] == DHCP_PADDING)
+ i++;
+ else
+ i += optionptr[i + OPT_LEN] + 2;
+ }
+ return i;
+}
+
+
+/* add an option string to the options (an option string contains an option code,
+ * length, then data) */
+int FAST_FUNC add_option_string(uint8_t *optionptr, uint8_t *string)
+{
+ int end = end_option(optionptr);
+
+ /* end position + string length + option code/length + end option */
+ if (end + string[OPT_LEN] + 2 + 1 >= DHCP_OPTIONS_BUFSIZE) {
+ bb_error_msg("option 0x%02x did not fit into the packet",
+ string[OPT_CODE]);
+ return 0;
+ }
+ DEBUG("adding option 0x%02x", string[OPT_CODE]);
+ memcpy(optionptr + end, string, string[OPT_LEN] + 2);
+ optionptr[end + string[OPT_LEN] + 2] = DHCP_END;
+ return string[OPT_LEN] + 2;
+}
+
+
+/* add a one to four byte option to a packet */
+int FAST_FUNC add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data)
+{
+ const struct dhcp_option *dh;
+
+ for (dh = dhcp_options; dh->code; dh++) {
+ if (dh->code == code) {
+ uint8_t option[6], len;
+
+ option[OPT_CODE] = code;
+ len = dhcp_option_lengths[dh->flags & TYPE_MASK];
+ option[OPT_LEN] = len;
+ if (BB_BIG_ENDIAN)
+ data <<= 8 * (4 - len);
+ /* This memcpy is for processors which can't
+ * handle a simple unaligned 32-bit assignment */
+ memcpy(&option[OPT_DATA], &data, 4);
+ return add_option_string(optionptr, option);
+ }
+ }
+
+ bb_error_msg("cannot add option 0x%02x", code);
+ return 0;
+}
diff --git a/networking/udhcp/options.h b/networking/udhcp/options.h
new file mode 100644
index 0000000..d18a353
--- /dev/null
+++ b/networking/udhcp/options.h
@@ -0,0 +1,121 @@
+/* vi: set sw=4 ts=4: */
+/* options.h */
+#ifndef _OPTIONS_H
+#define _OPTIONS_H
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility push(hidden)
+#endif
+
+#define TYPE_MASK 0x0F
+
+enum {
+ OPTION_IP = 1,
+ OPTION_IP_PAIR,
+ OPTION_STRING,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ OPTION_STR1035, /* RFC1035 compressed domain name list */
+#endif
+ OPTION_BOOLEAN,
+ OPTION_U8,
+ OPTION_U16,
+ OPTION_S16,
+ OPTION_U32,
+ OPTION_S32
+};
+
+#define OPTION_REQ 0x10 /* have the client request this option */
+#define OPTION_LIST 0x20 /* There can be a list of 1 or more of these */
+
+/*****************************************************************/
+/* Do not modify below here unless you know what you are doing!! */
+/*****************************************************************/
+
+/* DHCP protocol -- see RFC 2131 */
+#define DHCP_MAGIC 0x63825363
+
+
+/* DHCP option codes (partial list) */
+#define DHCP_PADDING 0x00
+#define DHCP_SUBNET 0x01
+#define DHCP_TIME_OFFSET 0x02
+#define DHCP_ROUTER 0x03
+#define DHCP_TIME_SERVER 0x04
+#define DHCP_NAME_SERVER 0x05
+#define DHCP_DNS_SERVER 0x06
+#define DHCP_LOG_SERVER 0x07
+#define DHCP_COOKIE_SERVER 0x08
+#define DHCP_LPR_SERVER 0x09
+#define DHCP_HOST_NAME 0x0c
+#define DHCP_BOOT_SIZE 0x0d
+#define DHCP_DOMAIN_NAME 0x0f
+#define DHCP_SWAP_SERVER 0x10
+#define DHCP_ROOT_PATH 0x11
+#define DHCP_IP_TTL 0x17
+#define DHCP_MTU 0x1a
+#define DHCP_BROADCAST 0x1c
+#define DHCP_NTP_SERVER 0x2a
+#define DHCP_WINS_SERVER 0x2c
+#define DHCP_REQUESTED_IP 0x32
+#define DHCP_LEASE_TIME 0x33
+#define DHCP_OPTION_OVER 0x34
+#define DHCP_MESSAGE_TYPE 0x35
+#define DHCP_SERVER_ID 0x36
+#define DHCP_PARAM_REQ 0x37
+#define DHCP_MESSAGE 0x38
+#define DHCP_MAX_SIZE 0x39
+#define DHCP_T1 0x3a
+#define DHCP_T2 0x3b
+#define DHCP_VENDOR 0x3c
+#define DHCP_CLIENT_ID 0x3d
+#define DHCP_FQDN 0x51
+#define DHCP_END 0xFF
+
+
+#define BOOTREQUEST 1
+#define BOOTREPLY 2
+
+#define ETH_10MB 1
+#define ETH_10MB_LEN 6
+
+#define DHCPDISCOVER 1 /* client -> server */
+#define DHCPOFFER 2 /* client <- server */
+#define DHCPREQUEST 3 /* client -> server */
+#define DHCPDECLINE 4 /* client -> server */
+#define DHCPACK 5 /* client <- server */
+#define DHCPNAK 6 /* client <- server */
+#define DHCPRELEASE 7 /* client -> server */
+#define DHCPINFORM 8 /* client -> server */
+
+#define OPTION_FIELD 0
+#define FILE_FIELD 1
+#define SNAME_FIELD 2
+
+/* miscellaneous defines */
+#define OPT_CODE 0
+#define OPT_LEN 1
+#define OPT_DATA 2
+
+struct dhcp_option {
+ uint8_t flags;
+ uint8_t code;
+};
+
+extern const struct dhcp_option dhcp_options[];
+extern const char dhcp_option_strings[];
+extern const uint8_t dhcp_option_lengths[];
+
+uint8_t *get_option(struct dhcpMessage *packet, int code) FAST_FUNC;
+int end_option(uint8_t *optionptr) FAST_FUNC;
+int add_option_string(uint8_t *optionptr, uint8_t *string) FAST_FUNC;
+int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data) FAST_FUNC;
+#if ENABLE_FEATURE_UDHCP_RFC3397
+char *dname_dec(const uint8_t *cstr, int clen, const char *pre) FAST_FUNC;
+uint8_t *dname_enc(const uint8_t *cstr, int clen, const char *src, int *retlen) FAST_FUNC;
+#endif
+
+#if __GNUC_PREREQ(4,1)
+# pragma GCC visibility pop
+#endif
+
+#endif
diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c
new file mode 100644
index 0000000..1a6f7e6
--- /dev/null
+++ b/networking/udhcp/packet.c
@@ -0,0 +1,238 @@
+/* vi: set sw=4 ts=4: */
+
+#include <netinet/in.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <asm/types.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+
+#include "common.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+void FAST_FUNC udhcp_init_header(struct dhcpMessage *packet, char type)
+{
+ memset(packet, 0, sizeof(struct dhcpMessage));
+ packet->op = BOOTREQUEST; /* if client to a server */
+ switch (type) {
+ case DHCPOFFER:
+ case DHCPACK:
+ case DHCPNAK:
+ packet->op = BOOTREPLY; /* if server to client */
+ }
+ packet->htype = ETH_10MB;
+ packet->hlen = ETH_10MB_LEN;
+ packet->cookie = htonl(DHCP_MAGIC);
+ packet->options[0] = DHCP_END;
+ add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type);
+}
+
+
+/* read a packet from socket fd, return -1 on read error, -2 on packet error */
+int FAST_FUNC udhcp_recv_kernel_packet(struct dhcpMessage *packet, int fd)
+{
+ int bytes;
+ unsigned char *vendor;
+
+ memset(packet, 0, sizeof(*packet));
+ bytes = safe_read(fd, packet, sizeof(*packet));
+ if (bytes < 0) {
+ DEBUG("cannot read on listening socket, ignoring");
+ return bytes; /* returns -1 */
+ }
+
+ if (packet->cookie != htonl(DHCP_MAGIC)) {
+ bb_error_msg("received bogus message, ignoring");
+ return -2;
+ }
+ DEBUG("Received a packet");
+
+ if (packet->op == BOOTREQUEST) {
+ vendor = get_option(packet, DHCP_VENDOR);
+ if (vendor) {
+#if 0
+ static const char broken_vendors[][8] = {
+ "MSFT 98",
+ ""
+ };
+ int i;
+ for (i = 0; broken_vendors[i][0]; i++) {
+ if (vendor[OPT_LEN - 2] == (uint8_t)strlen(broken_vendors[i])
+ && !strncmp((char*)vendor, broken_vendors[i], vendor[OPT_LEN - 2])
+ ) {
+ DEBUG("broken client (%s), forcing broadcast replies",
+ broken_vendors[i]);
+ packet->flags |= htons(BROADCAST_FLAG);
+ }
+ }
+#else
+ if (vendor[OPT_LEN - 2] == (uint8_t)(sizeof("MSFT 98")-1)
+ && memcmp(vendor, "MSFT 98", sizeof("MSFT 98")-1) == 0
+ ) {
+ DEBUG("broken client (%s), forcing broadcast replies", "MSFT 98");
+ packet->flags |= htons(BROADCAST_FLAG);
+ }
+#endif
+ }
+ }
+
+ return bytes;
+}
+
+
+uint16_t FAST_FUNC udhcp_checksum(void *addr, int count)
+{
+ /* Compute Internet Checksum for "count" bytes
+ * beginning at location "addr".
+ */
+ int32_t sum = 0;
+ uint16_t *source = (uint16_t *) addr;
+
+ while (count > 1) {
+ /* This is the inner loop */
+ sum += *source++;
+ count -= 2;
+ }
+
+ /* Add left-over byte, if any */
+ if (count > 0) {
+ /* Make sure that the left-over byte is added correctly both
+ * with little and big endian hosts */
+ uint16_t tmp = 0;
+ *(uint8_t*)&tmp = *(uint8_t*)source;
+ sum += tmp;
+ }
+ /* Fold 32-bit sum to 16 bits */
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ return ~sum;
+}
+
+
+/* Construct a ip/udp header for a packet, send packet */
+int FAST_FUNC udhcp_send_raw_packet(struct dhcpMessage *payload,
+ uint32_t source_ip, int source_port,
+ uint32_t dest_ip, int dest_port, const uint8_t *dest_arp,
+ int ifindex)
+{
+ struct sockaddr_ll dest;
+ struct udp_dhcp_packet packet;
+ int fd;
+ int result = -1;
+ const char *msg;
+
+ enum {
+ IP_UPD_DHCP_SIZE = sizeof(struct udp_dhcp_packet) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
+ UPD_DHCP_SIZE = IP_UPD_DHCP_SIZE - offsetof(struct udp_dhcp_packet, udp),
+ };
+
+ fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+ if (fd < 0) {
+ msg = "socket(%s)";
+ goto ret_msg;
+ }
+
+ memset(&dest, 0, sizeof(dest));
+ memset(&packet, 0, sizeof(packet));
+ packet.data = *payload; /* struct copy */
+
+ dest.sll_family = AF_PACKET;
+ dest.sll_protocol = htons(ETH_P_IP);
+ dest.sll_ifindex = ifindex;
+ dest.sll_halen = 6;
+ memcpy(dest.sll_addr, dest_arp, 6);
+ if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
+ msg = "bind(%s)";
+ goto ret_close;
+ }
+
+ packet.ip.protocol = IPPROTO_UDP;
+ packet.ip.saddr = source_ip;
+ packet.ip.daddr = dest_ip;
+ packet.udp.source = htons(source_port);
+ packet.udp.dest = htons(dest_port);
+ /* size, excluding IP header: */
+ packet.udp.len = htons(UPD_DHCP_SIZE);
+ /* for UDP checksumming, ip.len is set to UDP packet len */
+ packet.ip.tot_len = packet.udp.len;
+ packet.udp.check = udhcp_checksum(&packet, IP_UPD_DHCP_SIZE);
+ /* but for sending, it is set to IP packet len */
+ packet.ip.tot_len = htons(IP_UPD_DHCP_SIZE);
+ packet.ip.ihl = sizeof(packet.ip) >> 2;
+ packet.ip.version = IPVERSION;
+ packet.ip.ttl = IPDEFTTL;
+ packet.ip.check = udhcp_checksum(&packet.ip, sizeof(packet.ip));
+
+ /* Currently we send full-sized DHCP packets (zero padded).
+ * If you need to change this: last byte of the packet is
+ * packet.data.options[end_option(packet.data.options)]
+ */
+ result = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
+ (struct sockaddr *) &dest, sizeof(dest));
+ msg = "sendto";
+ ret_close:
+ close(fd);
+ if (result < 0) {
+ ret_msg:
+ bb_perror_msg(msg, "PACKET");
+ }
+ return result;
+}
+
+
+/* Let the kernel do all the work for packet generation */
+int FAST_FUNC udhcp_send_kernel_packet(struct dhcpMessage *payload,
+ uint32_t source_ip, int source_port,
+ uint32_t dest_ip, int dest_port)
+{
+ struct sockaddr_in client;
+ int fd;
+ int result = -1;
+ const char *msg;
+
+ enum {
+ DHCP_SIZE = sizeof(struct dhcpMessage) - CONFIG_UDHCPC_SLACK_FOR_BUGGY_SERVERS,
+ };
+
+ fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ msg = "socket(%s)";
+ goto ret_msg;
+ }
+ setsockopt_reuseaddr(fd);
+
+ memset(&client, 0, sizeof(client));
+ client.sin_family = AF_INET;
+ client.sin_port = htons(source_port);
+ client.sin_addr.s_addr = source_ip;
+ if (bind(fd, (struct sockaddr *)&client, sizeof(client)) == -1) {
+ msg = "bind(%s)";
+ goto ret_close;
+ }
+
+ memset(&client, 0, sizeof(client));
+ client.sin_family = AF_INET;
+ client.sin_port = htons(dest_port);
+ client.sin_addr.s_addr = dest_ip;
+ if (connect(fd, (struct sockaddr *)&client, sizeof(client)) == -1) {
+ msg = "connect";
+ goto ret_close;
+ }
+
+ /* Currently we send full-sized DHCP packets (see above) */
+ result = safe_write(fd, payload, DHCP_SIZE);
+ msg = "write";
+ ret_close:
+ close(fd);
+ if (result < 0) {
+ ret_msg:
+ bb_perror_msg(msg, "UDP");
+ }
+ return result;
+}
diff --git a/networking/udhcp/script.c b/networking/udhcp/script.c
new file mode 100644
index 0000000..8dff9b7
--- /dev/null
+++ b/networking/udhcp/script.c
@@ -0,0 +1,239 @@
+/* vi: set sw=4 ts=4: */
+/* script.c
+ *
+ * Functions to call the DHCP client notification scripts
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+#include "common.h"
+#include "dhcpc.h"
+#include "options.h"
+
+
+/* get a rough idea of how long an option will be (rounding up...) */
+static const uint8_t max_option_length[] = {
+ [OPTION_IP] = sizeof("255.255.255.255 "),
+ [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2,
+ [OPTION_STRING] = 1,
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ [OPTION_STR1035] = 1,
+#endif
+ [OPTION_BOOLEAN] = sizeof("yes "),
+ [OPTION_U8] = sizeof("255 "),
+ [OPTION_U16] = sizeof("65535 "),
+ [OPTION_S16] = sizeof("-32768 "),
+ [OPTION_U32] = sizeof("4294967295 "),
+ [OPTION_S32] = sizeof("-2147483684 "),
+};
+
+
+static inline int upper_length(int length, int opt_index)
+{
+ return max_option_length[opt_index] *
+ (length / dhcp_option_lengths[opt_index]);
+}
+
+
+static int sprintip(char *dest, const char *pre, const uint8_t *ip)
+{
+ return sprintf(dest, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]);
+}
+
+
+/* really simple implementation, just count the bits */
+static int mton(uint32_t mask)
+{
+ int i = 0;
+ mask = ntohl(mask); /* 111110000-like bit pattern */
+ while (mask) {
+ i++;
+ mask <<= 1;
+ }
+ return i;
+}
+
+
+/* Allocate and fill with the text of option 'option'. */
+static char *alloc_fill_opts(uint8_t *option, const struct dhcp_option *type_p, const char *opt_name)
+{
+ int len, type, optlen;
+ uint16_t val_u16;
+ int16_t val_s16;
+ uint32_t val_u32;
+ int32_t val_s32;
+ char *dest, *ret;
+
+ len = option[OPT_LEN - 2];
+ type = type_p->flags & TYPE_MASK;
+ optlen = dhcp_option_lengths[type];
+
+ dest = ret = xmalloc(upper_length(len, type) + strlen(opt_name) + 2);
+ dest += sprintf(ret, "%s=", opt_name);
+
+ for (;;) {
+ switch (type) {
+ case OPTION_IP_PAIR:
+ dest += sprintip(dest, "", option);
+ *dest++ = '/';
+ option += 4;
+ optlen = 4;
+ case OPTION_IP: /* Works regardless of host byte order. */
+ dest += sprintip(dest, "", option);
+ break;
+ case OPTION_BOOLEAN:
+ dest += sprintf(dest, *option ? "yes" : "no");
+ break;
+ case OPTION_U8:
+ dest += sprintf(dest, "%u", *option);
+ break;
+ case OPTION_U16:
+ memcpy(&val_u16, option, 2);
+ dest += sprintf(dest, "%u", ntohs(val_u16));
+ break;
+ case OPTION_S16:
+ memcpy(&val_s16, option, 2);
+ dest += sprintf(dest, "%d", ntohs(val_s16));
+ break;
+ case OPTION_U32:
+ memcpy(&val_u32, option, 4);
+ dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32));
+ break;
+ case OPTION_S32:
+ memcpy(&val_s32, option, 4);
+ dest += sprintf(dest, "%ld", (long) ntohl(val_s32));
+ break;
+ case OPTION_STRING:
+ memcpy(dest, option, len);
+ dest[len] = '\0';
+ return ret; /* Short circuit this case */
+#if ENABLE_FEATURE_UDHCP_RFC3397
+ case OPTION_STR1035:
+ /* unpack option into dest; use ret for prefix (i.e., "optname=") */
+ dest = dname_dec(option, len, ret);
+ free(ret);
+ return dest;
+#endif
+ }
+ option += optlen;
+ len -= optlen;
+ if (len <= 0) break;
+ dest += sprintf(dest, " ");
+ }
+ return ret;
+}
+
+
+/* put all the parameters into an environment */
+static char **fill_envp(struct dhcpMessage *packet)
+{
+ int num_options = 0;
+ int i, j;
+ char **envp;
+ char *var;
+ const char *opt_name;
+ uint8_t *temp;
+ char over = 0;
+
+ if (packet) {
+ for (i = 0; dhcp_options[i].code; i++) {
+ if (get_option(packet, dhcp_options[i].code)) {
+ num_options++;
+ if (dhcp_options[i].code == DHCP_SUBNET)
+ num_options++; /* for mton */
+ }
+ }
+ if (packet->siaddr)
+ num_options++;
+ temp = get_option(packet, DHCP_OPTION_OVER);
+ if (temp)
+ over = *temp;
+ if (!(over & FILE_FIELD) && packet->file[0])
+ num_options++;
+ if (!(over & SNAME_FIELD) && packet->sname[0])
+ num_options++;
+ }
+
+ envp = xzalloc(sizeof(char *) * (num_options + 5));
+ j = 0;
+ envp[j++] = xasprintf("interface=%s", client_config.interface);
+ var = getenv("PATH");
+ if (var)
+ envp[j++] = xasprintf("PATH=%s", var);
+ var = getenv("HOME");
+ if (var)
+ envp[j++] = xasprintf("HOME=%s", var);
+
+ if (packet == NULL)
+ return envp;
+
+ envp[j] = xmalloc(sizeof("ip=255.255.255.255"));
+ sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr);
+
+ opt_name = dhcp_option_strings;
+ i = 0;
+ while (*opt_name) {
+ temp = get_option(packet, dhcp_options[i].code);
+ if (!temp)
+ goto next;
+ envp[j++] = alloc_fill_opts(temp, &dhcp_options[i], opt_name);
+
+ /* Fill in a subnet bits option for things like /24 */
+ if (dhcp_options[i].code == DHCP_SUBNET) {
+ uint32_t subnet;
+ memcpy(&subnet, temp, 4);
+ envp[j++] = xasprintf("mask=%d", mton(subnet));
+ }
+ next:
+ opt_name += strlen(opt_name) + 1;
+ i++;
+ }
+ if (packet->siaddr) {
+ envp[j] = xmalloc(sizeof("siaddr=255.255.255.255"));
+ sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr);
+ }
+ if (!(over & FILE_FIELD) && packet->file[0]) {
+ /* watch out for invalid packets */
+ packet->file[sizeof(packet->file) - 1] = '\0';
+ envp[j++] = xasprintf("boot_file=%s", packet->file);
+ }
+ if (!(over & SNAME_FIELD) && packet->sname[0]) {
+ /* watch out for invalid packets */
+ packet->sname[sizeof(packet->sname) - 1] = '\0';
+ envp[j++] = xasprintf("sname=%s", packet->sname);
+ }
+ return envp;
+}
+
+
+/* Call a script with a par file and env vars */
+void FAST_FUNC udhcp_run_script(struct dhcpMessage *packet, const char *name)
+{
+ int pid;
+ char **envp, **curr;
+
+ if (client_config.script == NULL)
+ return;
+
+ DEBUG("vfork'ing and execle'ing %s", client_config.script);
+
+ envp = fill_envp(packet);
+
+ /* call script */
+// can we use wait4pid(spawn(...)) here?
+ pid = vfork();
+ if (pid < 0) return;
+ if (pid == 0) {
+ /* close fd's? */
+ /* exec script */
+ execle(client_config.script, client_config.script,
+ name, NULL, envp);
+ bb_perror_msg_and_die("exec %s", client_config.script);
+ }
+ safe_waitpid(pid, NULL, 0);
+ for (curr = envp; *curr; curr++)
+ free(*curr);
+ free(envp);
+}
diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c
new file mode 100644
index 0000000..dcc234c
--- /dev/null
+++ b/networking/udhcp/serverpacket.c
@@ -0,0 +1,266 @@
+/* vi: set sw=4 ts=4: */
+/* serverpacket.c
+ *
+ * Construct and send DHCP server packets
+ *
+ * Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "common.h"
+#include "dhcpc.h"
+#include "dhcpd.h"
+#include "options.h"
+
+
+/* send a packet to giaddr using the kernel ip stack */
+static int send_packet_to_relay(struct dhcpMessage *payload)
+{
+ DEBUG("Forwarding packet to relay");
+
+ return udhcp_send_kernel_packet(payload, server_config.server, SERVER_PORT,
+ payload->giaddr, SERVER_PORT);
+}
+
+
+/* send a packet to a specific arp address and ip address by creating our own ip packet */
+static int send_packet_to_client(struct dhcpMessage *payload, int force_broadcast)
+{
+ const uint8_t *chaddr;
+ uint32_t ciaddr;
+
+ if (force_broadcast) {
+ DEBUG("broadcasting packet to client (NAK)");
+ ciaddr = INADDR_BROADCAST;
+ chaddr = MAC_BCAST_ADDR;
+ } else if (payload->ciaddr) {
+ DEBUG("unicasting packet to client ciaddr");
+ ciaddr = payload->ciaddr;
+ chaddr = payload->chaddr;
+ } else if (payload->flags & htons(BROADCAST_FLAG)) {
+ DEBUG("broadcasting packet to client (requested)");
+ ciaddr = INADDR_BROADCAST;
+ chaddr = MAC_BCAST_ADDR;
+ } else {
+ DEBUG("unicasting packet to client yiaddr");
+ ciaddr = payload->yiaddr;
+ chaddr = payload->chaddr;
+ }
+ return udhcp_send_raw_packet(payload,
+ /*src*/ server_config.server, SERVER_PORT,
+ /*dst*/ ciaddr, CLIENT_PORT, chaddr,
+ server_config.ifindex);
+}
+
+
+/* send a dhcp packet, if force broadcast is set, the packet will be broadcast to the client */
+static int send_packet(struct dhcpMessage *payload, int force_broadcast)
+{
+ if (payload->giaddr)
+ return send_packet_to_relay(payload);
+ return send_packet_to_client(payload, force_broadcast);
+}
+
+
+static void init_packet(struct dhcpMessage *packet, struct dhcpMessage *oldpacket, char type)
+{
+ udhcp_init_header(packet, type);
+ packet->xid = oldpacket->xid;
+ memcpy(packet->chaddr, oldpacket->chaddr, 16);
+ packet->flags = oldpacket->flags;
+ packet->giaddr = oldpacket->giaddr;
+ packet->ciaddr = oldpacket->ciaddr;
+ add_simple_option(packet->options, DHCP_SERVER_ID, server_config.server);
+}
+
+
+/* add in the bootp options */
+static void add_bootp_options(struct dhcpMessage *packet)
+{
+ packet->siaddr = server_config.siaddr;
+ if (server_config.sname)
+ strncpy((char*)packet->sname, server_config.sname, sizeof(packet->sname) - 1);
+ if (server_config.boot_file)
+ strncpy((char*)packet->file, server_config.boot_file, sizeof(packet->file) - 1);
+}
+
+
+/* send a DHCP OFFER to a DHCP DISCOVER */
+int FAST_FUNC send_offer(struct dhcpMessage *oldpacket)
+{
+ struct dhcpMessage packet;
+ struct dhcpOfferedAddr *lease = NULL;
+ uint32_t req_align, lease_time_align = server_config.lease;
+ uint8_t *req, *lease_time;
+ struct option_set *curr;
+ struct in_addr addr;
+
+ uint32_t static_lease_ip;
+
+ init_packet(&packet, oldpacket, DHCPOFFER);
+
+ static_lease_ip = getIpByMac(server_config.static_leases, oldpacket->chaddr);
+
+ /* ADDME: if static, short circuit */
+ if (!static_lease_ip) {
+ /* the client is in our lease/offered table */
+ lease = find_lease_by_chaddr(oldpacket->chaddr);
+ if (lease) {
+ if (!lease_expired(lease))
+ lease_time_align = lease->expires - time(0);
+ packet.yiaddr = lease->yiaddr;
+ /* Or the client has a requested ip */
+ } else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP))
+ /* Don't look here (ugly hackish thing to do) */
+ && memcpy(&req_align, req, 4)
+ /* and the ip is in the lease range */
+ && ntohl(req_align) >= server_config.start_ip
+ && ntohl(req_align) <= server_config.end_ip
+ && !static_lease_ip /* Check that its not a static lease */
+ /* and is not already taken/offered */
+ && (!(lease = find_lease_by_yiaddr(req_align))
+ /* or its taken, but expired */ /* ADDME: or maybe in here */
+ || lease_expired(lease))
+ ) {
+ packet.yiaddr = req_align; /* FIXME: oh my, is there a host using this IP? */
+ /* otherwise, find a free IP */
+ } else {
+ /* Is it a static lease? (No, because find_address skips static lease) */
+ packet.yiaddr = find_address(0);
+ /* try for an expired lease */
+ if (!packet.yiaddr)
+ packet.yiaddr = find_address(1);
+ }
+
+ if (!packet.yiaddr) {
+ bb_error_msg("no IP addresses to give - OFFER abandoned");
+ return -1;
+ }
+ if (!add_lease(packet.chaddr, packet.yiaddr, server_config.offer_time)) {
+ bb_error_msg("lease pool is full - OFFER abandoned");
+ return -1;
+ }
+ lease_time = get_option(oldpacket, DHCP_LEASE_TIME);
+ if (lease_time) {
+ memcpy(&lease_time_align, lease_time, 4);
+ lease_time_align = ntohl(lease_time_align);
+ if (lease_time_align > server_config.lease)
+ lease_time_align = server_config.lease;
+ }
+
+ /* Make sure we aren't just using the lease time from the previous offer */
+ if (lease_time_align < server_config.min_lease)
+ lease_time_align = server_config.lease;
+ /* ADDME: end of short circuit */
+ } else {
+ /* It is a static lease... use it */
+ packet.yiaddr = static_lease_ip;
+ }
+
+ add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align));
+
+ curr = server_config.options;
+ while (curr) {
+ if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
+ add_option_string(packet.options, curr->data);
+ curr = curr->next;
+ }
+
+ add_bootp_options(&packet);
+
+ addr.s_addr = packet.yiaddr;
+ bb_info_msg("Sending OFFER of %s", inet_ntoa(addr));
+ return send_packet(&packet, 0);
+}
+
+
+int FAST_FUNC send_NAK(struct dhcpMessage *oldpacket)
+{
+ struct dhcpMessage packet;
+
+ init_packet(&packet, oldpacket, DHCPNAK);
+
+ DEBUG("Sending NAK");
+ return send_packet(&packet, 1);
+}
+
+
+int FAST_FUNC send_ACK(struct dhcpMessage *oldpacket, uint32_t yiaddr)
+{
+ struct dhcpMessage packet;
+ struct option_set *curr;
+ uint8_t *lease_time;
+ uint32_t lease_time_align = server_config.lease;
+ struct in_addr addr;
+
+ init_packet(&packet, oldpacket, DHCPACK);
+ packet.yiaddr = yiaddr;
+
+ lease_time = get_option(oldpacket, DHCP_LEASE_TIME);
+ if (lease_time) {
+ memcpy(&lease_time_align, lease_time, 4);
+ lease_time_align = ntohl(lease_time_align);
+ if (lease_time_align > server_config.lease)
+ lease_time_align = server_config.lease;
+ else if (lease_time_align < server_config.min_lease)
+ lease_time_align = server_config.lease;
+ }
+
+ add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align));
+
+ curr = server_config.options;
+ while (curr) {
+ if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
+ add_option_string(packet.options, curr->data);
+ curr = curr->next;
+ }
+
+ add_bootp_options(&packet);
+
+ addr.s_addr = packet.yiaddr;
+ bb_info_msg("Sending ACK to %s", inet_ntoa(addr));
+
+ if (send_packet(&packet, 0) < 0)
+ return -1;
+
+ add_lease(packet.chaddr, packet.yiaddr, lease_time_align);
+ if (ENABLE_FEATURE_UDHCPD_WRITE_LEASES_EARLY) {
+ /* rewrite the file with leases at every new acceptance */
+ write_leases();
+ }
+
+ return 0;
+}
+
+
+int FAST_FUNC send_inform(struct dhcpMessage *oldpacket)
+{
+ struct dhcpMessage packet;
+ struct option_set *curr;
+
+ init_packet(&packet, oldpacket, DHCPACK);
+
+ curr = server_config.options;
+ while (curr) {
+ if (curr->data[OPT_CODE] != DHCP_LEASE_TIME)
+ add_option_string(packet.options, curr->data);
+ curr = curr->next;
+ }
+
+ add_bootp_options(&packet);
+
+ return send_packet(&packet, 0);
+}
diff --git a/networking/udhcp/signalpipe.c b/networking/udhcp/signalpipe.c
new file mode 100644
index 0000000..a025bd8
--- /dev/null
+++ b/networking/udhcp/signalpipe.c
@@ -0,0 +1,82 @@
+/* vi: set sw=4 ts=4: */
+/* signalpipe.c
+ *
+ * Signal pipe infrastructure. A reliable way of delivering signals.
+ *
+ * Russ Dill <Russ.Dill@asu.edu> December 2003
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "common.h"
+
+
+static struct fd_pair signal_pipe;
+
+static void signal_handler(int sig)
+{
+ unsigned char ch = sig; /* use char, avoid dealing with partial writes */
+ if (write(signal_pipe.wr, &ch, 1) != 1)
+ bb_perror_msg("cannot send signal");
+}
+
+
+/* Call this before doing anything else. Sets up the socket pair
+ * and installs the signal handler */
+void FAST_FUNC udhcp_sp_setup(void)
+{
+ /* was socketpair, but it needs AF_UNIX in kernel */
+ xpiped_pair(signal_pipe);
+ close_on_exec_on(signal_pipe.rd);
+ close_on_exec_on(signal_pipe.wr);
+ ndelay_on(signal_pipe.wr);
+ bb_signals(0
+ + (1 << SIGUSR1)
+ + (1 << SIGUSR2)
+ + (1 << SIGTERM)
+ , signal_handler);
+}
+
+
+/* Quick little function to setup the rfds. Will return the
+ * max_fd for use with select. Limited in that you can only pass
+ * one extra fd */
+int FAST_FUNC udhcp_sp_fd_set(fd_set *rfds, int extra_fd)
+{
+ FD_ZERO(rfds);
+ FD_SET(signal_pipe.rd, rfds);
+ if (extra_fd >= 0) {
+ close_on_exec_on(extra_fd);
+ FD_SET(extra_fd, rfds);
+ }
+ return signal_pipe.rd > extra_fd ? signal_pipe.rd : extra_fd;
+}
+
+
+/* Read a signal from the signal pipe. Returns 0 if there is
+ * no signal, -1 on error (and sets errno appropriately), and
+ * your signal on success */
+int FAST_FUNC udhcp_sp_read(const fd_set *rfds)
+{
+ unsigned char sig;
+
+ if (!FD_ISSET(signal_pipe.rd, rfds))
+ return 0;
+
+ if (safe_read(signal_pipe.rd, &sig, 1) != 1)
+ return -1;
+
+ return sig;
+}
diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c
new file mode 100644
index 0000000..385d5c3
--- /dev/null
+++ b/networking/udhcp/socket.c
@@ -0,0 +1,111 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * socket.c -- DHCP server client/server socket creation
+ *
+ * udhcp client/server
+ * Copyright (C) 1999 Matthew Ramsay <matthewr@moreton.com.au>
+ * Chris Trew <ctrew@moreton.com.au>
+ *
+ * Rewrite by Russ Dill <Russ.Dill@asu.edu> July 2001
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <net/if.h>
+#include <features.h>
+#if (defined(__GLIBC__) && __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION
+#include <netpacket/packet.h>
+#include <net/ethernet.h>
+#else
+#include <asm/types.h>
+#include <linux/if_packet.h>
+#include <linux/if_ether.h>
+#endif
+
+#include "common.h"
+
+
+int FAST_FUNC udhcp_read_interface(const char *interface, int *ifindex, uint32_t *addr, uint8_t *arp)
+{
+ int fd;
+ struct ifreq ifr;
+ struct sockaddr_in *our_ip;
+
+ memset(&ifr, 0, sizeof(ifr));
+ fd = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+
+ ifr.ifr_addr.sa_family = AF_INET;
+ strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
+ if (addr) {
+ if (ioctl_or_perror(fd, SIOCGIFADDR, &ifr,
+ "is interface %s up and configured?", interface)
+ ) {
+ close(fd);
+ return -1;
+ }
+ our_ip = (struct sockaddr_in *) &ifr.ifr_addr;
+ *addr = our_ip->sin_addr.s_addr;
+ DEBUG("%s (our ip) = %s", ifr.ifr_name, inet_ntoa(our_ip->sin_addr));
+ }
+
+ if (ifindex) {
+ if (ioctl_or_warn(fd, SIOCGIFINDEX, &ifr) != 0) {
+ close(fd);
+ return -1;
+ }
+ DEBUG("adapter index %d", ifr.ifr_ifindex);
+ *ifindex = ifr.ifr_ifindex;
+ }
+
+ if (arp) {
+ if (ioctl_or_warn(fd, SIOCGIFHWADDR, &ifr) != 0) {
+ close(fd);
+ return -1;
+ }
+ memcpy(arp, ifr.ifr_hwaddr.sa_data, 6);
+ DEBUG("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x",
+ arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]);
+ }
+
+ close(fd);
+ return 0;
+}
+
+/* 1. None of the callers expects it to ever fail */
+/* 2. ip was always INADDR_ANY */
+int FAST_FUNC udhcp_listen_socket(/*uint32_t ip,*/ int port, const char *inf)
+{
+ int fd;
+ struct sockaddr_in addr;
+
+ DEBUG("Opening listen socket on *:%d %s", port, inf);
+ fd = xsocket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ setsockopt_reuseaddr(fd);
+ if (setsockopt_broadcast(fd) == -1)
+ bb_perror_msg_and_die("SO_BROADCAST");
+
+ /* NB: bug 1032 says this doesn't work on ethernet aliases (ethN:M) */
+ if (setsockopt_bindtodevice(fd, inf))
+ xfunc_die(); /* warning is already printed */
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ /* addr.sin_addr.s_addr = ip; - all-zeros is INADDR_ANY */
+ xbind(fd, (struct sockaddr *)&addr, sizeof(addr));
+
+ return fd;
+}
diff --git a/networking/udhcp/static_leases.c b/networking/udhcp/static_leases.c
new file mode 100644
index 0000000..43f1c98
--- /dev/null
+++ b/networking/udhcp/static_leases.c
@@ -0,0 +1,99 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * static_leases.c -- Couple of functions to assist with storing and
+ * retrieving data for static leases
+ *
+ * Wade Berrier <wberrier@myrealbox.com> September 2004
+ *
+ */
+
+#include "common.h"
+#include "dhcpd.h"
+
+
+/* Takes the address of the pointer to the static_leases linked list,
+ * Address to a 6 byte mac address
+ * Address to a 4 byte ip address */
+int FAST_FUNC addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip)
+{
+ struct static_lease *cur;
+ struct static_lease *new_static_lease;
+
+ /* Build new node */
+ new_static_lease = xmalloc(sizeof(struct static_lease));
+ new_static_lease->mac = mac;
+ new_static_lease->ip = ip;
+ new_static_lease->next = NULL;
+
+ /* If it's the first node to be added... */
+ if (*lease_struct == NULL) {
+ *lease_struct = new_static_lease;
+ } else {
+ cur = *lease_struct;
+ while (cur->next) {
+ cur = cur->next;
+ }
+
+ cur->next = new_static_lease;
+ }
+
+ return 1;
+}
+
+/* Check to see if a mac has an associated static lease */
+uint32_t FAST_FUNC getIpByMac(struct static_lease *lease_struct, void *arg)
+{
+ uint32_t return_ip;
+ struct static_lease *cur = lease_struct;
+ uint8_t *mac = arg;
+
+ return_ip = 0;
+
+ while (cur) {
+ /* If the client has the correct mac */
+ if (memcmp(cur->mac, mac, 6) == 0) {
+ return_ip = *(cur->ip);
+ }
+
+ cur = cur->next;
+ }
+
+ return return_ip;
+}
+
+/* Check to see if an ip is reserved as a static ip */
+uint32_t FAST_FUNC reservedIp(struct static_lease *lease_struct, uint32_t ip)
+{
+ struct static_lease *cur = lease_struct;
+
+ uint32_t return_val = 0;
+
+ while (cur) {
+ /* If the client has the correct ip */
+ if (*cur->ip == ip)
+ return_val = 1;
+
+ cur = cur->next;
+ }
+
+ return return_val;
+}
+
+#if ENABLE_UDHCP_DEBUG
+/* Print out static leases just to check what's going on */
+/* Takes the address of the pointer to the static_leases linked list */
+void FAST_FUNC printStaticLeases(struct static_lease **arg)
+{
+ /* Get a pointer to the linked list */
+ struct static_lease *cur = *arg;
+
+ while (cur) {
+ /* printf("PrintStaticLeases: Lease mac Address: %x\n", cur->mac); */
+ printf("PrintStaticLeases: Lease mac Value: %x\n", *(cur->mac));
+ /* printf("PrintStaticLeases: Lease ip Address: %x\n", cur->ip); */
+ printf("PrintStaticLeases: Lease ip Value: %x\n", *(cur->ip));
+
+ cur = cur->next;
+ }
+}
+#endif
diff --git a/networking/vconfig.c b/networking/vconfig.c
new file mode 100644
index 0000000..3f12e76
--- /dev/null
+++ b/networking/vconfig.c
@@ -0,0 +1,161 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * vconfig implementation for busybox
+ *
+ * Copyright (C) 2001 Manuel Novoa III <mjn3@codepoet.org>
+ *
+ * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
+ */
+
+/* BB_AUDIT SUSv3 N/A */
+
+#include "libbb.h"
+#include <net/if.h>
+
+/* Stuff from linux/if_vlan.h, kernel version 2.4.23 */
+enum vlan_ioctl_cmds {
+ ADD_VLAN_CMD,
+ DEL_VLAN_CMD,
+ SET_VLAN_INGRESS_PRIORITY_CMD,
+ SET_VLAN_EGRESS_PRIORITY_CMD,
+ GET_VLAN_INGRESS_PRIORITY_CMD,
+ GET_VLAN_EGRESS_PRIORITY_CMD,
+ SET_VLAN_NAME_TYPE_CMD,
+ SET_VLAN_FLAG_CMD
+};
+enum vlan_name_types {
+ VLAN_NAME_TYPE_PLUS_VID, /* Name will look like: vlan0005 */
+ VLAN_NAME_TYPE_RAW_PLUS_VID, /* name will look like: eth1.0005 */
+ VLAN_NAME_TYPE_PLUS_VID_NO_PAD, /* Name will look like: vlan5 */
+ VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, /* Name will look like: eth0.5 */
+ VLAN_NAME_TYPE_HIGHEST
+};
+
+struct vlan_ioctl_args {
+ int cmd; /* Should be one of the vlan_ioctl_cmds enum above. */
+ char device1[24];
+
+ union {
+ char device2[24];
+ int VID;
+ unsigned int skb_priority;
+ unsigned int name_type;
+ unsigned int bind_type;
+ unsigned int flag; /* Matches vlan_dev_info flags */
+ } u;
+
+ short vlan_qos;
+};
+
+#define VLAN_GROUP_ARRAY_LEN 4096
+#define SIOCSIFVLAN 0x8983 /* Set 802.1Q VLAN options */
+
+/* On entry, table points to the length of the current string plus
+ * nul terminator plus data length for the subsequent entry. The
+ * return value is the last data entry for the matching string. */
+static const char *xfind_str(const char *table, const char *str)
+{
+ while (strcasecmp(str, table+1) != 0) {
+ if (!*(table += table[0])) {
+ bb_show_usage();
+ }
+ }
+ return table - 1;
+}
+
+static const char cmds[] ALIGN1 = {
+ 4, ADD_VLAN_CMD, 7,
+ 'a', 'd', 'd', 0,
+ 3, DEL_VLAN_CMD, 7,
+ 'r', 'e', 'm', 0,
+ 3, SET_VLAN_NAME_TYPE_CMD, 17,
+ 's', 'e', 't', '_',
+ 'n', 'a', 'm', 'e', '_',
+ 't', 'y', 'p', 'e', 0,
+ 5, SET_VLAN_FLAG_CMD, 12,
+ 's', 'e', 't', '_',
+ 'f', 'l', 'a', 'g', 0,
+ 5, SET_VLAN_EGRESS_PRIORITY_CMD, 18,
+ 's', 'e', 't', '_',
+ 'e', 'g', 'r', 'e', 's', 's', '_',
+ 'm', 'a', 'p', 0,
+ 5, SET_VLAN_INGRESS_PRIORITY_CMD, 16,
+ 's', 'e', 't', '_',
+ 'i', 'n', 'g', 'r', 'e', 's', 's', '_',
+ 'm', 'a', 'p', 0,
+};
+
+static const char name_types[] ALIGN1 = {
+ VLAN_NAME_TYPE_PLUS_VID, 16,
+ 'V', 'L', 'A', 'N',
+ '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D',
+ 0,
+ VLAN_NAME_TYPE_PLUS_VID_NO_PAD, 22,
+ 'V', 'L', 'A', 'N',
+ '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D',
+ '_', 'N', 'O', '_', 'P', 'A', 'D', 0,
+ VLAN_NAME_TYPE_RAW_PLUS_VID, 15,
+ 'D', 'E', 'V',
+ '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D',
+ 0,
+ VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, 20,
+ 'D', 'E', 'V',
+ '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D',
+ '_', 'N', 'O', '_', 'P', 'A', 'D', 0,
+};
+
+static const char conf_file_name[] ALIGN1 = "/proc/net/vlan/config";
+
+int vconfig_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int vconfig_main(int argc, char **argv)
+{
+ struct vlan_ioctl_args ifr;
+ const char *p;
+ int fd;
+
+ if (argc < 3) {
+ bb_show_usage();
+ }
+
+ /* Don't bother closing the filedes. It will be closed on cleanup. */
+ /* Will die if 802.1q is not present */
+ xopen(conf_file_name, O_RDONLY);
+
+ memset(&ifr, 0, sizeof(struct vlan_ioctl_args));
+
+ ++argv;
+ p = xfind_str(cmds+2, *argv);
+ ifr.cmd = *p;
+ if (argc != p[-1]) {
+ bb_show_usage();
+ }
+
+ if (ifr.cmd == SET_VLAN_NAME_TYPE_CMD) { /* set_name_type */
+ ifr.u.name_type = *xfind_str(name_types+1, argv[1]);
+ } else {
+ strncpy(ifr.device1, argv[1], IFNAMSIZ);
+ p = argv[2];
+
+ /* I suppose one could try to combine some of the function calls below,
+ * since ifr.u.flag, ifr.u.VID, and ifr.u.skb_priority are all same-sized
+ * (unsigned) int members of a unions. But because of the range checking,
+ * doing so wouldn't save that much space and would also make maintainence
+ * more of a pain. */
+ if (ifr.cmd == SET_VLAN_FLAG_CMD) { /* set_flag */
+ ifr.u.flag = xatoul_range(p, 0, 1);
+ /* DM: in order to set reorder header, qos must be set */
+ ifr.vlan_qos = xatoul_range(argv[3], 0, 7);
+ } else if (ifr.cmd == ADD_VLAN_CMD) { /* add */
+ ifr.u.VID = xatoul_range(p, 0, VLAN_GROUP_ARRAY_LEN-1);
+ } else if (ifr.cmd != DEL_VLAN_CMD) { /* set_{egress|ingress}_map */
+ ifr.u.skb_priority = xatou(p);
+ ifr.vlan_qos = xatoul_range(argv[3], 0, 7);
+ }
+ }
+
+ fd = xsocket(AF_INET, SOCK_STREAM, 0);
+ ioctl_or_perror_and_die(fd, SIOCSIFVLAN, &ifr,
+ "ioctl error for %s", *argv);
+
+ return 0;
+}
diff --git a/networking/wget.c b/networking/wget.c
new file mode 100644
index 0000000..23143d5
--- /dev/null
+++ b/networking/wget.c
@@ -0,0 +1,836 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * wget - retrieve a file using HTTP or FTP
+ *
+ * Chip Rosenthal Covad Communications <chip@laserlink.net>
+ *
+ */
+
+#include "libbb.h"
+
+struct host_info {
+ // May be used if we ever will want to free() all xstrdup()s...
+ /* char *allocated; */
+ const char *path;
+ const char *user;
+ char *host;
+ int port;
+ smallint is_ftp;
+};
+
+
+/* Globals (can be accessed from signal handlers) */
+struct globals {
+ off_t content_len; /* Content-length of the file */
+ off_t beg_range; /* Range at which continue begins */
+#if ENABLE_FEATURE_WGET_STATUSBAR
+ off_t lastsize;
+ off_t totalsize;
+ off_t transferred; /* Number of bytes transferred so far */
+ const char *curfile; /* Name of current file being transferred */
+ unsigned lastupdate_sec;
+ unsigned start_sec;
+#endif
+ smallint chunked; /* chunked transfer encoding */
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+struct BUG_G_too_big {
+ char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
+};
+#define content_len (G.content_len )
+#define beg_range (G.beg_range )
+#define lastsize (G.lastsize )
+#define totalsize (G.totalsize )
+#define transferred (G.transferred )
+#define curfile (G.curfile )
+#define lastupdate_sec (G.lastupdate_sec )
+#define start_sec (G.start_sec )
+#define chunked (G.chunked )
+#define INIT_G() do { } while (0)
+
+
+#if ENABLE_FEATURE_WGET_STATUSBAR
+enum {
+ STALLTIME = 5 /* Seconds when xfer considered "stalled" */
+};
+
+static unsigned int getttywidth(void)
+{
+ unsigned width;
+ get_terminal_width_height(0, &width, NULL);
+ return width;
+}
+
+static void progressmeter(int flag)
+{
+ /* We can be called from signal handler */
+ int save_errno = errno;
+ off_t abbrevsize;
+ unsigned since_last_update, elapsed;
+ unsigned ratio;
+ int barlength, i;
+
+ if (flag == -1) { /* first call to progressmeter */
+ start_sec = monotonic_sec();
+ lastupdate_sec = start_sec;
+ lastsize = 0;
+ totalsize = content_len + beg_range; /* as content_len changes.. */
+ }
+
+ ratio = 100;
+ if (totalsize != 0 && !chunked) {
+ /* long long helps to have it working even if !LFS */
+ ratio = (unsigned) (100ULL * (transferred+beg_range) / totalsize);
+ if (ratio > 100) ratio = 100;
+ }
+
+ fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
+
+ barlength = getttywidth() - 49;
+ if (barlength > 0) {
+ /* god bless gcc for variable arrays :) */
+ i = barlength * ratio / 100;
+ {
+ char buf[i+1];
+ memset(buf, '*', i);
+ buf[i] = '\0';
+ fprintf(stderr, "|%s%*s|", buf, barlength - i, "");
+ }
+ }
+ i = 0;
+ abbrevsize = transferred + beg_range;
+ while (abbrevsize >= 100000) {
+ i++;
+ abbrevsize >>= 10;
+ }
+ /* see http://en.wikipedia.org/wiki/Tera */
+ fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
+
+// Nuts! Ain't it easier to update progress meter ONLY when we transferred++?
+
+ elapsed = monotonic_sec();
+ since_last_update = elapsed - lastupdate_sec;
+ if (transferred > lastsize) {
+ lastupdate_sec = elapsed;
+ lastsize = transferred;
+ if (since_last_update >= STALLTIME) {
+ /* We "cut off" these seconds from elapsed time
+ * by adjusting start time */
+ start_sec += since_last_update;
+ }
+ since_last_update = 0; /* we are un-stalled now */
+ }
+ elapsed -= start_sec; /* now it's "elapsed since start" */
+
+ if (since_last_update >= STALLTIME) {
+ fprintf(stderr, " - stalled -");
+ } else {
+ off_t to_download = totalsize - beg_range;
+ if (transferred <= 0 || (int)elapsed <= 0 || transferred > to_download || chunked) {
+ fprintf(stderr, "--:--:-- ETA");
+ } else {
+ /* to_download / (transferred/elapsed) - elapsed: */
+ int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed);
+ /* (long long helps to have working ETA even if !LFS) */
+ i = eta % 3600;
+ fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60);
+ }
+ }
+
+ if (flag == 0) {
+ /* last call to progressmeter */
+ alarm(0);
+ transferred = 0;
+ fputc('\n', stderr);
+ } else {
+ if (flag == -1) { /* first call to progressmeter */
+ signal_SA_RESTART_empty_mask(SIGALRM, progressmeter);
+ }
+ alarm(1);
+ }
+
+ errno = save_errno;
+}
+/* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
+ * much of which was blatantly stolen from openssh. */
+/*-
+ * Copyright (c) 1992, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
+ * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
+ *
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+#else /* FEATURE_WGET_STATUSBAR */
+
+static ALWAYS_INLINE void progressmeter(int flag UNUSED_PARAM) { }
+
+#endif
+
+
+/* Read NMEMB bytes into PTR from STREAM. Returns the number of bytes read,
+ * and a short count if an eof or non-interrupt error is encountered. */
+static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
+{
+ size_t ret;
+ char *p = (char*)ptr;
+
+ do {
+ clearerr(stream);
+ ret = fread(p, 1, nmemb, stream);
+ p += ret;
+ nmemb -= ret;
+ } while (nmemb && ferror(stream) && errno == EINTR);
+
+ return p - (char*)ptr;
+}
+
+/* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
+ * Returns S, or NULL if an eof or non-interrupt error is encountered. */
+static char *safe_fgets(char *s, int size, FILE *stream)
+{
+ char *ret;
+
+ do {
+ clearerr(stream);
+ ret = fgets(s, size, stream);
+ } while (ret == NULL && ferror(stream) && errno == EINTR);
+
+ return ret;
+}
+
+#if ENABLE_FEATURE_WGET_AUTHENTICATION
+/* Base64-encode character string. buf is assumed to be char buf[512]. */
+static char *base64enc_512(char buf[512], const char *str)
+{
+ unsigned len = strlen(str);
+ if (len > 512/4*3 - 10) /* paranoia */
+ len = 512/4*3 - 10;
+ bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
+ return buf;
+}
+#endif
+
+
+static FILE *open_socket(len_and_sockaddr *lsa)
+{
+ FILE *fp;
+
+ /* glibc 2.4 seems to try seeking on it - ??! */
+ /* hopefully it understands what ESPIPE means... */
+ fp = fdopen(xconnect_stream(lsa), "r+");
+ if (fp == NULL)
+ bb_perror_msg_and_die("fdopen");
+
+ return fp;
+}
+
+
+static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
+{
+ int result;
+ if (s1) {
+ if (!s2) s2 = "";
+ fprintf(fp, "%s%s\r\n", s1, s2);
+ fflush(fp);
+ }
+
+ do {
+ char *buf_ptr;
+
+ if (fgets(buf, 510, fp) == NULL) {
+ bb_perror_msg_and_die("error getting response");
+ }
+ buf_ptr = strstr(buf, "\r\n");
+ if (buf_ptr) {
+ *buf_ptr = '\0';
+ }
+ } while (!isdigit(buf[0]) || buf[3] != ' ');
+
+ buf[3] = '\0';
+ result = xatoi_u(buf);
+ buf[3] = ' ';
+ return result;
+}
+
+
+static void parse_url(char *src_url, struct host_info *h)
+{
+ char *url, *p, *sp;
+
+ /* h->allocated = */ url = xstrdup(src_url);
+
+ if (strncmp(url, "http://", 7) == 0) {
+ h->port = bb_lookup_port("http", "tcp", 80);
+ h->host = url + 7;
+ h->is_ftp = 0;
+ } else if (strncmp(url, "ftp://", 6) == 0) {
+ h->port = bb_lookup_port("ftp", "tcp", 21);
+ h->host = url + 6;
+ h->is_ftp = 1;
+ } else
+ bb_error_msg_and_die("not an http or ftp url: %s", url);
+
+ // FYI:
+ // "Real" wget 'http://busybox.net?var=a/b' sends this request:
+ // 'GET /?var=a/b HTTP 1.0'
+ // and saves 'index.html?var=a%2Fb' (we save 'b')
+ // wget 'http://busybox.net?login=john@doe':
+ // request: 'GET /?login=john@doe HTTP/1.0'
+ // saves: 'index.html?login=john@doe' (we save '?login=john@doe')
+ // wget 'http://busybox.net#test/test':
+ // request: 'GET / HTTP/1.0'
+ // saves: 'index.html' (we save 'test')
+ //
+ // We also don't add unique .N suffix if file exists...
+ sp = strchr(h->host, '/');
+ p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
+ p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
+ if (!sp) {
+ h->path = "";
+ } else if (*sp == '/') {
+ *sp = '\0';
+ h->path = sp + 1;
+ } else { // '#' or '?'
+ // http://busybox.net?login=john@doe is a valid URL
+ // memmove converts to:
+ // http:/busybox.nett?login=john@doe...
+ memmove(h->host - 1, h->host, sp - h->host);
+ h->host--;
+ sp[-1] = '\0';
+ h->path = sp;
+ }
+
+ sp = strrchr(h->host, '@');
+ h->user = NULL;
+ if (sp != NULL) {
+ h->user = h->host;
+ *sp = '\0';
+ h->host = sp + 1;
+ }
+
+ sp = h->host;
+}
+
+
+static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
+{
+ char *s, *hdrval;
+ int c;
+
+ /* *istrunc = 0; */
+
+ /* retrieve header line */
+ if (fgets(buf, bufsiz, fp) == NULL)
+ return NULL;
+
+ /* see if we are at the end of the headers */
+ for (s = buf; *s == '\r'; ++s)
+ continue;
+ if (*s == '\n')
+ return NULL;
+
+ /* convert the header name to lower case */
+ for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
+ *s = tolower(*s);
+
+ /* verify we are at the end of the header name */
+ if (*s != ':')
+ bb_error_msg_and_die("bad header line: %s", buf);
+
+ /* locate the start of the header value */
+ *s++ = '\0';
+ hdrval = skip_whitespace(s);
+
+ /* locate the end of header */
+ while (*s && *s != '\r' && *s != '\n')
+ ++s;
+
+ /* end of header found */
+ if (*s) {
+ *s = '\0';
+ return hdrval;
+ }
+
+ /* Rats! The buffer isn't big enough to hold the entire header value. */
+ while (c = getc(fp), c != EOF && c != '\n')
+ continue;
+ /* *istrunc = 1; */
+ return hdrval;
+}
+
+
+int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int wget_main(int argc UNUSED_PARAM, char **argv)
+{
+ char buf[512];
+ struct host_info server, target;
+ len_and_sockaddr *lsa;
+ int status;
+ int port;
+ int try = 5;
+ unsigned opt;
+ char *str;
+ char *proxy = 0;
+ char *dir_prefix = NULL;
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ char *extra_headers = NULL;
+ llist_t *headers_llist = NULL;
+#endif
+ FILE *sfp = NULL; /* socket to web/ftp server */
+ FILE *dfp; /* socket to ftp server (data) */
+ char *fname_out; /* where to direct output (-O) */
+ bool got_clen = 0; /* got content-length: from server */
+ int output_fd = -1;
+ bool use_proxy = 1; /* Use proxies if env vars are set */
+ const char *proxy_flag = "on"; /* Use proxies if env vars are set */
+ const char *user_agent = "Wget";/* "User-Agent" header field */
+
+ static const char keywords[] ALIGN1 =
+ "content-length\0""transfer-encoding\0""chunked\0""location\0";
+ enum {
+ KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
+ };
+ enum {
+ WGET_OPT_CONTINUE = (1 << 0),
+ WGET_OPT_SPIDER = (1 << 1),
+ WGET_OPT_QUIET = (1 << 2),
+ WGET_OPT_OUTNAME = (1 << 3),
+ WGET_OPT_PREFIX = (1 << 4),
+ WGET_OPT_PROXY = (1 << 5),
+ WGET_OPT_USER_AGENT = (1 << 6),
+ WGET_OPT_RETRIES = (1 << 7),
+ WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
+ WGET_OPT_PASSIVE = (1 << 9),
+ WGET_OPT_HEADER = (1 << 10),
+ };
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ static const char wget_longopts[] ALIGN1 =
+ /* name, has_arg, val */
+ "continue\0" No_argument "c"
+ "spider\0" No_argument "s"
+ "quiet\0" No_argument "q"
+ "output-document\0" Required_argument "O"
+ "directory-prefix\0" Required_argument "P"
+ "proxy\0" Required_argument "Y"
+ "user-agent\0" Required_argument "U"
+ /* Ignored: */
+ // "tries\0" Required_argument "t"
+ // "timeout\0" Required_argument "T"
+ /* Ignored (we always use PASV): */
+ "passive-ftp\0" No_argument "\xff"
+ "header\0" Required_argument "\xfe"
+ ;
+#endif
+
+ INIT_G();
+
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ applet_long_options = wget_longopts;
+#endif
+ /* server.allocated = target.allocated = NULL; */
+ opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
+ opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
+ &fname_out, &dir_prefix,
+ &proxy_flag, &user_agent,
+ NULL, /* -t RETRIES */
+ NULL /* -T NETWORK_READ_TIMEOUT */
+ USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
+ );
+ if (strcmp(proxy_flag, "off") == 0) {
+ /* Use the proxy if necessary */
+ use_proxy = 0;
+ }
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ if (headers_llist) {
+ int size = 1;
+ char *cp;
+ llist_t *ll = headers_llist;
+ while (ll) {
+ size += strlen(ll->data) + 2;
+ ll = ll->link;
+ }
+ extra_headers = cp = xmalloc(size);
+ while (headers_llist) {
+ cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
+ }
+ }
+#endif
+
+ parse_url(argv[optind], &target);
+ server.host = target.host;
+ server.port = target.port;
+
+ /* Use the proxy if necessary */
+ if (use_proxy) {
+ proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
+ if (proxy && *proxy) {
+ parse_url(proxy, &server);
+ } else {
+ use_proxy = 0;
+ }
+ }
+
+ /* Guess an output filename, if there was no -O FILE */
+ if (!(opt & WGET_OPT_OUTNAME)) {
+ fname_out = bb_get_last_path_component_nostrip(target.path);
+ /* handle "wget http://kernel.org//" */
+ if (fname_out[0] == '/' || !fname_out[0])
+ fname_out = (char*)"index.html";
+ /* -P DIR is considered only if there was no -O FILE */
+ if (dir_prefix)
+ fname_out = concat_path_file(dir_prefix, fname_out);
+ } else {
+ if (LONE_DASH(fname_out)) {
+ /* -O - */
+ output_fd = 1;
+ opt &= ~WGET_OPT_CONTINUE;
+ }
+ }
+#if ENABLE_FEATURE_WGET_STATUSBAR
+ curfile = bb_get_last_path_component_nostrip(fname_out);
+#endif
+
+ /* Impossible?
+ if ((opt & WGET_OPT_CONTINUE) && !fname_out)
+ bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */
+
+ /* Determine where to start transfer */
+ if (opt & WGET_OPT_CONTINUE) {
+ output_fd = open(fname_out, O_WRONLY);
+ if (output_fd >= 0) {
+ beg_range = xlseek(output_fd, 0, SEEK_END);
+ }
+ /* File doesn't exist. We do not create file here yet.
+ We are not sure it exists on remove side */
+ }
+
+ /* We want to do exactly _one_ DNS lookup, since some
+ * sites (i.e. ftp.us.debian.org) use round-robin DNS
+ * and we want to connect to only one IP... */
+ lsa = xhost2sockaddr(server.host, server.port);
+ if (!(opt & WGET_OPT_QUIET)) {
+ fprintf(stderr, "Connecting to %s (%s)\n", server.host,
+ xmalloc_sockaddr2dotted(&lsa->u.sa));
+ /* We leak result of xmalloc_sockaddr2dotted */
+ }
+
+ if (use_proxy || !target.is_ftp) {
+ /*
+ * HTTP session
+ */
+ do {
+ got_clen = 0;
+ chunked = 0;
+
+ if (!--try)
+ bb_error_msg_and_die("too many redirections");
+
+ /* Open socket to http server */
+ if (sfp) fclose(sfp);
+ sfp = open_socket(lsa);
+
+ /* Send HTTP request. */
+ if (use_proxy) {
+ fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
+ target.is_ftp ? "f" : "ht", target.host,
+ target.path);
+ } else {
+ fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
+ }
+
+ fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
+ target.host, user_agent);
+
+#if ENABLE_FEATURE_WGET_AUTHENTICATION
+ if (target.user) {
+ fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
+ base64enc_512(buf, target.user));
+ }
+ if (use_proxy && server.user) {
+ fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
+ base64enc_512(buf, server.user));
+ }
+#endif
+
+ if (beg_range)
+ fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range);
+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+ if (extra_headers)
+ fputs(extra_headers, sfp);
+#endif
+ fprintf(sfp, "Connection: close\r\n\r\n");
+
+ /*
+ * Retrieve HTTP response line and check for "200" status code.
+ */
+ read_response:
+ if (fgets(buf, sizeof(buf), sfp) == NULL)
+ bb_error_msg_and_die("no response from server");
+
+ str = buf;
+ str = skip_non_whitespace(str);
+ str = skip_whitespace(str);
+ // FIXME: no error check
+ // xatou wouldn't work: "200 OK"
+ status = atoi(str);
+ switch (status) {
+ case 0:
+ case 100:
+ while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
+ /* eat all remaining headers */;
+ goto read_response;
+ case 200:
+/*
+Response 204 doesn't say "null file", it says "metadata
+has changed but data didn't":
+
+"10.2.5 204 No Content
+The server has fulfilled the request but does not need to return
+an entity-body, and might want to return updated metainformation.
+The response MAY include new or updated metainformation in the form
+of entity-headers, which if present SHOULD be associated with
+the requested variant.
+
+If the client is a user agent, it SHOULD NOT change its document
+view from that which caused the request to be sent. This response
+is primarily intended to allow input for actions to take place
+without causing a change to the user agent's active document view,
+although any new or updated metainformation SHOULD be applied
+to the document currently in the user agent's active view.
+
+The 204 response MUST NOT include a message-body, and thus
+is always terminated by the first empty line after the header fields."
+
+However, in real world it was observed that some web servers
+(e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
+*/
+ case 204:
+ break;
+ case 300: /* redirection */
+ case 301:
+ case 302:
+ case 303:
+ break;
+ case 206:
+ if (beg_range)
+ break;
+ /* fall through */
+ default:
+ /* Show first line only and kill any ESC tricks */
+ buf[strcspn(buf, "\n\r\x1b")] = '\0';
+ bb_error_msg_and_die("server returned error: %s", buf);
+ }
+
+ /*
+ * Retrieve HTTP headers.
+ */
+ while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
+ /* gethdr did already convert the "FOO:" string to lowercase */
+ smalluint key = index_in_strings(keywords, *&buf) + 1;
+ if (key == KEY_content_length) {
+ content_len = BB_STRTOOFF(str, NULL, 10);
+ if (errno || content_len < 0) {
+ bb_error_msg_and_die("content-length %s is garbage", str);
+ }
+ got_clen = 1;
+ continue;
+ }
+ if (key == KEY_transfer_encoding) {
+ if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
+ bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
+ chunked = got_clen = 1;
+ }
+ if (key == KEY_location) {
+ if (str[0] == '/')
+ /* free(target.allocated); */
+ target.path = /* target.allocated = */ xstrdup(str+1);
+ else {
+ parse_url(str, &target);
+ if (use_proxy == 0) {
+ server.host = target.host;
+ server.port = target.port;
+ }
+ free(lsa);
+ lsa = xhost2sockaddr(server.host, server.port);
+ break;
+ }
+ }
+ }
+ } while (status >= 300);
+
+ dfp = sfp;
+
+ } else {
+
+ /*
+ * FTP session
+ */
+ if (!target.user)
+ target.user = xstrdup("anonymous:busybox@");
+
+ sfp = open_socket(lsa);
+ if (ftpcmd(NULL, NULL, sfp, buf) != 220)
+ bb_error_msg_and_die("%s", buf+4);
+
+ /*
+ * Splitting username:password pair,
+ * trying to log in
+ */
+ str = strchr(target.user, ':');
+ if (str)
+ *(str++) = '\0';
+ switch (ftpcmd("USER ", target.user, sfp, buf)) {
+ case 230:
+ break;
+ case 331:
+ if (ftpcmd("PASS ", str, sfp, buf) == 230)
+ break;
+ /* fall through (failed login) */
+ default:
+ bb_error_msg_and_die("ftp login: %s", buf+4);
+ }
+
+ ftpcmd("TYPE I", NULL, sfp, buf);
+
+ /*
+ * Querying file size
+ */
+ if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) {
+ content_len = BB_STRTOOFF(buf+4, NULL, 10);
+ if (errno || content_len < 0) {
+ bb_error_msg_and_die("SIZE value is garbage");
+ }
+ got_clen = 1;
+ }
+
+ /*
+ * Entering passive mode
+ */
+ if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
+ pasv_error:
+ bb_error_msg_and_die("bad response to %s: %s", "PASV", buf);
+ }
+ // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
+ // Server's IP is N1.N2.N3.N4 (we ignore it)
+ // Server's port for data connection is P1*256+P2
+ str = strrchr(buf, ')');
+ if (str) str[0] = '\0';
+ str = strrchr(buf, ',');
+ if (!str) goto pasv_error;
+ port = xatou_range(str+1, 0, 255);
+ *str = '\0';
+ str = strrchr(buf, ',');
+ if (!str) goto pasv_error;
+ port += xatou_range(str+1, 0, 255) * 256;
+ set_nport(lsa, htons(port));
+ dfp = open_socket(lsa);
+
+ if (beg_range) {
+ sprintf(buf, "REST %"OFF_FMT"d", beg_range);
+ if (ftpcmd(buf, NULL, sfp, buf) == 350)
+ content_len -= beg_range;
+ }
+
+ if (ftpcmd("RETR ", target.path, sfp, buf) > 150)
+ bb_error_msg_and_die("bad response to %s: %s", "RETR", buf);
+ }
+
+ if (opt & WGET_OPT_SPIDER) {
+ if (ENABLE_FEATURE_CLEAN_UP)
+ fclose(sfp);
+ return EXIT_SUCCESS;
+ }
+
+ /*
+ * Retrieve file
+ */
+
+ /* Do it before progressmeter (want to have nice error message) */
+ if (output_fd < 0) {
+ int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
+ /* compat with wget: -O FILE can overwrite */
+ if (opt & WGET_OPT_OUTNAME)
+ o_flags = O_WRONLY | O_CREAT | O_TRUNC;
+ output_fd = xopen(fname_out, o_flags);
+ }
+
+ if (!(opt & WGET_OPT_QUIET))
+ progressmeter(-1);
+
+ if (chunked)
+ goto get_clen;
+
+ /* Loops only if chunked */
+ while (1) {
+ while (content_len > 0 || !got_clen) {
+ int n;
+ unsigned rdsz = sizeof(buf);
+
+ if (content_len < sizeof(buf) && (chunked || got_clen))
+ rdsz = (unsigned)content_len;
+ n = safe_fread(buf, rdsz, dfp);
+ if (n <= 0) {
+ if (ferror(dfp)) {
+ /* perror will not work: ferror doesn't set errno */
+ bb_error_msg_and_die(bb_msg_read_error);
+ }
+ break;
+ }
+ xwrite(output_fd, buf, n);
+#if ENABLE_FEATURE_WGET_STATUSBAR
+ transferred += n;
+#endif
+ if (got_clen)
+ content_len -= n;
+ }
+
+ if (!chunked)
+ break;
+
+ safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
+ get_clen:
+ safe_fgets(buf, sizeof(buf), dfp);
+ content_len = STRTOOFF(buf, NULL, 16);
+ /* FIXME: error check? */
+ if (content_len == 0)
+ break; /* all done! */
+ }
+
+ if (!(opt & WGET_OPT_QUIET))
+ progressmeter(0);
+
+ if ((use_proxy == 0) && target.is_ftp) {
+ fclose(dfp);
+ if (ftpcmd(NULL, NULL, sfp, buf) != 226)
+ bb_error_msg_and_die("ftp error: %s", buf+4);
+ ftpcmd("QUIT", NULL, sfp, buf);
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/networking/zcip.c b/networking/zcip.c
new file mode 100644
index 0000000..ff9c83d
--- /dev/null
+++ b/networking/zcip.c
@@ -0,0 +1,566 @@
+/* vi: set sw=4 ts=4: */
+/*
+ * RFC3927 ZeroConf IPv4 Link-Local addressing
+ * (see <http://www.zeroconf.org/>)
+ *
+ * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com)
+ * Copyright (C) 2004 by David Brownell
+ *
+ * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
+ */
+
+/*
+ * ZCIP just manages the 169.254.*.* addresses. That network is not
+ * routed at the IP level, though various proxies or bridges can
+ * certainly be used. Its naming is built over multicast DNS.
+ */
+
+//#define DEBUG
+
+// TODO:
+// - more real-world usage/testing, especially daemon mode
+// - kernel packet filters to reduce scheduling noise
+// - avoid silent script failures, especially under load...
+// - link status monitoring (restart on link-up; stop on link-down)
+
+#include <netinet/ether.h>
+#include <net/ethernet.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <linux/if_packet.h>
+#include <linux/sockios.h>
+
+#include "libbb.h"
+#include <syslog.h>
+
+/* We don't need more than 32 bits of the counter */
+#define MONOTONIC_US() ((unsigned)monotonic_us())
+
+struct arp_packet {
+ struct ether_header eth;
+ struct ether_arp arp;
+} PACKED;
+
+enum {
+/* 169.254.0.0 */
+ LINKLOCAL_ADDR = 0xa9fe0000,
+
+/* protocol timeout parameters, specified in seconds */
+ PROBE_WAIT = 1,
+ PROBE_MIN = 1,
+ PROBE_MAX = 2,
+ PROBE_NUM = 3,
+ MAX_CONFLICTS = 10,
+ RATE_LIMIT_INTERVAL = 60,
+ ANNOUNCE_WAIT = 2,
+ ANNOUNCE_NUM = 2,
+ ANNOUNCE_INTERVAL = 2,
+ DEFEND_INTERVAL = 10
+};
+
+/* States during the configuration process. */
+enum {
+ PROBE = 0,
+ RATE_LIMIT_PROBE,
+ ANNOUNCE,
+ MONITOR,
+ DEFEND
+};
+
+#define VDBG(...) do { } while (0)
+
+
+enum {
+ sock_fd = 3
+};
+
+struct globals {
+ struct sockaddr saddr;
+ struct ether_addr eth_addr;
+};
+#define G (*(struct globals*)&bb_common_bufsiz1)
+#define saddr (G.saddr )
+#define eth_addr (G.eth_addr)
+
+
+/**
+ * Pick a random link local IP address on 169.254/16, except that
+ * the first and last 256 addresses are reserved.
+ */
+static uint32_t pick(void)
+{
+ unsigned tmp;
+
+ do {
+ tmp = rand() & IN_CLASSB_HOST;
+ } while (tmp > (IN_CLASSB_HOST - 0x0200));
+ return htonl((LINKLOCAL_ADDR + 0x0100) + tmp);
+}
+
+/**
+ * Broadcast an ARP packet.
+ */
+static void arp(
+ /* int op, - always ARPOP_REQUEST */
+ /* const struct ether_addr *source_eth, - always &eth_addr */
+ struct in_addr source_ip,
+ const struct ether_addr *target_eth, struct in_addr target_ip)
+{
+ enum { op = ARPOP_REQUEST };
+#define source_eth (&eth_addr)
+
+ struct arp_packet p;
+ memset(&p, 0, sizeof(p));
+
+ // ether header
+ p.eth.ether_type = htons(ETHERTYPE_ARP);
+ memcpy(p.eth.ether_shost, source_eth, ETH_ALEN);
+ memset(p.eth.ether_dhost, 0xff, ETH_ALEN);
+
+ // arp request
+ p.arp.arp_hrd = htons(ARPHRD_ETHER);
+ p.arp.arp_pro = htons(ETHERTYPE_IP);
+ p.arp.arp_hln = ETH_ALEN;
+ p.arp.arp_pln = 4;
+ p.arp.arp_op = htons(op);
+ memcpy(&p.arp.arp_sha, source_eth, ETH_ALEN);
+ memcpy(&p.arp.arp_spa, &source_ip, sizeof(p.arp.arp_spa));
+ memcpy(&p.arp.arp_tha, target_eth, ETH_ALEN);
+ memcpy(&p.arp.arp_tpa, &target_ip, sizeof(p.arp.arp_tpa));
+
+ // send it
+ // Even though sock_fd is already bound to saddr, just send()
+ // won't work, because "socket is not connected"
+ // (and connect() won't fix that, "operation not supported").
+ // Thus we sendto() to saddr. I wonder which sockaddr
+ // (from bind() or from sendto()?) kernel actually uses
+ // to determine iface to emit the packet from...
+ xsendto(sock_fd, &p, sizeof(p), &saddr, sizeof(saddr));
+#undef source_eth
+}
+
+/**
+ * Run a script.
+ * argv[0]:intf argv[1]:script_name argv[2]:junk argv[3]:NULL
+ */
+static int run(char *argv[3], const char *param, struct in_addr *ip)
+{
+ int status;
+ char *addr = addr; /* for gcc */
+ const char *fmt = "%s %s %s" + 3;
+
+ argv[2] = (char*)param;
+
+ VDBG("%s run %s %s\n", argv[0], argv[1], argv[2]);
+
+ if (ip) {
+ addr = inet_ntoa(*ip);
+ xsetenv("ip", addr);
+ fmt -= 3;
+ }
+ bb_info_msg(fmt, argv[2], argv[0], addr);
+
+ status = wait4pid(spawn(argv + 1));
+ if (status < 0) {
+ bb_perror_msg("%s %s %s" + 3, argv[2], argv[0]);
+ return -errno;
+ }
+ if (status != 0)
+ bb_error_msg("script %s %s failed, exitcode=%d", argv[1], argv[2], status);
+ return status;
+}
+
+/**
+ * Return milliseconds of random delay, up to "secs" seconds.
+ */
+static ALWAYS_INLINE unsigned random_delay_ms(unsigned secs)
+{
+ return rand() % (secs * 1000);
+}
+
+/**
+ * main program
+ */
+int zcip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int zcip_main(int argc, char **argv)
+{
+ int state;
+ char *r_opt;
+ unsigned opts;
+
+ // ugly trick, but I want these zeroed in one go
+ struct {
+ const struct in_addr null_ip;
+ const struct ether_addr null_addr;
+ struct in_addr ip;
+ struct ifreq ifr;
+ int timeout_ms; /* must be signed */
+ unsigned conflicts;
+ unsigned nprobes;
+ unsigned nclaims;
+ int ready;
+ int verbose;
+ } L;
+#define null_ip (L.null_ip )
+#define null_addr (L.null_addr )
+#define ip (L.ip )
+#define ifr (L.ifr )
+#define timeout_ms (L.timeout_ms)
+#define conflicts (L.conflicts )
+#define nprobes (L.nprobes )
+#define nclaims (L.nclaims )
+#define ready (L.ready )
+#define verbose (L.verbose )
+
+ memset(&L, 0, sizeof(L));
+
+#define FOREGROUND (opts & 1)
+#define QUIT (opts & 2)
+ // parse commandline: prog [options] ifname script
+ // exactly 2 args; -v accumulates and implies -f
+ opt_complementary = "=2:vv:vf";
+ opts = getopt32(argv, "fqr:v", &r_opt, &verbose);
+#if !BB_MMU
+ // on NOMMU reexec early (or else we will rerun things twice)
+ if (!FOREGROUND)
+ bb_daemonize_or_rexec(0 /*was: DAEMON_CHDIR_ROOT*/, argv);
+#endif
+ // open an ARP socket
+ // (need to do it before openlog to prevent openlog from taking
+ // fd 3 (sock_fd==3))
+ xmove_fd(xsocket(AF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)), sock_fd);
+ if (!FOREGROUND) {
+ // do it before all bb_xx_msg calls
+ openlog(applet_name, 0, LOG_DAEMON);
+ logmode |= LOGMODE_SYSLOG;
+ }
+ if (opts & 4) { // -r n.n.n.n
+ if (inet_aton(r_opt, &ip) == 0
+ || (ntohl(ip.s_addr) & IN_CLASSB_NET) != LINKLOCAL_ADDR
+ ) {
+ bb_error_msg_and_die("invalid link address");
+ }
+ }
+ argc -= optind;
+ argv += optind - 1;
+
+ /* Now: argv[0]:junk argv[1]:intf argv[2]:script argv[3]:NULL */
+ /* We need to make space for script argument: */
+ argv[0] = argv[1];
+ argv[1] = argv[2];
+ /* Now: argv[0]:intf argv[1]:script argv[2]:junk argv[3]:NULL */
+#define argv_intf (argv[0])
+
+ xsetenv("interface", argv_intf);
+
+ // initialize the interface (modprobe, ifup, etc)
+ if (run(argv, "init", NULL))
+ return EXIT_FAILURE;
+
+ // initialize saddr
+ // saddr is: { u16 sa_family; u8 sa_data[14]; }
+ //memset(&saddr, 0, sizeof(saddr));
+ //TODO: are we leaving sa_family == 0 (AF_UNSPEC)?!
+ safe_strncpy(saddr.sa_data, argv_intf, sizeof(saddr.sa_data));
+
+ // bind to the interface's ARP socket
+ xbind(sock_fd, &saddr, sizeof(saddr));
+
+ // get the interface's ethernet address
+ //memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, argv_intf, sizeof(ifr.ifr_name));
+ xioctl(sock_fd, SIOCGIFHWADDR, &ifr);
+ memcpy(&eth_addr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN);
+
+ // start with some stable ip address, either a function of
+ // the hardware address or else the last address we used.
+ // we are taking low-order four bytes, as top-order ones
+ // aren't random enough.
+ // NOTE: the sequence of addresses we try changes only
+ // depending on when we detect conflicts.
+ {
+ uint32_t t = get_unaligned_u32p((uint32_t *) ((char *)&eth_addr + 2));
+ srand(t);
+ }
+ if (ip.s_addr == 0)
+ ip.s_addr = pick();
+
+ // FIXME cases to handle:
+ // - zcip already running!
+ // - link already has local address... just defend/update
+
+ // daemonize now; don't delay system startup
+ if (!FOREGROUND) {
+#if BB_MMU
+ bb_daemonize(0 /*was: DAEMON_CHDIR_ROOT*/);
+#endif
+ bb_info_msg("start, interface %s", argv_intf);
+ }
+
+ // run the dynamic address negotiation protocol,
+ // restarting after address conflicts:
+ // - start with some address we want to try
+ // - short random delay
+ // - arp probes to see if another host uses it
+ // - arp announcements that we're claiming it
+ // - use it
+ // - defend it, within limits
+ // exit if:
+ // - address is successfully obtained and -q was given:
+ // run "<script> config", then exit with exitcode 0
+ // - poll error (when does this happen?)
+ // - read error (when does this happen?)
+ // - sendto error (in arp()) (when does this happen?)
+ // - revents & POLLERR (link down). run "<script> deconfig" first
+ state = PROBE;
+ while (1) {
+ struct pollfd fds[1];
+ unsigned deadline_us;
+ struct arp_packet p;
+ int source_ip_conflict;
+ int target_ip_conflict;
+
+ fds[0].fd = sock_fd;
+ fds[0].events = POLLIN;
+ fds[0].revents = 0;
+
+ // poll, being ready to adjust current timeout
+ if (!timeout_ms) {
+ timeout_ms = random_delay_ms(PROBE_WAIT);
+ // FIXME setsockopt(sock_fd, SO_ATTACH_FILTER, ...) to
+ // make the kernel filter out all packets except
+ // ones we'd care about.
+ }
+ // set deadline_us to the point in time when we timeout
+ deadline_us = MONOTONIC_US() + timeout_ms * 1000;
+
+ VDBG("...wait %d %s nprobes=%u, nclaims=%u\n",
+ timeout_ms, argv_intf, nprobes, nclaims);
+
+ switch (safe_poll(fds, 1, timeout_ms)) {
+
+ default:
+ //bb_perror_msg("poll"); - done in safe_poll
+ return EXIT_FAILURE;
+
+ // timeout
+ case 0:
+ VDBG("state = %d\n", state);
+ switch (state) {
+ case PROBE:
+ // timeouts in the PROBE state mean no conflicting ARP packets
+ // have been received, so we can progress through the states
+ if (nprobes < PROBE_NUM) {
+ nprobes++;
+ VDBG("probe/%u %s@%s\n",
+ nprobes, argv_intf, inet_ntoa(ip));
+ arp(/* ARPOP_REQUEST, */
+ /* &eth_addr, */ null_ip,
+ &null_addr, ip);
+ timeout_ms = PROBE_MIN * 1000;
+ timeout_ms += random_delay_ms(PROBE_MAX - PROBE_MIN);
+ }
+ else {
+ // Switch to announce state.
+ state = ANNOUNCE;
+ nclaims = 0;
+ VDBG("announce/%u %s@%s\n",
+ nclaims, argv_intf, inet_ntoa(ip));
+ arp(/* ARPOP_REQUEST, */
+ /* &eth_addr, */ ip,
+ &eth_addr, ip);
+ timeout_ms = ANNOUNCE_INTERVAL * 1000;
+ }
+ break;
+ case RATE_LIMIT_PROBE:
+ // timeouts in the RATE_LIMIT_PROBE state mean no conflicting ARP packets
+ // have been received, so we can move immediately to the announce state
+ state = ANNOUNCE;
+ nclaims = 0;
+ VDBG("announce/%u %s@%s\n",
+ nclaims, argv_intf, inet_ntoa(ip));
+ arp(/* ARPOP_REQUEST, */
+ /* &eth_addr, */ ip,
+ &eth_addr, ip);
+ timeout_ms = ANNOUNCE_INTERVAL * 1000;
+ break;
+ case ANNOUNCE:
+ // timeouts in the ANNOUNCE state mean no conflicting ARP packets
+ // have been received, so we can progress through the states
+ if (nclaims < ANNOUNCE_NUM) {
+ nclaims++;
+ VDBG("announce/%u %s@%s\n",
+ nclaims, argv_intf, inet_ntoa(ip));
+ arp(/* ARPOP_REQUEST, */
+ /* &eth_addr, */ ip,
+ &eth_addr, ip);
+ timeout_ms = ANNOUNCE_INTERVAL * 1000;
+ }
+ else {
+ // Switch to monitor state.
+ state = MONITOR;
+ // link is ok to use earlier
+ // FIXME update filters
+ run(argv, "config", &ip);
+ ready = 1;
+ conflicts = 0;
+ timeout_ms = -1; // Never timeout in the monitor state.
+
+ // NOTE: all other exit paths
+ // should deconfig ...
+ if (QUIT)
+ return EXIT_SUCCESS;
+ }
+ break;
+ case DEFEND:
+ // We won! No ARP replies, so just go back to monitor.
+ state = MONITOR;
+ timeout_ms = -1;
+ conflicts = 0;
+ break;
+ default:
+ // Invalid, should never happen. Restart the whole protocol.
+ state = PROBE;
+ ip.s_addr = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ break;
+ } // switch (state)
+ break; // case 0 (timeout)
+
+ // packets arriving, or link went down
+ case 1:
+ // We need to adjust the timeout in case we didn't receive
+ // a conflicting packet.
+ if (timeout_ms > 0) {
+ unsigned diff = deadline_us - MONOTONIC_US();
+ if ((int)(diff) < 0) {
+ // Current time is greater than the expected timeout time.
+ // Should never happen.
+ VDBG("missed an expected timeout\n");
+ timeout_ms = 0;
+ } else {
+ VDBG("adjusting timeout\n");
+ timeout_ms = (diff / 1000) | 1; /* never 0 */
+ }
+ }
+
+ if ((fds[0].revents & POLLIN) == 0) {
+ if (fds[0].revents & POLLERR) {
+ // FIXME: links routinely go down;
+ // this shouldn't necessarily exit.
+ bb_error_msg("iface %s is down", argv_intf);
+ if (ready) {
+ run(argv, "deconfig", &ip);
+ }
+ return EXIT_FAILURE;
+ }
+ continue;
+ }
+
+ // read ARP packet
+ if (safe_read(sock_fd, &p, sizeof(p)) < 0) {
+ bb_perror_msg_and_die(bb_msg_read_error);
+ }
+ if (p.eth.ether_type != htons(ETHERTYPE_ARP))
+ continue;
+#ifdef DEBUG
+ {
+ struct ether_addr *sha = (struct ether_addr *) p.arp.arp_sha;
+ struct ether_addr *tha = (struct ether_addr *) p.arp.arp_tha;
+ struct in_addr *spa = (struct in_addr *) p.arp.arp_spa;
+ struct in_addr *tpa = (struct in_addr *) p.arp.arp_tpa;
+ VDBG("%s recv arp type=%d, op=%d,\n",
+ argv_intf, ntohs(p.eth.ether_type),
+ ntohs(p.arp.arp_op));
+ VDBG("\tsource=%s %s\n",
+ ether_ntoa(sha),
+ inet_ntoa(*spa));
+ VDBG("\ttarget=%s %s\n",
+ ether_ntoa(tha),
+ inet_ntoa(*tpa));
+ }
+#endif
+ if (p.arp.arp_op != htons(ARPOP_REQUEST)
+ && p.arp.arp_op != htons(ARPOP_REPLY))
+ continue;
+
+ source_ip_conflict = 0;
+ target_ip_conflict = 0;
+
+ if (memcmp(p.arp.arp_spa, &ip.s_addr, sizeof(struct in_addr)) == 0
+ && memcmp(&p.arp.arp_sha, &eth_addr, ETH_ALEN) != 0
+ ) {
+ source_ip_conflict = 1;
+ }
+ if (p.arp.arp_op == htons(ARPOP_REQUEST)
+ && memcmp(p.arp.arp_tpa, &ip.s_addr, sizeof(struct in_addr)) == 0
+ && memcmp(&p.arp.arp_tha, &eth_addr, ETH_ALEN) != 0
+ ) {
+ target_ip_conflict = 1;
+ }
+
+ VDBG("state = %d, source ip conflict = %d, target ip conflict = %d\n",
+ state, source_ip_conflict, target_ip_conflict);
+ switch (state) {
+ case PROBE:
+ case ANNOUNCE:
+ // When probing or announcing, check for source IP conflicts
+ // and other hosts doing ARP probes (target IP conflicts).
+ if (source_ip_conflict || target_ip_conflict) {
+ conflicts++;
+ if (conflicts >= MAX_CONFLICTS) {
+ VDBG("%s ratelimit\n", argv_intf);
+ timeout_ms = RATE_LIMIT_INTERVAL * 1000;
+ state = RATE_LIMIT_PROBE;
+ }
+
+ // restart the whole protocol
+ ip.s_addr = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ }
+ break;
+ case MONITOR:
+ // If a conflict, we try to defend with a single ARP probe.
+ if (source_ip_conflict) {
+ VDBG("monitor conflict -- defending\n");
+ state = DEFEND;
+ timeout_ms = DEFEND_INTERVAL * 1000;
+ arp(/* ARPOP_REQUEST, */
+ /* &eth_addr, */ ip,
+ &eth_addr, ip);
+ }
+ break;
+ case DEFEND:
+ // Well, we tried. Start over (on conflict).
+ if (source_ip_conflict) {
+ state = PROBE;
+ VDBG("defend conflict -- starting over\n");
+ ready = 0;
+ run(argv, "deconfig", &ip);
+
+ // restart the whole protocol
+ ip.s_addr = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ }
+ break;
+ default:
+ // Invalid, should never happen. Restart the whole protocol.
+ VDBG("invalid state -- starting over\n");
+ state = PROBE;
+ ip.s_addr = pick();
+ timeout_ms = 0;
+ nprobes = 0;
+ nclaims = 0;
+ break;
+ } // switch state
+ break; // case 1 (packets arriving)
+ } // switch poll
+ } // while (1)
+#undef argv_intf
+}