From 06e9565b6c365668dafeef1fdc0e60c9a1154623 Mon Sep 17 00:00:00 2001 From: Glenn L McGrath Date: Sun, 9 Feb 2003 06:51:14 +0000 Subject: [PATCH] New applet, inetd, make httpd features more configurable, update authors, last_patch_80 from Vladimir N. Oleynik --- AUTHORS | 7 +- docs/busybox_footer.pod | 12 +- docs/busybox_header.pod | 19 +- include/applets.h | 3 + include/usage.h | 8 + networking/Config.in | 113 ++- networking/Makefile.in | 1 + networking/httpd.c | 1948 ++++++++++++++++++++++++--------------- networking/inetd.c | 1280 +++++++++++++++++++++++++ 9 files changed, 2636 insertions(+), 755 deletions(-) create mode 100644 networking/inetd.c diff --git a/AUTHORS b/AUTHORS index 1da11bbae..c965d3c72 100644 --- a/AUTHORS +++ b/AUTHORS @@ -44,6 +44,9 @@ Magnus Damm Larry Doolittle pristine source directory compilation, lots of patches and fixes. +Glenn Engel + httpd + Gennady Feldman Sysklogd (single threaded syslogd, IPC Circular buffer support, logread), various fixes. @@ -67,8 +70,8 @@ Glenn McGrath ar, dpkg, dpkg-deb Vladimir Oleynik - cmdedit; xargs(current); - ports: ash, crond, fdisk, stty, traceroute, telnetd, top; + cmdedit; xargs(current), httpd(current); + ports: ash, crond, fdisk, inetd, stty, traceroute, telnetd, top; locale, various fixes and irreconcilable critic of everything not perfect. diff --git a/docs/busybox_footer.pod b/docs/busybox_footer.pod index 7c53cced5..f72bd217a 100644 --- a/docs/busybox_footer.pod +++ b/docs/busybox_footer.pod @@ -113,8 +113,8 @@ Glenn McGrath Vladimir Oleynik - cmdedit, xargs(current); - ports: ash, crond, fdisk, stty, traceroute, telnetd, top; + cmdedit, xargs(current), httpd(current); + ports: ash, crond, fdisk, inetd, stty, traceroute, telnetd, top; locale, various fixes and irreconcilable critic of everything not perfect. @@ -166,6 +166,12 @@ Enrique Zanardi tarcat (since removed), loadkmap, various fixes, Debian maintenance +=for html
+ +Glenn Engel + + httpd + =cut -# $Id: busybox_footer.pod,v 1.9 2002/11/26 22:00:19 bug1 Exp $ +# $Id: busybox_footer.pod,v 1.10 2003/02/09 06:51:12 bug1 Exp $ diff --git a/docs/busybox_header.pod b/docs/busybox_header.pod index a684cc194..af37b5a59 100644 --- a/docs/busybox_header.pod +++ b/docs/busybox_header.pod @@ -60,15 +60,16 @@ chmod, chown, chroot, chvt, clear, cmp, cp, cpio, crond, crontab, cut, date, dc, dd, deallocvt, deluser, df, dirname, dmesg, dos2unix, dpkg, dpkg-deb, du, dumpkmap, dutmp, echo, expr, false, fbset, fdflush, fdisk, find, free, freeramdisk, fsck.minix, getopt, getty, grep, gunzip, gzip, -halt, head, hostid, hostname, id, ifconfig, init, insmod, kill, killall, -klogd, length, ln, loadacm, loadfont, loadkmap, logger, logname, ls, lsmod, -makedevs, md5sum, mkdir, mkfifo, mkfs.minix, mknod, mkswap, mktemp, more, -mount, mt, mv, nc, netstat, nslookup, ping, pivot_root, poweroff, printf, -ps, pwd, rdate, readlink, reboot, renice, reset, rm, rmdir, rmmod, route, -rpm2cpio, sed, setkeycodes, sh, sleep, sort, stty, swapoff, swapon, sync, -syslogd, tail, tar, tee, telnet, telnetd, test, tftp, time, top, touch, tr, -true, tty, umount, uname, uniq, unix2dos, update, uptime, usleep, uudecode, -uuencode, watchdog, wc, wget, which, whoami, xargs, yes, zcat, [ +halt, head, hostid, hostname, httpd, id, ifconfig, inetd, init, insmod, +kill, killall, klogd, length, ln, loadacm, loadfont, loadkmap, logger, +logname, ls, lsmod, makedevs, md5sum, mkdir, mkfifo, mkfs.minix, mknod, +mkswap, mktemp, more, mount, mt, mv, nc, netstat, nslookup, ping, +pivot_root, poweroff, printf, ps, pwd, rdate, readlink, reboot, renice, +reset, rm, rmdir, rmmod, route, rpm2cpio, sed, setkeycodes, sh, sleep, +sort, stty, swapoff, swapon, sync, syslogd, tail, tar, tee, telnet, +telnetd, test, tftp, time, top, touch, tr, true, tty, umount, uname, uniq, +unix2dos, update, uptime, usleep, uudecode, uuencode, watchdog, wc, wget, +which, whoami, xargs, yes, zcat, [ =over 4 diff --git a/include/applets.h b/include/applets.h index 9e88f5044..5474de581 100644 --- a/include/applets.h +++ b/include/applets.h @@ -265,6 +265,9 @@ #ifdef CONFIG_IFUPDOWN APPLET(ifup, ifupdown_main, _BB_DIR_SBIN, _BB_SUID_NEVER) #endif +#ifdef CONFIG_INETD + APPLET(inetd, inetd_main, _BB_DIR_USR_SBIN, _BB_SUID_NEVER) +#endif #ifdef CONFIG_INIT APPLET(init, init_main, _BB_DIR_SBIN, _BB_SUID_NEVER) #endif diff --git a/include/usage.h b/include/usage.h index d2fb19372..55693c61e 100644 --- a/include/usage.h +++ b/include/usage.h @@ -915,6 +915,14 @@ "\t-m\tdon't run any mappings\n" \ "\t-f\tforce de/configuration\n" +#define inetd_trivial_usage \ + "[-q len] [conf]" +#define inetd_full_usage \ + "Usage: [-q len] [conf]\n\n" \ + "Option:\n" \ + "\t-q\tSets the size of the socket listen queue to\n" \ + "the specified value. Default is 128." + #define init_trivial_usage \ "" #define init_full_usage \ diff --git a/networking/Config.in b/networking/Config.in index 22676c085..527aebbac 100644 --- a/networking/Config.in +++ b/networking/Config.in @@ -41,6 +41,15 @@ config CONFIG_HTTPD help Serve web pages via an HTTP server. +config CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + bool " Support call from inetd only" + default n + depends on CONFIG_HTTPD + help + This option disabling uid and port options for httpd applet + and more others reducing size moments, but require + internet superserver daemon for usaging. + config CONFIG_FEATURE_HTTPD_BASIC_AUTH bool " Enable Basic Authentication and IP address checking" default n @@ -49,6 +58,66 @@ config CONFIG_FEATURE_HTTPD_BASIC_AUTH Utilizes /etc/httpd.conf for security settings allowing ip address filtering and basic authentication on a per url basis. +config CONFIG_FEATURE_HTTPD_CGI + bool " Enable support Common Gateway Interface" + default n + depends on CONFIG_HTTPD + help + Disable this for do very small module + +config CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + bool " Enable support reload global config file after hup signaled" + default n + depends on CONFIG_HTTPD + help + Disable this for do very small module + +config CONFIG_FEATURE_HTTPD_SETUID + bool " Enable support -u user option" + default n + depends on CONFIG_HTTPD + help + Require for drop privilegies after bind() to privilegies port + +config CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + bool " Enable support load from config file mime types" + default n + depends on CONFIG_HTTPD + help + After set this you can adding or change mime types from file + suffixes in config files + +config CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + bool " Enable support set eviroment REMOTE_PORT" + default n + depends on CONFIG_FEATURE_HTTPD_CGI + help + After set this your CGI script can know own remote port connecting + +config CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV + bool " Enable support nonstandart httpd feature set CGI_var=value" + default n + depends on CONFIG_FEATURE_HTTPD_CGI + help + After set this your CGI script can have trivial parse getted vars + +config CONFIG_FEATURE_HTTPD_DECODE_URL_STR + bool " Support nonstandart httpd feature decode URL to stdout" + default n + depends on CONFIG_HTTPD + help + After set this your can decode URL from -d argument to stdout, + example -d "Hello%20World" as "Hello World" + +config CONFIG_FEATURE_HTTPD_ENCODE_URL_STR + bool " Support nonstandart httpd feature encode argument to URL" + default n + depends on CONFIG_HTTPD + help + After set this your can encode from -d argument to stdout as URL, + example -e "" as "%3CHello%20World%3E" + + config CONFIG_IFCONFIG bool "ifconfig" default n @@ -132,6 +201,48 @@ config CONFIG_FEATURE_IFUPDOWN_MAPPING This enables support for the "mapping" stanza, unless you have a weird network setup you dont need it. +config CONFIG_INETD + bool "inetd" + default n + help + Internet superserver daemon + +config CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO + bool " Support echo service" + default y + depends on CONFIG_INETD + help + Echo received data internal inetd service + +config CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD + bool " Support discard service" + default y + depends on CONFIG_INETD + help + Internet /dev/null internal inetd service + +config CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME + bool " Support time service" + default y + depends on CONFIG_INETD + help + Return 32 bit time since 1900 internal inetd service + +config CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME + bool " Support daytime service" + default y + depends on CONFIG_INETD + help + Return human-readable time internal inetd service + +config CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN + bool " Support chargen service" + default y + depends on CONFIG_INETD + help + Familiar character generator internal inetd service + + config CONFIG_IP bool "ip" default n @@ -316,7 +427,7 @@ config CONFIG_TELNETD Please submit a patch to add help text for this item. config CONFIG_FEATURE_TELNETD_INETD - bool " Use inetd" + bool " Support call from inetd only" default n depends on CONFIG_TELNETD help diff --git a/networking/Makefile.in b/networking/Makefile.in index 12fc8ddbd..95709ee42 100644 --- a/networking/Makefile.in +++ b/networking/Makefile.in @@ -30,6 +30,7 @@ NETWORKING-$(CONFIG_HOSTNAME) += hostname.o NETWORKING-$(CONFIG_HTTPD) += httpd.o NETWORKING-$(CONFIG_IFCONFIG) += ifconfig.o NETWORKING-$(CONFIG_IFUPDOWN) += ifupdown.o +NETWORKING-$(CONFIG_INETD) += inetd.o NETWORKING-$(CONFIG_IP) += ip.o NETWORKING-$(CONFIG_IPCALC) += ipcalc.o NETWORKING-$(CONFIG_IPADDR) += ipaddr.o diff --git a/networking/httpd.c b/networking/httpd.c index bceb89ba3..e61518c20 100644 --- a/networking/httpd.c +++ b/networking/httpd.c @@ -1,8 +1,10 @@ /* * httpd implementation for busybox * - * Copyright (C) 2002 Glenn Engel + * Copyright (C) 2002,2003 Glenn Engel + * Copyright (C) 2003 Vladimir Oleynik * + * simplify patch stolen from libbb without using strdup * * 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 @@ -20,100 +22,252 @@ * ***************************************************************************** * - * Typical usage: - * cd /var/www - * httpd - * This is equivalent to - * cd /var/www - * httpd -p 80 -c /etc/httpd.conf -r "Web Server Authentication" + * 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 contains "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. If url args * are included in the url or as a post, the args are placed into decoded * environment variables. e.g. /cgi-bin/setup?foo=Hello%20World will set - * the $CGI_foo environment variable to "Hello World". + * the $CGI_foo environment variable to "Hello World" while + * CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV enabled. * * The server 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 ""` # encode as "<Hello World>" + * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World" + * bar=`httpd -e ""` # encode as "%3CHello%20World%3E" * * httpd.conf has the following format: - -ip:10.10. # Allow any address that begins with 10.10. -ip:172.20. # Allow 172.20.x.x -ip:127.0.0.1 # Allow local loopback connections -/cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin -/:admin:setup # Require user admin, pwd setup on urls starting with / - * - * To open up the server: - * ip:* # Allow any IP address - * /:* # no password required for urls starting with / (all) - * - * Processing of the file stops on the first sucessful match. If the file - * is not found, the server is assumed to be wide open. - * - ***************************************************************************** - * - * Desired enhancements: - * cache httpd.conf - * support tinylogin - * +A:172.20. # Allow any address that begins with 172.20 +A:10.10. # Allow any address that begins with 10.10. +A:10.10 # Allow any address that previous set and 10.100-109.X.X +A:127.0.0.1 # Allow local loopback connections +D:* # Deny from other IP connections +/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 + +A shortes path and D:from[^*] automaticaly sorting to top. +All longest path can`t reset user:password if shorted protect setted. + +A/D may be as a/d or allow/deny - first char case unsensitive parsed only. + +Each subdir can have config file. +For protect as user:pass current subdir and subpathes set from subdir config: +/:user:pass +if not, other subpathes for give effect must have path from httpd root +/current_subdir_path_from_httpd_root/subpath:user:pass + +The Deny/Allow IP logic: + + 1. Allow all: +The config don`t set D: lines + + 2. Allow from setted only: +see the begin format example + + 3. Set deny, allow from other: +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:* # allow from other, this line not strongly require + + A global and subdirs config merging logic: +allow rules reducing, deny lines strongled. + The algorithm combinations: + + 4. If current config have +A:from +D:* + subdir config A: lines skiping, D:from - moving top + + 5. If current config have +D:from +A:* + result config: +D:from current +D:from subdir +A:from subdir + and seting D:* if subdir config have this + + If -c don`t setted, used httpd root config, else httpd root config skiped. + Exited with fault if can`t open start config. + For set wide open server, use -c /dev/null ;=) */ + #include #include /* for isspace */ -#include /* for varargs */ -#include /* for strerror */ +#include #include /* for malloc */ #include -#include #include /* for close */ #include #include #include /* for connect and socket*/ #include /* for sockaddr_in */ -#include +#include #include #include -#include +#include /* for open modes */ +#include "busybox.h" + -static const char httpdVersion[] = "busybox httpd/1.13 3-Jan-2003"; +static const char httpdVersion[] = "busybox httpd/1.20 31-Jan-2003"; +static const char default_patch_httpd_conf[] = "/etc"; +static const char httpd_conf[] = "httpd.conf"; +static const char home[] = "/www"; -// #define DEBUG 1 -#ifndef HTTPD_STANDALONE -#include -#include // Note: xfuncs are not used because we want the server to keep running // if something bad happens due to a malformed user request. -// As a result, all memory allocation is checked rigorously -#else -/* standalone */ +// As a result, all memory allocation after daemonize +// is checked rigorously + +//#define DEBUG 1 + +/* Configure options, disabled by default as nonstandart httpd feature */ +//#define CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV +//#define CONFIG_FEATURE_HTTPD_ENCODE_URL_STR +//#define CONFIG_FEATURE_HTTPD_DECODE_URL_STR + +/* disabled as not necessary feature */ +//#define CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV +//#define CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +//#define CONFIG_FEATURE_HTTPD_SETUID +//#define CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + +/* If seted this you can use this server from internet superserver only */ +//#define CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + +/* You can use this server as standalone, require libbb.a for linking */ +//#define HTTPD_STANDALONE + +/* Config options, disable this for do very small module */ +//#define CONFIG_FEATURE_HTTPD_CGI +//#define CONFIG_FEATURE_HTTPD_BASIC_AUTH + +#ifdef HTTPD_STANDALONE +/* standalone, enable all features */ +#undef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY +/* unset config option for remove warning as redefined */ +#undef CONFIG_FEATURE_HTTPD_BASIC_AUTH +#undef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV +#undef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR +#undef CONFIG_FEATURE_HTTPD_DECODE_URL_STR +#undef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV +#undef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +#undef CONFIG_FEATURE_HTTPD_CGI +#undef CONFIG_FEATURE_HTTPD_SETUID +#undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP +/* enable all features now */ #define CONFIG_FEATURE_HTTPD_BASIC_AUTH -void show_usage() +#define CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV +#define CONFIG_FEATURE_HTTPD_ENCODE_URL_STR +#define CONFIG_FEATURE_HTTPD_DECODE_URL_STR +#define CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV +#define CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +#define CONFIG_FEATURE_HTTPD_CGI +#define CONFIG_FEATURE_HTTPD_SETUID +#define CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + +/* require from libbb.a for linking */ +const char *applet_name = "httpd"; + +void show_usage(void) { - fprintf(stderr,"Usage: httpd [-p ] [-c configFile] [-d/-e ] [-r realm]\n"); + fprintf(stderr, "Usage: %s [-p ] [-c configFile] [-d/-e ] " + "[-r realm] [-u user]\n", applet_name); + exit(1); } #endif -/* minimal global vars for busybox */ -#ifndef ENVSIZE -#define ENVSIZE 50 +#ifdef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY +#undef CONFIG_FEATURE_HTTPD_SETUID /* use inetd user.group config settings */ +#undef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP /* so is not daemon */ +/* inetd set stderr to accepted socket and we can`t true see debug messages */ +#undef DEBUG +#endif + +/* CGI environ size */ +#ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV +#define ENVSIZE 50 /* set max 35 CGI_variable */ +#else +#define ENVSIZE 15 /* minimal requires */ #endif -int debugHttpd; -static char **envp; -static int envCount; -static char *realm = "Web Server Authentication"; -static char *configFile; + +#define MAX_POST_SIZE (64*1024) /* 64k. Its Small? May be ;) */ + +#define MAX_MEMORY_BUFF 8192 /* IO buffer */ + +typedef struct HT_ACCESS { + char *after_colon; + struct HT_ACCESS *next; + char before_colon[1]; /* really bigger, must last */ +} Htaccess; + +typedef struct +{ +#ifdef CONFIG_FEATURE_HTTPD_CGI + char *envp[ENVSIZE+1]; + int envCount; +#endif + char buf[MAX_MEMORY_BUFF]; + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + const char *realm; +#endif + const char *configFile; + + char rmt_ip[16]; /* for set env REMOTE_ADDR */ + unsigned port; /* server initial port and for + set env REMOTE_PORT */ + + const char *found_mime_type; + off_t ContentLength; /* -1 - unknown */ + time_t last_mod; +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + int accepted_socket; +#define a_c_r config->accepted_socket +#define a_c_w config->accepted_socket + int debugHttpd; /* if seted, don`t stay daemon */ +#else +#define a_c_r 0 +#define a_c_w 1 +#endif + Htaccess *Httpd_conf_parsed; +} HttpdConfig; + +static HttpdConfig *config; + +static const char request_GET[] = "GET"; /* size algorithic optimize */ static const char* const suffixTable [] = { +/* Warning: shorted equalent 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", - 0,0 + ".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 + 0, "application/octet-stream" /* default */ }; typedef enum @@ -121,9 +275,10 @@ typedef enum HTTP_OK = 200, HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ HTTP_NOT_FOUND = 404, + HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ + HTTP_BAD_REQUEST = 400, /* malformed syntax */ + HTTP_FORBIDDEN = 403, HTTP_INTERNAL_SERVER_ERROR = 500, - HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ - HTTP_BAD_REQUEST = 400, /* malformed syntax */ #if 0 /* future use */ HTTP_CONTINUE = 100, HTTP_SWITCHING_PROTOCOLS = 101, @@ -136,7 +291,6 @@ typedef enum HTTP_MOVED_TEMPORARILY = 302, HTTP_NOT_MODIFIED = 304, HTTP_PAYMENT_REQUIRED = 402, - HTTP_FORBIDDEN = 403, HTTP_BAD_GATEWAY = 502, HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ HTTP_RESPONSE_SETSIZE=0xffffffff @@ -152,15 +306,16 @@ typedef struct static const HttpEnumString httpResponseNames[] = { { HTTP_OK, "OK" }, - { HTTP_NOT_IMPLEMENTED, "Not Implemented", + { HTTP_NOT_IMPLEMENTED, "Not Implemented", "The requested method is not recognized by this server." }, { HTTP_UNAUTHORIZED, "Unauthorized", "" }, { HTTP_NOT_FOUND, "Not Found", "The requested URL was not found on this server." }, + { HTTP_BAD_REQUEST, "Bad Request" , + "Unsupported method." }, + { HTTP_FORBIDDEN, "Forbidden", "" }, { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error" "Internal Server Error" }, - { HTTP_BAD_REQUEST, "Bad Request" , - "Unsupported method.\n" }, #if 0 { HTTP_CREATED, "Created" }, { HTTP_ACCEPTED, "Accepted" }, @@ -169,12 +324,247 @@ static const HttpEnumString httpResponseNames[] = { { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, { HTTP_MOVED_TEMPORARILY, "Moved Temporarily" }, { HTTP_NOT_MODIFIED, "Not Modified" }, - { HTTP_FORBIDDEN, "Forbidden", "" }, { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, #endif }; + +static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT"; +static const char Content_length[] = "Content-length:"; + + +/* + * sotring to: + * .ext:mime/type + * /path:user:pass + * /path/subdir:user:pass + * D:from + * A:from + * D:* + */ +static int conf_sort(const void *p1, const void *p2) +{ + const Htaccess *cl1 = *(const Htaccess **)p1; + const Htaccess *cl2 = *(const Htaccess **)p2; + char c1 = cl1->before_colon[0]; + char c2 = cl2->before_colon[0]; + int test; + +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + /* .ext line up before other lines for simlify algorithm */ + test = c2 == '.'; + if(c1 == '.') + return -(!test); + if(test) + return test; +#endif + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + test = c1 == '/'; + /* /path line up before A/D lines for simlify algorithm */ + if(test) { + if(c2 != '/') + return -test; + /* a shortes path with user:pass must be first */ + return strlen(cl1->before_colon) - strlen(cl2->before_colon); + } else if(c2 == '/') + return !test; +#endif + + /* D:from must move top */ + test = c2 == 'D' && cl2->after_colon[0] != 0; + if(c1 == 'D' && cl1->after_colon[0] != 0) { + return -(!test); + } + if(test) + return test; + + /* next lines - A:from */ + test = c2 == 'A' && cl2->after_colon[0] != 0; + if(c1 == 'A' && cl1->after_colon[0] != 0) { + return -(!test); + } + if(test) + return test; + + /* end lines - D:* */ + test = c2 == 'D' && cl2->after_colon[0] == 0; + if(c1 == 'D' && cl1->after_colon[0] == 0) { + return -(!test); + } +#ifdef DEBUG + if(!test) + error_msg_and_die("sort: can`t found compares!"); +#endif + return test; +} + + +/* flag */ +#define FIRST_PARSE 0 +#define SUBDIR_PARSE 1 +#define SIGNALED_PARSE 2 + +static void parse_conf(const char *path, int flag) +{ +#define bc cur->before_colon[0] +#define ac cur->after_colon[0] + FILE *f; + Htaccess *prev; + Htaccess *cur; + const char *cf = config->configFile; + char buf[80]; + char *p0 = NULL; + int deny_all = 0; /* default A:* */ + int n = 0; /* count config lines */ + + if(flag == SUBDIR_PARSE || cf == NULL) { + cf = p0 = alloca(strlen(path) + sizeof(httpd_conf) + 2); + if(p0 == NULL) { + if(flag == FIRST_PARSE) + error_msg_and_die(memory_exhausted); + return; + } + sprintf(p0, "%s/%s", path, httpd_conf); + } + + while((f = fopen(cf, "r")) == NULL) { + if(flag != FIRST_PARSE) + return; /* subdir config not found */ + if(p0 == NULL) /* if -c option gived */ + perror_msg_and_die("%s", cf); + p0 = NULL; + cf = httpd_conf; /* set -c ./httpd_conf */ + } + + prev = config->Httpd_conf_parsed; + if(flag != SUBDIR_PARSE) { + /* free previous setuped */ + while( prev ) { + cur = prev; + prev = cur->next; + free(cur); + } + config->Httpd_conf_parsed = prev; /* eq NULL */ + } else { + /* parse previous IP logic for merge */ + for(cur = prev; cur; cur = cur->next) { + if(bc == 'D' && ac == 0) + deny_all++; + n++; + /* find last for set prev->next of merging */ + if(cur != prev) + prev = prev->next; + } + } + + /* This could stand some work */ + while ( (p0 = fgets(buf, 80, f)) != NULL) { + char *p; + char *colon; + + for(p = colon = p0; *p; p++) { + if(*p == '#') { + *p = 0; + break; + } + if(isspace(*p)) { + if(p != p0) { + *p = 0; + break; + } + p0++; + } + else if(*p == ':' && colon <= p0) + colon = p; + } + + /* test for empty or strange line */ + if (colon <= p0 || colon[1] == 0) + continue; + if(colon[1] == '*') + colon[1] = 0; /* Allow all */ + if(*p0 == 'a') + *p0 = 'A'; + if(*p0 == 'd') + *p0 = 'D'; + if(*p0 != 'A' && *p0 != 'D' +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + && *p0 != '/' +#endif +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + && *p0 != '.' +#endif + ) + continue; +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + if(*p0 == '/' && colon[1] == 0) { + /* skip /path:* */ + continue; + } +#endif + + if(*p0 == 'A' || *p0 == 'D') { + if(colon[1] == 0) { + if(*p0 == 'A' || deny_all != 0) + continue; /* skip default A:* or double D:* */ + } + if(deny_all != 0 && *p0 == 'A') + continue; // if previous setted rule D:* skip all subdir A: + } + + /* storing current config line */ + cur = calloc(1, sizeof(Htaccess) + (p-p0)); + if(cur) { + if(*(colon-1) == '/' && (colon-1) != p0) + colon[-1] = 0; // remove last / from /path/ + cur->after_colon = strcpy(cur->before_colon, p0); + cur->after_colon += (colon-p0); + *cur->after_colon++ = 0; + + if(prev == NULL) { + /* first line */ + config->Httpd_conf_parsed = prev = cur; + } else { + prev->next = cur; + prev = cur; + } + n++; + } + } + fclose(f); + + if(n > 1) { + /* sorting conf lines */ + Htaccess ** pcur; /* array for qsort */ + + prev = config->Httpd_conf_parsed; + pcur = alloca((n + 1) * sizeof(Htaccess *)); + if(pcur == NULL) { + if(flag == FIRST_PARSE) + error_msg_and_die(memory_exhausted); + return; + } + n = 0; + for(cur = prev; cur; cur = cur->next) + pcur[n++] = cur; + pcur[n] = NULL; + + qsort(pcur, n, sizeof(Htaccess *), conf_sort); + + /* storing sorted config */ + config->Httpd_conf_parsed = *pcur; + for(cur = *pcur; cur; cur = cur->next) { +#ifdef DEBUG + error_msg("%s: %s:%s", cf, cur->before_colon, cur->after_colon); +#endif + cur->next = *++pcur; + } + } +} + +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR /**************************************************************************** * > $Function: encodeString() @@ -197,20 +587,22 @@ 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 = (char*)malloc(len*5 +1); + char *out = malloc(len*5 +1); char *p=out; char ch; + if (!out) return ""; - while ((ch = *string++)) - { + while ((ch = *string++)) { // very simple check for what to encode if (isalnum(ch)) *p++ = ch; - else p += sprintf(p,"&#%d", (unsigned char) ch); + else p += sprintf(p, "&#%d", (unsigned char) ch); } *p=0; return out; } +#endif /* CONFIG_FEATURE_HTTPD_ENCODE_URL_STR */ +#ifdef CONFIG_FEATURE_HTTPD_DECODE_URL_STR /**************************************************************************** * > $Function: decodeString() @@ -236,12 +628,11 @@ static char *decodeString(char *string) char *ptr = string; while (*ptr) { - if (*ptr == '+') { *string++ = ' '; ptr++; } + if (*ptr == '+') { *string++ = ' '; ptr++; } else if (*ptr != '%') *string++ = *ptr++; - else - { + else { unsigned int value; - sscanf(ptr+1,"%2X",&value); + sscanf(ptr+1, "%2X", &value); *string++ = value; ptr += 3; } @@ -249,8 +640,10 @@ static char *decodeString(char *string) *string = '\0'; return orig; } +#endif /* CONFIG_FEATURE_HTTPD_DECODE_URL_STR */ +#ifdef CONFIG_FEATURE_HTTPD_CGI /**************************************************************************** * > $Function: addEnv() @@ -260,28 +653,47 @@ static char *decodeString(char *string) * environment settings passed to the cgi execution script. * * $Parameters: - * (char *) name . . . The environment variable name. - * (char *) value . . The value to which the env variable is set. + * (char *) name_before_underline - The first part environment variable name. + * (char *) name_after_underline - The second part environment variable name. + * (char *) value . . The value to which the env variable is set. * - * $Return: (void) + * $Return: (void) * * $Errors: Silently returns if the env runs out of space to hold the new item * ****************************************************************************/ -static void addEnv(const char *name, const char *value) +static void addEnv(const char *name_before_underline, + const char *name_after_underline, const char *value) { char *s; - if (envCount >= ENVSIZE) return; - if (!value) value = ""; - s=(char*)malloc(strlen(name)+strlen(value)+2); - if (s) - { - sprintf(s,"%s=%s",name, value); - envp[envCount++]=s; - envp[envCount]=0; + + if (config->envCount >= ENVSIZE) + return; + if (!value) + value = ""; + s = malloc(strlen(name_before_underline) + strlen(name_after_underline) + + strlen(value) + 3); + if (s) { + const char *underline = *name_after_underline ? "_" : ""; + + sprintf(s,"%s%s%s=%s", name_before_underline, underline, + name_after_underline, value); + config->envp[config->envCount++] = s; + config->envp[config->envCount] = 0; } } +/* set environs SERVER_PORT and REMOTE_PORT */ +static void addEnvPort(const char *port_name) +{ + char buf[16]; + + sprintf(buf, "%u", config->port); + addEnv(port_name, "PORT", buf); +} +#endif /* CONFIG_FEATURE_HTTPD_CGI */ + +#ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV /**************************************************************************** * > $Function: addEnvCgi @@ -289,7 +701,8 @@ static void addEnv(const char *name, const char *value) * $Description: Create environment variables given a URL encoded arg list. * For each variable setting the URL encoded arg list, create a corresponding * environment variable. URL encoded arguments have the form - * name1=value1&name2=value2&name3=value3 + * name1=value1&name2=value2&name3=&ignores + * from this example, name3 set empty value, tail without '=' skiping * * $Parameters: * (char *) pargs . . . . A pointer to the URL encoded arguments. @@ -302,65 +715,29 @@ static void addEnv(const char *name, const char *value) static void addEnvCgi(const char *pargs) { char *args; + char *memargs; if (pargs==0) return; - + /* args are a list of name=value&name2=value2 sequences */ - args = strdup(pargs); - while (args && *args) - { - char *sep; - char *name=args; - char *value=strchr(args,'='); - char *cginame; - if (!value) break; - *value++=0; - sep=strchr(value,'&'); - if (sep) - { - *sep=0; - args=sep+1; - } - else - { - sep = value + strlen(value); - args = 0; /* no more */ - } - cginame=(char*)malloc(strlen(decodeString(name))+5); - if (!cginame) break; - sprintf(cginame,"CGI_%s",name); - addEnv(cginame,decodeString(value)); - free(cginame); + memargs = args = strdup(pargs); + while (args && *args) { + const char *name = args; + char *value = strchr(args, '='); + + if (!value) /* &XXX without '=' */ + break; + *value++ = 0; + args = strchr(value, '&'); + if (args) + *args++ = 0; + addEnv("CGI", name, decodeString(value)); } + free(memargs); } +#endif /* CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV */ -#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH -static const unsigned char base64ToBin[] = { - 255 /* ' ' */, 255 /* '!' */, 255 /* '"' */, 255 /* '#' */, - 1 /* '$' */, 255 /* '%' */, 255 /* '&' */, 255 /* ''' */, - 255 /* '(' */, 255 /* ')' */, 255 /* '*' */, 62 /* '+' */, - 255 /* ',' */, 255 /* '-' */, 255 /* '.' */, 63 /* '/' */, - 52 /* '0' */, 53 /* '1' */, 54 /* '2' */, 55 /* '3' */, - 56 /* '4' */, 57 /* '5' */, 58 /* '6' */, 59 /* '7' */, - 60 /* '8' */, 61 /* '9' */, 255 /* ':' */, 255 /* ';' */, - 255 /* '<' */, 00 /* '=' */, 255 /* '>' */, 255 /* '?' */, - 255 /* '@' */, 00 /* 'A' */, 01 /* 'B' */, 02 /* 'C' */, - 03 /* 'D' */, 04 /* 'E' */, 05 /* 'F' */, 06 /* 'G' */, - 7 /* 'H' */, 8 /* 'I' */, 9 /* 'J' */, 10 /* 'K' */, - 11 /* 'L' */, 12 /* 'M' */, 13 /* 'N' */, 14 /* 'O' */, - 15 /* 'P' */, 16 /* 'Q' */, 17 /* 'R' */, 18 /* 'S' */, - 19 /* 'T' */, 20 /* 'U' */, 21 /* 'V' */, 22 /* 'W' */, - 23 /* 'X' */, 24 /* 'Y' */, 25 /* 'Z' */, 255 /* '[' */, - 255 /* '\' */, 255 /* ']' */, 255 /* '^' */, 255 /* '_' */, - 255 /* '`' */, 26 /* 'a' */, 27 /* 'b' */, 28 /* 'c' */, - 29 /* 'd' */, 30 /* 'e' */, 31 /* 'f' */, 32 /* 'g' */, - 33 /* 'h' */, 34 /* 'i' */, 35 /* 'j' */, 36 /* 'k' */, - 37 /* 'l' */, 38 /* 'm' */, 39 /* 'n' */, 40 /* 'o' */, - 41 /* 'p' */, 42 /* 'q' */, 43 /* 'r' */, 44 /* 's' */, - 45 /* 't' */, 46 /* 'u' */, 47 /* 'v' */, 48 /* 'w' */, - 49 /* 'x' */, 50 /* 'y' */, 51 /* 'z' */, 255 /* '{' */, - 255 /* '|' */, 255 /* '}' */, 255 /* '~' */, 255 /* '' */ -}; +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH /**************************************************************************** * > $Function: decodeBase64() @@ -370,186 +747,96 @@ static const unsigned char base64ToBin[] = { * Since the decode always results in a shorter size than the input, it is * OK to pass the input arg as an output arg. * - * $Parameters: - * (void *) outData. . . Where to place the decoded data. - * (size_t) outDataLen . The length of the output data string. - * (void *) inData . . . A pointer to a base64 encoded string. - * (size_t) inDataLen . The length of the input data string. + * $Parameter: + * (char *) Data . . . . A pointer to a base64 encoded string. + * Where to place the decoded data. * - * $Return: (char *) . . . . A pointer to the decoded string (same as input). + * $Return: void * * $Errors: None * ****************************************************************************/ -static size_t decodeBase64(void *outData, size_t outDataLen, - void *inData, size_t inDataLen) +static void decodeBase64(char *Data) { int i = 0; - unsigned char *in = inData; - unsigned char *out = outData; + static const char base64ToBin[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const unsigned char *in = Data; + // The decoded size will be at most 3/4 the size of the encoded unsigned long ch = 0; - while (inDataLen && outDataLen) - { + + while (*in) { unsigned char conv = 0; - unsigned char newch; - - while (inDataLen) - { - inDataLen--; - newch = *in++; - if ((newch < '0') || (newch > 'z')) continue; - conv = base64ToBin[newch - 32]; - if (conv == 255) continue; + + while (*in) { + const char *p64; + + p64 = strchr(base64ToBin, *in++); + if(p64 == NULL) + continue; + conv = (p64 - base64ToBin); break; } ch = (ch << 6) | conv; i++; - if (i== 4) - { - if (outDataLen >= 3) - { - *(out++) = (unsigned char) (ch >> 16); - *(out++) = (unsigned char) (ch >> 8); - *(out++) = (unsigned char) ch; - outDataLen-=3; - } - + if (i== 4) { + *Data++ = (char) (ch >> 16); + *Data++ = (char) (ch >> 8); + *Data++ = (char) ch; i = 0; } - - if ((inDataLen == 0) && (i != 0)) - { - /* error - non multiple of 4 chars on input */ - break; - } - } - - /* return the actual number of chars in output array */ - return out-(unsigned char*) outData; + *Data = 0; } #endif -/**************************************************************************** - * - > $Function: perror_and_exit() - * - > $Description: A helper function to print an error and exit. - * - * $Parameters: - * (const char *) msg . . . A 'context' message to include. - * - * $Return: None - * - * $Errors: None - * - ****************************************************************************/ -static void perror_exit(const char *msg) -{ - perror(msg); - exit(1); -} - - -/**************************************************************************** - * - > $Function: strncmpi() - * - * $Description: compare two strings without regard to case. - * - * $Parameters: - * (char *) a . . . . . The first string. - * (char *) b . . . . . The second string. - * (int) n . . . . . . The number of chars to compare. - * - * $Return: (int) . . . . . . 0 if strings equal. 1 if a>b, -1 if b < a. - * - * $Errors: None - * - ****************************************************************************/ -#define __toupper(c) ((('a' <= (c))&&((c) <= 'z')) ? ((c) - 'a' + 'A') : (c)) -#define __tolower(c) ((('A' <= (c))&&((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) -static int strncmpi(const char *a, const char *b,int n) -{ - char a1,b1; - a1 = b1 = 0; - - while(n-- && ((a1 = *a++) != '\0') && ((b1 = *b++) != '\0')) - { - if(a1 == b1) continue; /* No need to convert */ - a1 = __tolower(a1); - b1 = __tolower(b1); - if(a1 != b1) break; /* No match, abort */ - } - if (n>=0) - { - if(a1 > b1) return 1; - if(a1 < b1) return -1; - } - return 0; -} +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY /**************************************************************************** * > $Function: openServer() * * $Description: create a listen server socket on the designated port. * - * $Parameters: - * (int) port . . . The port to listen on for connections. - * * $Return: (int) . . . A connection socket. -1 for errors. * * $Errors: None * ****************************************************************************/ -static int openServer(int port) +static int openServer(void) { struct sockaddr_in lsocket; int fd; - + /* create the socket right now */ /* inet_addr() returns a value that is already in network order */ memset(&lsocket, 0, sizeof(lsocket)); lsocket.sin_family = AF_INET; lsocket.sin_addr.s_addr = INADDR_ANY; - lsocket.sin_port = htons(port) ; + lsocket.sin_port = htons(config->port) ; fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd >= 0) - { + if (fd >= 0) { /* tell the OS it's OK to reuse a previous address even though */ /* it may still be in a close down state. Allows bind to succeed. */ - int one = 1; + int on = 1; #ifdef SO_REUSEPORT - setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&one, sizeof(one)) ; + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on)) ; #else - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)) ; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) ; #endif - if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0) - { + if (bind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)) == 0) { listen(fd, 9); signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */ + } else { + perror_msg_and_die("bind"); } - else - { - perror("failure to bind to server port"); - shutdown(fd,0); - close(fd); - fd = -1; - } - } - else - { - fprintf(stderr,"httpd: unable to create socket \n"); - } + } else { + perror_msg_and_die("create socket"); + } return fd; } - -static int sendBuf(int s, char *buf, int len) -{ - if (len == -1) len = strlen(buf); - return send(s, buf, len, 0); -} +#endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ /**************************************************************************** * @@ -558,84 +845,65 @@ static int sendBuf(int s, char *buf, int len) * $Description: 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. If contentType is null the - * content type is assumed to be text/html + * second packet is delayed for any reason. * - * $Parameters: - * (int) s . . . The http socket. + * $Parameter: * (HttpResponseNum) responseNum . . . The result code to send. - * (const char *) contentType . . . . A string indicating the type. - * (int) contentLength . . . . . . . . Content length. -1 if unknown. - * (time_t) expire . . . . . . . . . . Expiration time (secs since 1970) * - * $Return: (int) . . . . Always 0 - * - * $Errors: None + * $Return: (int) . . . . writing errors * ****************************************************************************/ -static int sendHeaders(int s, HttpResponseNum responseNum , - const char *contentType, - int contentLength, time_t expire) +static int sendHeaders(HttpResponseNum responseNum) { - char buf[1200]; + char *buf = config->buf; const char *responseString = ""; const char *infoString = 0; unsigned int i; time_t timer = time(0); char timeStr[80]; - for (i=0; - i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++) - { - if (httpResponseNames[i].type != responseNum) continue; - responseString = httpResponseNames[i].name; - infoString = httpResponseNames[i].info; - break; + int len; + + for (i = 0; + i < (sizeof(httpResponseNames)/sizeof(httpResponseNames[0])); i++) { + if (httpResponseNames[i].type == responseNum) { + responseString = httpResponseNames[i].name; + infoString = httpResponseNames[i].info; + break; + } } - if (infoString || !contentType) - { - contentType = "text/html"; + if (responseNum != HTTP_OK) { + config->found_mime_type = "text/html"; // error message is HTML } - - sprintf(buf, "HTTP/1.0 %d %s\nContent-type: %s\r\n", - responseNum, responseString, contentType); /* emit the current date */ - strftime(timeStr, sizeof(timeStr), - "%a, %d %b %Y %H:%M:%S GMT",gmtime(&timer)); - sprintf(buf+strlen(buf), "Date: %s\r\n", timeStr); - sprintf(buf+strlen(buf), "Connection: close\r\n"); - if (expire) - { - strftime(timeStr, sizeof(timeStr), - "%a, %d %b %Y %H:%M:%S GMT",gmtime(&expire)); - sprintf(buf+strlen(buf), "Expire: %s\r\n", timeStr); - } + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); + len = sprintf(buf, + "HTTP/1.0 %d %s\nContent-type: %s\r\n" + "Date: %s\r\nConnection: close\r\n", + responseNum, responseString, config->found_mime_type, timeStr); - if (responseNum == HTTP_UNAUTHORIZED) - { - sprintf(buf+strlen(buf), - "WWW-Authenticate: Basic realm=\"%s\"\r\n", realm); + if (responseNum == HTTP_UNAUTHORIZED) { + len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n", + config->realm); } - if (contentLength != -1) - { - int len = strlen(buf); - sprintf(buf+len,"Content-length: %d\r\n", contentLength); + if (config->ContentLength != -1) { /* file */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod)); + len += sprintf(buf+len, "Last-Modified: %s\r\n%s %ld\r\n", + timeStr, Content_length, config->ContentLength); } - strcat(buf,"\r\n"); - if (infoString) - { - sprintf(buf+strlen(buf), + strcat(buf, "\r\n"); + len += 2; + if (infoString) { + len += sprintf(buf+len, "%d %s\n" "

%d %s

\n%s\n\n", responseNum, responseString, - responseNum, responseString, - infoString); + responseNum, responseString, infoString); } #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"Headers:'%s'", buf); + if (config->debugHttpd) fprintf(stderr, "Headers: '%s'", buf); #endif - sendBuf(s, buf,-1); - return 0; + return full_write(a_c_w, buf, len); } /**************************************************************************** @@ -647,30 +915,29 @@ static int sendHeaders(int s, HttpResponseNum responseNum , * Characters are read one at a time until an eol sequence is found. * * $Parameters: - * (int) s . . . . . The socket fildes. * (char *) buf . . Where to place the read result. - * (int) maxBuf . . Maximum number of chars to fit in buf. * * $Return: (int) . . . . number of characters read. -1 if error. * ****************************************************************************/ -static int getLine(int s, char *buf, int maxBuf) +static int getLine(char *buf) { int count = 0; - while (recv(s, buf+count, 1, 0) == 1) - { + + while (read(a_c_r, buf + count, 1) == 1) { if (buf[count] == '\r') continue; - if (buf[count] == '\n') - { + if (buf[count] == '\n') { buf[count] = 0; return count; } - count++; + if(count < (MAX_MEMORY_BUFF-1)) /* check owerflow */ + count++; } if (count) return count; else return -1; } +#ifdef CONFIG_FEATURE_HTTPD_CGI /**************************************************************************** * > $Function: sendCgi() @@ -682,193 +949,215 @@ static int getLine(int s, char *buf, int maxBuf) * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). * * $Parameters: - * (int ) s . . . . . . . . The session socket. * (const char *) url . . . The requested URL (with leading /). * (const char *urlArgs). . Any URL arguments. * (const char *body) . . . POST body contents. * (int bodyLen) . . . . . Length of the post body. - + * (const char *cookie) . . For set HTTP_COOKIE. + * * $Return: (char *) . . . . A pointer to the decoded string (same as input). * * $Errors: None * ****************************************************************************/ -static int sendCgi(int s, const char *url, - const char *request, const char *urlArgs, - const char *body, int bodyLen) +static int sendCgi(const char *url, + const char *request, const char *urlArgs, + const char *body, int bodyLen, const char *cookie) { int fromCgi[2]; /* pipe for reading data from CGI */ int toCgi[2]; /* pipe for sending data to CGI */ - - char *argp[] = { 0, 0 }; - int pid=0; - int inFd=inFd; + + static char * argp[] = { 0, 0 }; + int pid = 0; + int inFd; int outFd; - int firstLine=1; + int firstLine = 1; - do - { - if (pipe(fromCgi) != 0) - { + do { + if (pipe(fromCgi) != 0) { break; } - if (pipe(toCgi) != 0) - { + if (pipe(toCgi) != 0) { break; } pid = fork(); - if (pid < 0) - { + if (pid < 0) { pid = 0; - break;; + break; } - - if (!pid) - { + + if (!pid) { /* child process */ char *script; - char *directory; - inFd=toCgi[0]; - outFd=fromCgi[1]; + char *purl = strdup( url ); + char realpath_buff[MAXPATHLEN]; + + if(purl == NULL) + _exit(242); + + inFd = toCgi[0]; + outFd = fromCgi[1]; dup2(inFd, 0); // replace stdin with the pipe dup2(outFd, 1); // replace stdout with the pipe - if (!debugHttpd) dup2(outFd, 2); // replace stderr with the pipe + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + if (!config->debugHttpd) +#endif + dup2(outFd, 2); // replace stderr with the pipe + close(toCgi[0]); close(toCgi[1]); close(fromCgi[0]); close(fromCgi[1]); -#if 0 - fcntl(0,F_SETFD, 1); - fcntl(1,F_SETFD, 1); - fcntl(2,F_SETFD, 1); -#endif - - script = (char*) malloc(strlen(url)+2); - if (!script) _exit(242); - sprintf(script,".%s",url); - - envCount=0; - addEnv("SCRIPT_NAME",script); - addEnv("REQUEST_METHOD",request); - addEnv("QUERY_STRING",urlArgs); - addEnv("SERVER_SOFTWARE",httpdVersion); - if (strncmpi(request,"POST",4)==0) addEnvCgi(body); - else addEnvCgi(urlArgs); - /* - * Most HTTP servers chdir to the cgi directory. + * Find PATH_INFO. */ - while (*url == '/') url++; // skip leading slash(s) - directory = strdup( url ); - if ( directory == (char*) 0 ) - script = (char*) (url); /* ignore errors */ - else - { - script = strrchr( directory, '/' ); - if ( script == (char*) 0 ) - script = directory; - else - { - *script++ = '\0'; - (void) chdir( directory ); /* ignore errors */ + script = purl; + while((script = strchr( script + 1, '/' )) != NULL) { + /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */ + struct stat sb; + + *script = '\0'; + if(is_directory(purl + 1, 1, &sb) == 0) { + /* not directory, found script.cgi/PATH_INFO */ + *script = '/'; + break; } + *script = '/'; /* is directory, find next '/' */ + } + addEnv("PATH", "INFO", script); /* set /PATH_INFO or NULL */ + addEnv("PATH", "", getenv("PATH")); + addEnv("REQUEST", "METHOD", request); + if(urlArgs) { + char *uri = alloca(strlen(purl) + 2 + strlen(urlArgs)); + if(uri) + sprintf(uri, "%s?%s", purl, urlArgs); + addEnv("REQUEST", "URI", uri); + } else { + addEnv("REQUEST", "URI", purl); + } + if(script != NULL) + *script = '\0'; /* reduce /PATH_INFO */ + /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ + addEnv("SCRIPT_NAME", "", purl); + addEnv("QUERY_STRING", "", urlArgs); + addEnv("SERVER", "SOFTWARE", httpdVersion); + addEnv("SERVER", "PROTOCOL", "HTTP/1.0"); + addEnv("GATEWAY_INTERFACE", "", "CGI/1.1"); +#ifdef CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + addEnv("REMOTE", "ADDR", config->rmt_ip); + addEnvPort("REMOTE"); +#else + addEnv("REMOTE_ADDR", "", config->rmt_ip); +#endif + if(bodyLen) { + char sbl[32]; + + sprintf(sbl, "%d", bodyLen); + addEnv("CONTENT_LENGTH", "", sbl); + } + if(cookie) + addEnv("HTTP_COOKIE", "", cookie); + +#ifdef CONFIG_FEATURE_HTTPD_SET_CGI_VARS_TO_ENV + if (request != request_GET) { + addEnvCgi(body); + } else { + addEnvCgi(urlArgs); } +#endif + + /* set execve argp[0] without path */ + argp[0] = strrchr( purl, '/' ) + 1; + /* but script argp[0] must have absolute path and chdiring to this */ + if(realpath(purl + 1, realpath_buff) != NULL) { + script = strrchr(realpath_buff, '/'); + if(script) { + *script = '\0'; + if(chdir(realpath_buff) == 0) { + *script = '/'; // now run the program. If it fails, use _exit() so no destructors // get called and make a mess. - execve(script, argp, envp); - -#ifdef DEBUG - fprintf(stderr, "exec failed\n"); + execve(realpath_buff, argp, config->envp); + } + } + } +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + config->accepted_socket = 1; /* send to stdout */ #endif - close(2); - close(1); - close(0); + sendHeaders(HTTP_NOT_FOUND); _exit(242); } /* end child */ + } while (0); + + if (pid) { /* parent process */ - inFd=fromCgi[0]; - outFd=toCgi[1]; + int status; + + inFd = fromCgi[0]; + outFd = toCgi[1]; close(fromCgi[1]); close(toCgi[0]); - if (body) write(outFd, body, bodyLen); + if (body) full_write(outFd, body, bodyLen); close(outFd); - } while (0); - - if (pid) - { - int status; - pid_t dead_pid; - - while (1) - { + while (1) { struct timeval timeout; fd_set readSet; char buf[160]; int nfound; int count; - + FD_ZERO(&readSet); FD_SET(inFd, &readSet); - + /* Now wait on the set of sockets! */ timeout.tv_sec = 0; timeout.tv_usec = 10000; - nfound = select(inFd+1, &readSet, 0, 0, &timeout); + nfound = select(inFd + 1, &readSet, 0, 0, &timeout); - if (nfound <= 0) - { - dead_pid = waitpid(pid, &status, WNOHANG); - if (dead_pid != 0) - { - close(fromCgi[0]); - close(fromCgi[1]); - close(toCgi[0]); - close(toCgi[1]); + if (nfound <= 0) { + if (waitpid(pid, &status, WNOHANG) > 0) { + close(inFd); #ifdef DEBUG - if (debugHttpd) - { + if (config->debugHttpd) { if (WIFEXITED(status)) - fprintf(stderr,"piped has exited with status=%d\n", WEXITSTATUS(status)); + error_msg("piped has exited with status=%d", WEXITSTATUS(status)); if (WIFSIGNALED(status)) - fprintf(stderr,"piped has exited with signal=%d\n", WTERMSIG(status)); + error_msg("piped has exited with signal=%d", WTERMSIG(status)); } -#endif +#endif pid = -1; break; } - } - else - { + } else { + int s = a_c_w; + // There is something to read - count = read(inFd,buf,sizeof(buf)-1); + count = full_read(inFd, buf, sizeof(buf)-1); // If a read returns 0 at this point then some type of error has // occurred. Bail now. if (count == 0) break; - if (count > 0) - { - if (firstLine) - { + if (count > 0) { + if (firstLine) { /* check to see if the user script added headers */ - if (strcmp(buf,"HTTP")!= 0) - { - write(s,"HTTP/1.0 200 OK\n", 16); + if (strncmp(buf, "HTTP/1.0 200 OK\n", 4) != 0) { + full_write(s, "HTTP/1.0 200 OK\n", 16); } - if (strstr(buf,"ontent-") == 0) - { - write(s,"Content-type: text/plain\n\n", 26); + if (strstr(buf, "ontent-") == 0) { + full_write(s, "Content-type: text/plain\n\n", 26); } - firstLine=0; } - write(s,buf,count); + full_write(s, buf, count); #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"cgi read %d bytes\n", count); + if (config->debugHttpd) + fprintf(stderr, "cgi read %d bytes\n", count); #endif } } @@ -876,6 +1165,7 @@ static int sendCgi(int s, const char *url, } return 0; } +#endif /* CONFIG_FEATURE_HTTPD_CGI */ /**************************************************************************** * @@ -883,55 +1173,66 @@ static int sendCgi(int s, const char *url, * * $Description: Send a file response to an HTTP request * - * $Parameters: - * (int) s . . . . . . . The http session socket. + * $Parameter: * (const char *) url . . The URL requested. + * (char *) buf . . . . . The stack buffer. * * $Return: (int) . . . . . . Always 0. * ****************************************************************************/ -static int sendFile(int s, const char *url) +static int sendFile(const char *url, char *buf) { - char *suffix = strrchr(url,'.'); - const char *content = "application/octet-stream"; + char * suffix; int f; + const char * const * table; + const char * try_suffix; - if (suffix) - { - const char ** table; - for (table = (const char **) &suffixTable[0]; - *table && (strstr(*table, suffix) == 0); table+=2); - if (table) content = *(table+1); + suffix = strrchr(url, '.'); + + for (table = suffixTable; *table; table += 2) + if(suffix != NULL && (try_suffix = strstr(*table, suffix)) != 0) { + try_suffix += strlen(suffix); + if(*try_suffix == 0 || *try_suffix == '.') + break; + } + /* also, if not found, set default as "application/octet-stream"; */ + config->found_mime_type = *(table+1); +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (suffix) { + Htaccess * cur; + + for (cur = config->Httpd_conf_parsed; cur; cur = cur->next) { + if(strcmp(cur->before_colon, suffix) == 0) { + config->found_mime_type = cur->after_colon; + break; + } + } } - - if (*url == '/') url++; - suffix = strchr(url,'?'); - if (suffix) *suffix = 0; +#endif /* CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */ #ifdef DEBUG - fprintf(stderr,"Sending file '%s'\n", url); + if (config->debugHttpd) + fprintf(stderr, "Sending file '%s' Content-type: %s\n", + url, config->found_mime_type); #endif - - f = open(url,O_RDONLY, 0444); - if (f >= 0) - { - char buf[1450]; - int count; - sendHeaders(s, HTTP_OK, content, -1, 0 ); - while ((count = read(f, buf, sizeof(buf)))) - { - sendBuf(s, buf, count); - } - close(f); - } - else - { + + f = open(url, O_RDONLY); + if (f >= 0) { + int count; + + sendHeaders(HTTP_OK); + while ((count = full_read(f, buf, MAX_MEMORY_BUFF)) > 0) { + full_write(a_c_w, buf, count); + } + close(f); + } else { #ifdef DEBUG - fprintf(stderr,"Unable to open '%s'\n", url); + if (config->debugHttpd) + perror_msg("Unable to open '%s'", url); #endif - sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0); + sendHeaders(HTTP_NOT_FOUND); } - + return 0; } @@ -941,93 +1242,85 @@ static int sendFile(int s, const char *url) * * $Description: Check the permission file for access. * - * Both IP addresses as well as url pathnames can be specified. If an IP - * address check is desired, the 'path' should be specified as "ip" and the - * dotted decimal IP address placed in request. - * - * For url pathnames, place the url (with leading /) in 'path' and any - * authentication information in request. e.g. "user:pass" - * - ******* - * - * Keep the algorithm simple. * If config file isn't present, everything is allowed. - * Run down /etc/httpd.hosts a line at a time. - * Stop if match is found. - * Entries are of the form: - * ip:10.10 # any address that begins with 10.10 - * dir:user:pass # dir security for dirs that start with 'dir' - * - * httpd.conf has the following format: - * ip:10.10. # Allow any address that begins with 10.10. - * ip:172.20. # Allow 172.20.x.x - * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin - * /:foo:bar # Require user foo, pwd bar on urls starting with / - * - * To open up the server: - * ip:* # Allow any IP address - * /:* # no password required for urls starting with / (all) + * Entries are of the form you can see example from header source * * $Parameters: - * (const char *) path . . . . The file path or "ip" for ip addresses. + * (const char *) path . . . . The file path or NULL for ip addresses. * (const char *) request . . . User information to validate. * * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise. * ****************************************************************************/ + + static int checkPerm(const char *path, const char *request) { - FILE *f=NULL; - int rval; - char buf[80]; - char *p; - int ipaddr=0; - - /* If httpd.conf not there assume anyone can get in */ - if (configFile) f = fopen(configFile,"r"); - if(f == NULL) f = fopen("/etc/httpd.conf","r"); - if(f == NULL) f = fopen("httpd.conf","r"); - if(f == NULL) { - return(1); - } - if (strcmp("ip",path) == 0) ipaddr=1; + Htaccess * cur; + const char *p; - rval=0; +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + int ipaddr = path == NULL; + const char *prev = NULL; +#else +# define ipaddr 1 +#endif /* This could stand some work */ - while ( fgets(buf, 80, f) != NULL) - { - if(buf[0] == '#') continue; - if(buf[0] == '\0') continue; - for(p = buf + (strlen(buf) - 1); p >= buf; p--) - { - if(isspace(*p)) *p = 0; - } - - p = strchr(buf,':'); - if (!p) continue; - *p++=0; + for (cur = config->Httpd_conf_parsed; cur; cur = cur->next) { + const char *p0 = cur->before_colon; + + if(*p0 == 'A' || *p0 == 'D') { + if(!ipaddr) + continue; + } else { + if(ipaddr) + continue; +#ifdef CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if(*p0 == '.') + continue; +#endif +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + if(prev != NULL && strcmp(prev, p0) != 0) + continue; /* find next identical */ +#endif + } + + p = cur->after_colon; #ifdef DEBUG - fprintf(stderr,"checkPerm: '%s' ? '%s'\n",buf,path); + if (config->debugHttpd) + fprintf(stderr,"checkPerm: '%s' ? '%s'\n", + (ipaddr ? p : p0), request); #endif - if((ipaddr ? strcmp(buf,path) : strncmp(buf, path, strlen(buf))) == 0) - { - /* match found. Check request */ - if ((strcmp("*",p) == 0) || - (strcmp(p, request) == 0) || - (ipaddr && (strncmp(p, request, strlen(p)) == 0))) - { - rval = 1; - break; - } + if(ipaddr) { + if(strncmp(p, request, strlen(p)) != 0) + continue; + return *p0 == 'A'; /* Allow/Deny */ - /* reject on first failure for non ipaddresses */ - if (!ipaddr) break; } - }; - fclose(f); - return(rval); -}; +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + else { + int l = strlen(p0); + + if(strncmp(p0, path, l) == 0 && + (l == 1 || path[l] == '/' || path[l] == 0)) { + /* path match found. Check request */ + if (strcmp(p, request) == 0) + return 1; /* Ok */ + /* unauthorized, but check next /path:user:password */ + prev = p0; + } + } +#endif + } /* for */ + +#ifndef CONFIG_FEATURE_HTTPD_BASIC_AUTH + /* if uncofigured, return 1 - access from all */ + return 1; +#else + return prev == NULL; +#endif +} /**************************************************************************** @@ -1036,156 +1329,242 @@ static int checkPerm(const char *path, const char *request) * * $Description: Handle an incoming http request. * - * $Parameters: - * (s) s . . . . . The http request socket. - * - * $Return: (int) . . . Always 0. - * ****************************************************************************/ -static int handleIncoming(int s) +static void handleIncoming(void) { - char buf[8192]; - char url[8192]; /* hold args too initially */ - char credentials[80]; - char request[20]; - long length=0; - int major; - int minor; + char *buf = config->buf; + char *url; + char *purl; + int blank = -1; char *urlArgs; - char *body=0; +#ifdef CONFIG_FEATURE_HTTPD_CGI + const char *prequest = request_GET; + char *body = 0; + long length=0; + char *cookie = 0; +#endif + char *test; + struct stat sb; - credentials[0] = 0; - do - { - int count = getLine(s, buf, sizeof(buf)); - int blank; - if (count <= 0) break; - count = sscanf(buf, "%9s %1000s HTTP/%d.%d", request, - url, &major, &minor); - - if (count < 2) - { - /* Garbled request/URL */ -#if 0 - genHttpHeader(&requestInfo, - HTTP_BAD_REQUEST, requestInfo.dataType, - HTTP_LENGTH_UNKNOWN); +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + int credentials = -1; /* if not requred this is Ok */ #endif + + do { + int count; + + if (getLine(buf) <= 0) + break; /* closed */ + + purl = strpbrk(buf, " \t"); + if(purl == NULL) { +BAD_REQUEST: + sendHeaders(HTTP_BAD_REQUEST); break; } - - /* If no version info, assume 0.9 */ - if (count != 4) - { - major = 0; - minor = 9; + *purl = 0; +#ifdef CONFIG_FEATURE_HTTPD_CGI + if(strcasecmp(buf, prequest) != 0) { + prequest = "POST"; + if(strcasecmp(buf, prequest) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } + } +#else + if(strcasecmp(buf, request_GET) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; } +#endif + *purl = ' '; + count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank); + if (count < 1 || buf[0] != '/') { + /* Garbled request/URL */ + goto BAD_REQUEST; + } + url = alloca(strlen(buf) + 12); /* + sizeof("/index.html\0") */ + if(url == NULL) { + sendHeaders(HTTP_INTERNAL_SERVER_ERROR); + break; + } + strcpy(url, buf); /* extract url args if present */ - urlArgs = strchr(url,'?'); - if (urlArgs) - { - *urlArgs=0; - urlArgs++; + urlArgs = strchr(url, '?'); + if (urlArgs) { + *urlArgs++ = 0; /* next code can set '/' to this pointer, + but CGI script can`t be a directory */ + } + + /* algorithm stolen from libbb simplify_path(), + but don`t strdup and reducing trailing slash */ + purl = test = url; + + do { + if (*purl == '/') { + if (*test == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*test == '.') { + if (test[1] == '/' || test[1] == 0) { /* skip extra '.' */ + continue; + } else if ((test[1] == '.') && (test[2] == '/' || test[2] == 0)) { + ++test; + if (purl == url) { + /* protect out root */ + goto BAD_REQUEST; + } + while (*--purl != '/'); /* omit previous dir */ + continue; + } + } + } + *++purl = *test; + } while (*++test); + + *++purl = 0; /* so keep last character */ + test = purl; /* end ptr */ + + /* If URL is directory, adding '/' */ + if(test[-1] != '/') { + if ( is_directory(url + 1, 1, &sb) ) { + *test++ = '/'; + *test = 0; + purl = test; /* end ptr */ + } } - #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"url='%s', args=%s\n", url, urlArgs); -#endif - - // read until blank line(s) - blank = 0; - while ((count = getLine(s, buf, sizeof(buf))) >= 0) - { - if (count == 0) - { - if (major > 0) break; - blank++; - if (blank == 2) break; - } + if (config->debugHttpd) + fprintf(stderr, "url='%s', args=%s\n", url, urlArgs); +#endif + + test = url; + while((test = strchr( test + 1, '/' )) != NULL) { + /* have path1/path2 */ + *test = '\0'; + if( is_directory(url + 1, 1, &sb) ) { + /* may be having subdir config */ + parse_conf(url + 1, SUBDIR_PARSE); + } + *test = '/'; + } + + // read until blank line for HTTP version specified, else parse immediate + while (blank >= 0 && (count = getLine(buf)) > 0) { + #ifdef DEBUG - if (debugHttpd) fprintf(stderr,"Header: '%s'\n", buf); -#endif + if (config->debugHttpd) fprintf(stderr, "Header: '%s'\n", buf); +#endif +#ifdef CONFIG_FEATURE_HTTPD_CGI /* try and do our best to parse more lines */ - if ((strncmpi(buf, "Content-length:", 15) == 0)) - { - sscanf(buf, "%*s %ld", &length); - } + if ((strncasecmp(buf, Content_length, 15) == 0)) { + if(prequest != request_GET) + length = strtol(buf + 15, 0, 0); // extra read only for POST + } else if ((strncasecmp(buf, "Cookie:", 7) == 0)) { + for(test = buf + 7; isspace(*test); test++) + ; + cookie = strdup(test); + } +#endif + #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH - else if (strncmpi(buf, "Authorization:", 14) == 0) - { + if (strncasecmp(buf, "Authorization:", 14) == 0) { /* We only allow Basic credentials. * It shows up as "Authorization: Basic " where * the userid:password is base64 encoded. */ - char *ptr = buf+14; - while (*ptr == ' ') ptr++; - if (strncmpi(ptr, "Basic", 5) != 0) break; - ptr += 5; - while (*ptr == ' ') ptr++; - memset(credentials, 0, sizeof(credentials)); - decodeBase64(credentials, - sizeof(credentials)-1, - ptr, - strlen(ptr) ); - + for(test = buf + 14; isspace(*test); test++) + ; + if (strncasecmp(test, "Basic", 5) != 0) + continue; + + test += 5; /* decodeBase64() skiping space self */ + decodeBase64(test); + credentials = checkPerm(url, test); } +#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ + + } /* while extra header reading */ + + + if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || + checkPerm(NULL, config->rmt_ip) == 0) { + /* protect listing [/path]/httpd_conf or IP deny */ +#ifdef CONFIG_FEATURE_HTTPD_CGI +FORBIDDEN: /* protect listing /cgi-bin */ +#endif + sendHeaders(HTTP_FORBIDDEN); + break; } - if (!checkPerm(url, credentials)) - { - sendHeaders(s, HTTP_UNAUTHORIZED, 0, -1, 0); - length=-1; - break; /* no more processing */ - } -#else + +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + if (credentials <= 0 && checkPerm(url, ":") == 0) { + sendHeaders(HTTP_UNAUTHORIZED); + break; } -#endif /* CONFIG_FEATURE_HTTPD_BASIC_AUTH */ +#endif - /* we are done if an error occurred */ - if (length == -1) break; + test = url + 1; /* skip first '/' */ - if (strcmp(url,"/") == 0) strcpy(url,"/index.html"); +#ifdef CONFIG_FEATURE_HTTPD_CGI + /* if strange Content-Length */ + if (length < 0 || length > MAX_POST_SIZE) + break; - if (length>0) - { - body=(char*) malloc(length+1); - if (body) - { - length = read(s,body,length); - body[length]=0; // always null terminate for safety - urlArgs=body; + if (length > 0) { + body = malloc(length + 1); + if (body) { + length = full_read(a_c_r, body, length); + if(length < 0) // closed + length = 0; + body[length] = 0; // always null terminate for safety + urlArgs = body; } } - - if (strstr(url,"..") || strstr(url, "httpd.conf")) - { - /* protect from .. path creep */ - sendHeaders(s, HTTP_NOT_FOUND, "text/html", -1, 0); - } - else if (strstr(url,"cgi-bin")) - { - sendCgi(s, url, request, urlArgs, body, length); - } - else if (strncmpi(request,"GET",3) == 0) - { - sendFile(s, url); - } - else - { - sendHeaders(s, HTTP_NOT_IMPLEMENTED, 0, -1, 0); + + if (strncmp(test, "cgi-bin", 7) == 0) { + if(test[7] == 0 || (test[7] == '/' && test[8] == 0)) + goto FORBIDDEN; // protect listing cgi-bin + sendCgi(url, prequest, urlArgs, body, length, cookie); + } else { + if (prequest != request_GET) + sendHeaders(HTTP_NOT_IMPLEMENTED); + else { +#endif /* CONFIG_FEATURE_HTTPD_CGI */ + if(purl[-1] == '/') + strcpy(purl, "index.html"); + if ( stat(test, &sb ) == 0 ) { + config->ContentLength = sb.st_size; + config->last_mod = sb.st_mtime; + } + sendFile(test, buf); +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + /* unset if non inetd looped */ + config->ContentLength = -1; +#endif + +#ifdef CONFIG_FEATURE_HTTPD_CGI + } } +#endif + } while (0); -#ifdef DEBUG - if (debugHttpd) fprintf(stderr,"closing socket\n"); -#endif - if (body) free(body); - shutdown(s,SHUT_WR); - shutdown(s,SHUT_RD); - close(s); - - return 0; + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY +/* from inetd don`t looping: freeing, closing automatic from exit always */ +# ifdef DEBUG + if (config->debugHttpd) fprintf(stderr, "closing socket\n"); +# endif +# ifdef CONFIG_FEATURE_HTTPD_CGI + free(body); + free(cookie); +# endif + shutdown(a_c_w, SHUT_WR); + shutdown(a_c_r, SHUT_RD); + close(config->accepted_socket); +#endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ } /**************************************************************************** @@ -1203,24 +1582,23 @@ static int handleIncoming(int s) * $Return: (int) . . . . Always 0. * ****************************************************************************/ +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY static int miniHttpd(int server) { fd_set readfd, portfd; int nfound; - + FD_ZERO(&portfd); FD_SET(server, &portfd); - + /* copy the ports we are watching to the readfd set */ - while (1) - { - readfd = portfd ; - + while (1) { + readfd = portfd; + /* Now wait INDEFINATELY on the set of sockets! */ - nfound = select(server+1, &readfd, 0, 0, 0); - - switch (nfound) - { + nfound = select(server + 1, &readfd, 0, 0, 0); + + switch (nfound) { case 0: /* select timeout error! */ break ; @@ -1228,48 +1606,41 @@ static int miniHttpd(int server) /* select error */ break; default: - if (FD_ISSET(server, &readfd)) - { - char on; + if (FD_ISSET(server, &readfd)) { + int on; struct sockaddr_in fromAddr; - char rmt_ip[20]; - int addr; + + unsigned int addr; socklen_t fromAddrLen = sizeof(fromAddr); int s = accept(server, - (struct sockaddr *)&fromAddr, &fromAddrLen) ; - if (s < 0) - { + (struct sockaddr *)&fromAddr, &fromAddrLen); + + if (s < 0) { continue; } + config->accepted_socket = s; addr = ntohl(fromAddr.sin_addr.s_addr); - sprintf(rmt_ip,"%u.%u.%u.%u", - (unsigned char)(addr >> 24), - (unsigned char)(addr >> 16), - (unsigned char)(addr >> 8), - (unsigned char)(addr >> 0)); + sprintf(config->rmt_ip, "%u.%u.%u.%u", + (unsigned char)(addr >> 24), + (unsigned char)(addr >> 16), + (unsigned char)(addr >> 8), + addr & 0xff); + config->port = ntohs(fromAddr.sin_port); #ifdef DEBUG - if (debugHttpd) - { - fprintf(stderr,"httpMini.cpp: connection from IP=%s, port %d\n", - rmt_ip, ntohs(fromAddr.sin_port)); + if (config->debugHttpd) { + error_msg("connection from IP=%s, port %u\n", + config->rmt_ip, config->port); } -#endif - if(checkPerm("ip", rmt_ip) == 0) - { - close(s); - continue; - } - +#endif /* set the KEEPALIVE option to cull dead connections */ on = 1; - setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, - sizeof (on)); + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof (on)); - if (fork() == 0) - { + if (config->debugHttpd || fork() == 0) { /* This is the spawned thread */ - handleIncoming(s); - exit(0); + handleIncoming(); + if(!config->debugHttpd) + exit(0); } close(s); } @@ -1278,72 +1649,169 @@ static int miniHttpd(int server) return 0; } +#else + /* from inetd */ + +static int miniHttpd(void) +{ + struct sockaddr_in fromAddrLen; + socklen_t sinlen = sizeof (struct sockaddr_in); + unsigned int addr; + + getpeername (0, (struct sockaddr *)&fromAddrLen, &sinlen); + addr = ntohl(fromAddrLen.sin_addr.s_addr); + sprintf(config->rmt_ip, "%u.%u.%u.%u", + (unsigned char)(addr >> 24), + (unsigned char)(addr >> 16), + (unsigned char)(addr >> 8), + addr & 0xff); + config->port = ntohs(fromAddrLen.sin_port); + handleIncoming(); + return 0; +} +#endif /* CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY */ + +#ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP +static void sighup_handler(int sig) +{ + /* set and reset */ + struct sigaction sa; + + sa.sa_handler = sighup_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGHUP, &sa, NULL); + parse_conf(default_patch_httpd_conf, + sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE); +} +#endif + +#ifdef HTTPD_STANDALONE +int main(int argc, char *argv[]) +#else int httpd_main(int argc, char *argv[]) +#endif { + const char *home_httpd = home; + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY int server; - int port = 80; - int c; - +#endif + +#ifdef CONFIG_FEATURE_HTTPD_SETUID + long uid = -1; +#endif + + config = xcalloc(1, sizeof(*config)); +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH + config->realm = "Web Server Authentication"; +#endif + +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + config->port = 80; +#endif + + config->ContentLength = -1; + /* check if user supplied a port number */ for (;;) { - c = getopt( argc, argv, "p:ve:d:" + int c = getopt( argc, argv, "c:h:" +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + "p:v" +#endif +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR + "e:" +#endif +#ifdef CONFIG_FEATURE_HTTPD_DECODE_URL_STR + "d:" +#endif #ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH - "r:c:" + "r:" +#endif +#ifdef CONFIG_FEATURE_HTTPD_SETUID + "u:" #endif ); if (c == EOF) break; switch (c) { + case 'c': + config->configFile = optarg; + break; + case 'h': + home_httpd = optarg; + break; +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY case 'v': - debugHttpd=1; + config->debugHttpd = 1; break; case 'p': - port = atoi(optarg); + config->port = atoi(optarg); + if(config->port <= 0 || config->port > 0xffff) + error_msg_and_die("invalid %s for -p", optarg); break; +#endif +#ifdef CONFIG_FEATURE_HTTPD_DECODE_URL_STR case 'd': - printf("%s",decodeString(optarg)); + printf("%s", decodeString(optarg)); return 0; +#endif +#ifdef CONFIG_FEATURE_HTTPD_ENCODE_URL_STR case 'e': - printf("%s",encodeString(optarg)); + printf("%s", encodeString(optarg)); return 0; +#endif +#ifdef CONFIG_FEATURE_HTTPD_BASIC_AUTH case 'r': - realm = optarg; + config->realm = optarg; break; - case 'c': - configFile = optarg; +#endif +#ifdef CONFIG_FEATURE_HTTPD_SETUID + case 'u': + { + char *e; + + uid = strtol(optarg, &e, 0); + if(*e != '\0') { + /* not integer */ + uid = my_getpwnam(optarg); + } + } break; +#endif default: - fprintf(stderr,"%s\n", httpdVersion); + error_msg("%s", httpdVersion); show_usage(); - exit(1); } } - envp = (char**) malloc((ENVSIZE+1)*sizeof(char*)); - if (envp == 0) perror_exit("envp alloc"); - - server = openServer(port); - if (server < 0) exit(1); - - if (!debugHttpd) - { - /* remember our current pwd, daemonize, chdir back */ - char *dir = (char *) malloc(256); - if (dir == 0) perror_exit("out of memory for getpwd"); - if (getcwd(dir, 256) == 0) perror_exit("getcwd failed"); - if (daemon(0, 1) < 0) perror_exit("daemon"); - chdir(dir); - free(dir); + if(chdir(home_httpd)) { + perror_msg_and_die("can`t chdir to %s", home_httpd); } +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + server = openServer(); +# ifdef CONFIG_FEATURE_HTTPD_SETUID + /* drop privilegies */ + if(uid > 0) + setuid(uid); +# endif +# ifdef CONFIG_FEATURE_HTTPD_CGI + addEnvPort("SERVER"); +# endif +#endif - miniHttpd(server); - - return 0; -} - -#ifdef HTTPD_STANDALONE -int main(int argc, char *argv[]) -{ - return httpd_main(argc, argv); -} +#ifdef CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + sighup_handler(0); +#else + parse_conf(default_patch_httpd_conf, FIRST_PARSE); +#endif +#ifndef CONFIG_FEATURE_HTTPD_USAGE_FROM_INETD_ONLY + if (!config->debugHttpd) { + if (daemon(1, 0) < 0) /* don`t change curent directory */ + perror_msg_and_die("daemon"); + } + return miniHttpd(server); +#else + return miniHttpd(); #endif +} diff --git a/networking/inetd.c b/networking/inetd.c new file mode 100644 index 000000000..047358c90 --- /dev/null +++ b/networking/inetd.c @@ -0,0 +1,1280 @@ +/* + * Copyright (c) 1983,1991 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * David A. Holland. + * + * Busybox port by Vladimir Oleynik (C) 2001-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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * Inetd - Internet super-server + * + * This program invokes all internet services as needed. + * 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 the source 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. Datagram servers may either connect + * to their peer, freeing up the original socket for inetd + * to receive further messages on, or ``take over the socket'', + * processing all arriving datagrams and, eventually, timing + * out. The first type of server is said to be ``multi-threaded''; + * the second type of server ``single-threaded''. + * + * 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 being 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 + * wait/nowait[.max] single-threaded/multi-threaded, max # + * user[.group] user/group to run daemon as + * server program full path name + * server program arguments maximum of MAXARGS (20) + * + * RPC services unsuported + * + * Comment lines are indicated by a `#' in column 1. + */ + +/* + * Here's the scoop concerning the user.group feature: + * + * 1) No group listed. + * + * a) for root: NO setuid() or setgid() is done + * + * b) nonroot: setuid() + * setgid(primary group as found in passwd) + * initgroups(name, primary group) + * + * 2) set-group-option on. + * + * a) for root: NO setuid() + * setgid(specified group) + * setgroups(1, specified group) + * + * b) nonroot: setuid() + * setgid(specified group) + * initgroups(name, specified group) + * + * All supplementary groups are discarded at startup in case inetd was + * run manually. + */ + +#define __USE_BSD_SIGNAL + +#include "busybox.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __linux__ +#ifndef RLIMIT_NOFILE +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _PATH_INETDCONF "/etc/inetd.conf" +#define _PATH_INETDPID "/var/run/inetd.pid" + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define TOOMANY 40 /* don't start more than TOOMANY */ +#define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ +#define RETRYTIME (60*10) /* retry after bind or server fail */ + +#ifndef OPEN_MAX +#define OPEN_MAX 64 +#endif + + +/* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */ +#define FD_MARGIN (8) +static int rlim_ofile_cur = OPEN_MAX; + +#ifdef RLIMIT_NOFILE +static struct rlimit rlim_ofile; +#endif + + +static struct servtab { + char *se_service; /* name of service */ + int se_socktype; /* type of socket to use */ + int se_family; /* address family */ + char *se_proto; /* protocol used */ + short se_wait; /* single threaded server */ + short se_checked; /* looked at during merge */ + char *se_user; /* user name to run as */ + char *se_group; /* group name to run as */ +#ifndef INETD_UNSUPPORT_BILTIN + const struct biltin *se_bi; /* if built-in, description */ +#endif + char *se_server; /* server program */ +#define MAXARGV 20 + char *se_argv[MAXARGV+1]; /* program arguments */ + int se_fd; /* open descriptor */ + union { + struct sockaddr se_un_ctrladdr; + struct sockaddr_in se_un_ctrladdr_in; + struct sockaddr_un se_un_ctrladdr_un; + } se_un; /* bound address */ +#define se_ctrladdr se_un.se_un_ctrladdr +#define se_ctrladdr_in se_un.se_un_ctrladdr_in +#define se_ctrladdr_un se_un.se_un_ctrladdr_un + int se_ctrladdr_size; + int se_max; /* max # of instances of this service */ + int se_count; /* number started since se_time */ + struct timeval se_time; /* start of se_count */ + struct servtab *se_next; +} *servtab; + +/* Length of socket listen queue. Should be per-service probably. */ +static int global_queuelen = 128; + +static int nsock, maxsock; +static fd_set allsock; +static int timingout; +static sigset_t blockmask, emptymask; + + +#define INETD_UNSUPPORT_BILTIN 1 + + /* Echo received data */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO +#undef INETD_UNSUPPORT_BILTIN +static void echo_stream(int, struct servtab *); +static void echo_dg(int, struct servtab *); +#endif + /* Internet /dev/null */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD +#undef INETD_UNSUPPORT_BILTIN +static void discard_stream(int, struct servtab *); +static void discard_dg(int, struct servtab *); +#endif + /* Return 32 bit time since 1900 */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME +#undef INETD_UNSUPPORT_BILTIN +static void machtime_stream(int, struct servtab *); +static void machtime_dg(int, struct servtab *); +#endif + /* Return human-readable time */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME +#undef INETD_UNSUPPORT_BILTIN +static void daytime_stream(int, struct servtab *); +static void daytime_dg(int, struct servtab *); +#endif + /* Familiar character generator */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN +#undef INETD_UNSUPPORT_BILTIN +static void chargen_stream(int, struct servtab *); +static void chargen_dg(int, struct servtab *); +#endif + +#ifndef INETD_UNSUPPORT_BILTIN +struct biltin { + const char *bi_service; /* internally provided service name */ + int bi_socktype; /* type of socket supported */ + short bi_fork; /* 1 if should fork before call */ + short bi_wait; /* 1 if should wait for child */ + void (*bi_fn)(int, struct servtab *); /* fn which performs it */ +}; + +static const struct biltin biltins[] = { +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO + /* Echo received data */ + { "echo", SOCK_STREAM, 1, 0, echo_stream, }, + { "echo", SOCK_DGRAM, 0, 0, echo_dg, }, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD + /* Internet /dev/null */ + { "discard", SOCK_STREAM, 1, 0, discard_stream, }, + { "discard", SOCK_DGRAM, 0, 0, discard_dg, }, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME + /* Return 32 bit time since 1900 */ + { "time", SOCK_STREAM, 0, 0, machtime_stream, }, + { "time", SOCK_DGRAM, 0, 0, machtime_dg, }, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME + /* Return human-readable time */ + { "daytime", SOCK_STREAM, 0, 0, daytime_stream, }, + { "daytime", SOCK_DGRAM, 0, 0, daytime_dg, }, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN + /* Familiar character generator */ + { "chargen", SOCK_STREAM, 1, 0, chargen_stream, }, + { "chargen", SOCK_DGRAM, 0, 0, chargen_dg, }, +#endif + { NULL, 0, 0, 0, NULL } +}; +#endif /* INETD_UNSUPPORT_BILTIN */ + +#define NUMINT (sizeof(intab) / sizeof(struct inent)) +static const char *CONFIG = _PATH_INETDCONF; + +#define BCOPY(s, d, z) memcpy(d, s, z) + +static void +syslog_err_and_discard_dg(int se_socktype, const char *msg, ...) + __attribute__ ((noreturn, format (printf, 2, 3))); + +static void +syslog_err_and_discard_dg(int se_socktype, const char *msg, ...) +{ + char buf[50]; + va_list p; + + va_start(p, msg); + vsyslog(LOG_ERR, msg, p); + if (se_socktype != SOCK_STREAM) + recv(0, buf, sizeof (buf), 0); + _exit(1); +} + +static FILE *fconfig; +static char line[256]; + +static FILE * +setconfig(void) +{ + FILE *f = fconfig; + + if (f != NULL) { + fseek(f, 0L, L_SET); + } else { + f = fconfig = fopen(CONFIG, "r"); + if(f == NULL) + syslog(LOG_ERR, "%s: %m", CONFIG); + } + return f; +} + +static char * +nextline(void) +{ + char *cp; + FILE *fd = fconfig; + + if (fgets(line, sizeof (line), fd) == NULL) + return ((char *)0); + cp = strchr(line, '\n'); + if (cp) + *cp = '\0'; + return (line); +} + +static char * +skip(char **cpp) +{ + char *cp = *cpp; + char *start; + + if (*cpp == NULL) + return ((char *)0); + +again: + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '\0') { + int c; + + c = getc(fconfig); + (void) ungetc(c, fconfig); + if (c == ' ' || c == '\t') + if ((cp = nextline()) != NULL) + goto again; + *cpp = NULL; + return NULL; + } + start = cp; + while (*cp && *cp != ' ' && *cp != '\t') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + *cpp = cp; + return (start); +} + +static char * +newstr(char *cp) +{ + cp = strdup(cp ? cp : ""); + if (cp) + return(cp); + + syslog_err_and_discard_dg(SOCK_STREAM, "strdup: %m"); +} + + +static struct servtab * +getconfigent(void) +{ + static struct servtab serv; + struct servtab *sep = &serv; + int argc; + char *cp, *arg; + +more: + while ((cp = nextline()) && *cp == '#') + ; + if (cp == NULL) + return ((struct servtab *)0); + memset((char *)sep, 0, sizeof *sep); + sep->se_service = newstr(skip(&cp)); + arg = skip(&cp); + if (arg == NULL) + goto more; + + if (strcmp(arg, "stream") == 0) + sep->se_socktype = SOCK_STREAM; + else if (strcmp(arg, "dgram") == 0) + sep->se_socktype = SOCK_DGRAM; + else if (strcmp(arg, "rdm") == 0) + sep->se_socktype = SOCK_RDM; + else if (strcmp(arg, "seqpacket") == 0) + sep->se_socktype = SOCK_SEQPACKET; + else if (strcmp(arg, "raw") == 0) + sep->se_socktype = SOCK_RAW; + else + sep->se_socktype = -1; + + sep->se_proto = newstr(skip(&cp)); + if (strcmp(sep->se_proto, "unix") == 0) { + sep->se_family = AF_UNIX; + } else { + sep->se_family = AF_INET; + if (strncmp(sep->se_proto, "rpc/", 4) == 0) { + syslog(LOG_ERR, "%s: rpc services not suported", + sep->se_service); + goto more; + } + } + arg = skip(&cp); + if (arg == NULL) + goto more; + { + char *s = strchr(arg, '.'); + if (s) { + *s++ = '\0'; + sep->se_max = atoi(s); + } else + sep->se_max = TOOMANY; + } + sep->se_wait = strcmp(arg, "wait") == 0; + sep->se_user = newstr(skip(&cp)); + sep->se_group = strchr(sep->se_user, '.'); + if (sep->se_group) { + *sep->se_group++ = '\0'; + } + sep->se_server = newstr(skip(&cp)); + if (strcmp(sep->se_server, "internal") == 0) { +#ifndef INETD_UNSUPPORT_BILTIN + const struct biltin *bi; + + for (bi = biltins; bi->bi_service; bi++) + if (bi->bi_socktype == sep->se_socktype && + strcmp(bi->bi_service, sep->se_service) == 0) + break; + if (bi->bi_service == 0) { + syslog(LOG_ERR, "internal service %s unknown", + sep->se_service); + goto more; + } + sep->se_bi = bi; + sep->se_wait = bi->bi_wait; +#else + syslog(LOG_ERR, "internal service %s unknown", + sep->se_service); + goto more; +#endif + } else +#ifndef INETD_UNSUPPORT_BILTIN + sep->se_bi = NULL +#endif + ; + argc = 0; + for (arg = skip(&cp); cp; arg = skip(&cp)) { + if (argc < MAXARGV) + sep->se_argv[argc++] = newstr(arg); + } + while (argc <= MAXARGV) + sep->se_argv[argc++] = NULL; + return (sep); +} + +static void +freeconfig(struct servtab *cp) +{ + int i; + + free(cp->se_service); + free(cp->se_proto); + free(cp->se_user); + /* Note: se_group is part of the newstr'ed se_user */ + free(cp->se_server); + for (i = 0; i < MAXARGV; i++) + free(cp->se_argv[i]); +} + +#ifndef INETD_UNSUPPORT_BILTIN +static char **Argv; +static char *LastArg; + +static void +setproctitle(char *a, int s) +{ + size_t size; + char *cp; + struct sockaddr_in sn; + char buf[80]; + + cp = Argv[0]; + size = sizeof(sn); + if (getpeername(s, (struct sockaddr *)&sn, &size) == 0) + (void) sprintf(buf, "-%s [%s]", a, inet_ntoa(sn.sin_addr)); + else + (void) sprintf(buf, "-%s", a); + strncpy(cp, buf, LastArg - cp); + cp += strlen(cp); + while (cp < LastArg) + *cp++ = ' '; +} +#endif /* INETD_UNSUPPORT_BILTIN */ + +static struct servtab * +enter(struct servtab *cp) +{ + struct servtab *sep; + sigset_t oldmask; + + sep = (struct servtab *)malloc(sizeof (*sep)); + if (sep == NULL) { + syslog_err_and_discard_dg(SOCK_STREAM, memory_exhausted); + } + *sep = *cp; + sep->se_fd = -1; + sigprocmask(SIG_BLOCK, &blockmask, &oldmask); + sep->se_next = servtab; + servtab = sep; + sigprocmask(SIG_SETMASK, &oldmask, NULL); + return (sep); +} + +static int +bump_nofile(void) +{ +#ifdef RLIMIT_NOFILE + +#define FD_CHUNK 32 + + struct rlimit rl; + + if (getrlimit(RLIMIT_NOFILE, &rl) < 0) { + syslog(LOG_ERR, "getrlimit: %m"); + return -1; + } + rl.rlim_cur = MIN(rl.rlim_max, rl.rlim_cur + FD_CHUNK); + if (rl.rlim_cur <= rlim_ofile_cur) { + syslog(LOG_ERR, + "bump_nofile: cannot extend file limit, max = %d", + rl.rlim_cur); + return -1; + } + + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { + syslog(LOG_ERR, "setrlimit: %m"); + return -1; + } + + rlim_ofile_cur = rl.rlim_cur; + return 0; + +#else + syslog(LOG_ERR, "bump_nofile: cannot extend file limit"); + return -1; +#endif +} + + +static void +setup(struct servtab *sep) +{ + int on = 1; + + if ((sep->se_fd = socket(sep->se_family, sep->se_socktype, 0)) < 0) { + syslog(LOG_ERR, "%s/%s: socket: %m", + sep->se_service, sep->se_proto); + return; + } + if (setsockopt(sep->se_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, + sizeof(on)) < 0) + syslog(LOG_ERR, "setsockopt (SO_REUSEADDR): %m"); + if (bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size) < 0) { + syslog(LOG_ERR, "%s/%s: bind: %m", + sep->se_service, sep->se_proto); + (void) close(sep->se_fd); + sep->se_fd = -1; + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + return; + } + if (sep->se_socktype == SOCK_STREAM) + listen(sep->se_fd, global_queuelen); + + FD_SET(sep->se_fd, &allsock); + nsock++; + if (sep->se_fd > maxsock) { + maxsock = sep->se_fd; + if (maxsock > rlim_ofile_cur - FD_MARGIN) + bump_nofile(); + } +} + +static void +config(int signum) +{ + struct servtab *sep, *cp, **sepp; + sigset_t oldmask; + unsigned n; + + (void)signum; + if (setconfig() == NULL) + return; + + for (sep = servtab; sep; sep = sep->se_next) + sep->se_checked = 0; + while ((cp = getconfigent()) != NULL) { + for (sep = servtab; sep; sep = sep->se_next) + if (strcmp(sep->se_service, cp->se_service) == 0 && + strcmp(sep->se_proto, cp->se_proto) == 0) + break; + if (sep != 0) { + int i; + +#define SWAP(type, a, b) {type c=(type)a; (type)a=(type)b; (type)b=(type)c;} + + sigprocmask(SIG_BLOCK, &emptymask, &oldmask); + /* + * sep->se_wait may be holding the pid of a daemon + * that we're waiting for. If so, don't overwrite + * it unless the config file explicitly says don't + * wait. + */ + if ( +#ifndef INETD_UNSUPPORT_BILTIN + cp->se_bi == 0 && +#endif + (sep->se_wait == 1 || cp->se_wait == 0)) + sep->se_wait = cp->se_wait; + if (cp->se_max != sep->se_max) + SWAP(int, cp->se_max, sep->se_max); + if (cp->se_user) + SWAP(char *, sep->se_user, cp->se_user); + if (cp->se_group) + SWAP(char *, sep->se_group, cp->se_group); + if (cp->se_server) + SWAP(char *, sep->se_server, cp->se_server); + for (i = 0; i < MAXARGV; i++) + SWAP(char *, sep->se_argv[i], cp->se_argv[i]); +#undef SWAP + sigprocmask(SIG_SETMASK, &oldmask, NULL); + freeconfig(cp); + } else { + sep = enter(cp); + } + sep->se_checked = 1; + + switch (sep->se_family) { + case AF_UNIX: + if (sep->se_fd != -1) + break; + (void)unlink(sep->se_service); + n = strlen(sep->se_service); + if (n > sizeof(sep->se_ctrladdr_un.sun_path) - 1) + n = sizeof(sep->se_ctrladdr_un.sun_path) - 1; + strncpy(sep->se_ctrladdr_un.sun_path, sep->se_service, n); + sep->se_ctrladdr_un.sun_family = AF_UNIX; + sep->se_ctrladdr_size = n + + sizeof sep->se_ctrladdr_un.sun_family; + setup(sep); + break; + case AF_INET: + sep->se_ctrladdr_in.sin_family = AF_INET; + sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in; + { + u_short port = htons(atoi(sep->se_service)); + + if (!port) { + struct servent *sp; + sp = getservbyname(sep->se_service, + sep->se_proto); + if (sp == 0) { + syslog(LOG_ERR, + "%s/%s: unknown service", + sep->se_service, sep->se_proto); + continue; + } + port = sp->s_port; + } + if (port != sep->se_ctrladdr_in.sin_port) { + sep->se_ctrladdr_in.sin_port = port; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + sep->se_fd = -1; + } + if (sep->se_fd == -1) + setup(sep); + } + } + } + if (fconfig) { + (void) fclose(fconfig); + fconfig = NULL; + } + /* + * Purge anything not looked at above. + */ + sigprocmask(SIG_SETMASK, &blockmask, &oldmask); + sepp = &servtab; + while ((sep = *sepp) != NULL) { + if (sep->se_checked) { + sepp = &sep->se_next; + continue; + } + *sepp = sep->se_next; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + if (sep->se_family == AF_UNIX) + (void)unlink(sep->se_service); + freeconfig(sep); + free((char *)sep); + } + sigprocmask(SIG_SETMASK, &oldmask, NULL); +} + + + +static void +reapchild(int signum) +{ + int status; + int pid; + struct servtab *sep; + + (void)signum; + for (;;) { + pid = wait3(&status, WNOHANG, (struct rusage *)0); + if (pid <= 0) + break; + for (sep = servtab; sep; sep = sep->se_next) + if (sep->se_wait == pid) { + if (WIFEXITED(status) && WEXITSTATUS(status)) + syslog(LOG_WARNING, + "%s: exit status 0x%x", + sep->se_server, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + syslog(LOG_WARNING, + "%s: exit signal 0x%x", + sep->se_server, WTERMSIG(status)); + sep->se_wait = 1; + FD_SET(sep->se_fd, &allsock); + nsock++; + } + } +} + +static void +retry(int signum) +{ + struct servtab *sep; + + (void)signum; + timingout = 0; + for (sep = servtab; sep; sep = sep->se_next) { + if (sep->se_fd == -1) { + switch (sep->se_family) { + case AF_UNIX: + case AF_INET: + setup(sep); + break; + } + } + } +} + +static void +goaway(int signum) +{ + struct servtab *sep; + + (void)signum; + for (sep = servtab; sep; sep = sep->se_next) + if (sep->se_fd != -1 && sep->se_family == AF_UNIX) + (void)unlink(sep->se_service); + (void)unlink(_PATH_INETDPID); + exit(0); +} + + + +extern int +inetd_main(int argc, char *argv[]) +{ + struct servtab *sep; + struct passwd *pwd; + struct group *grp = NULL; + struct sigaction sa; + int ch, pid; + gid_t gid; + +#ifdef INETD_UNSUPPORT_BILTIN +# define dofork 1 +#else + int dofork; + extern char **environ; +#endif + + gid = getgid(); + setgroups(1, &gid); + +#ifndef INETD_UNSUPPORT_BILTIN + Argv = argv; + if (environ == 0 || *environ == 0) + environ = argv; + while (*environ) + environ++; + LastArg = environ[-1] + strlen(environ[-1]); +#endif + + while ((ch = getopt(argc, argv, "q:")) != EOF) + switch(ch) { + case 'q': + global_queuelen = atoi(optarg); + if (global_queuelen < 8) global_queuelen=8; + break; + default: + show_usage(); // "[-q len] [conf]" + } + argc -= optind; + argv += optind; + + if (argc > 0) + CONFIG = argv[0]; + + daemon(0, 0); + openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + { + FILE *fp; + + if ((fp = fopen(_PATH_INETDPID, "w")) != NULL) { + fprintf(fp, "%u\n", getpid()); + (void)fclose(fp); + } + } + +#ifdef RLIMIT_NOFILE + if (getrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) { + syslog(LOG_ERR, "getrlimit: %m"); + } else { + rlim_ofile_cur = rlim_ofile.rlim_cur; + if (rlim_ofile_cur == RLIM_INFINITY) /* ! */ + rlim_ofile_cur = OPEN_MAX; + } +#endif + + config(0); + + sigemptyset(&emptymask); + sigemptyset(&blockmask); + sigaddset(&blockmask, SIGCHLD); + sigaddset(&blockmask, SIGHUP); + sigaddset(&blockmask, SIGALRM); + + memset(&sa, 0, sizeof(sa)); + sa.sa_mask = blockmask; + sa.sa_handler = retry; + sigaction(SIGALRM, &sa, NULL); + sa.sa_handler = config; + sigaction(SIGHUP, &sa, NULL); + sa.sa_handler = reapchild; + sigaction(SIGCHLD, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGINT, &sa, NULL); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, NULL); + + { + /* space for daemons to overwrite environment for ps */ +#define DUMMYSIZE 100 + char dummy[DUMMYSIZE]; + + (void)memset(dummy, 'x', DUMMYSIZE - 1); + dummy[DUMMYSIZE - 1] = '\0'; + + (void)setenv("inetd_dummy", dummy, 1); + } + + for (;;) { + int n, ctrl; + fd_set readable; + + if (nsock == 0) { + sigprocmask(SIG_BLOCK, &blockmask, NULL); + while (nsock == 0) + sigsuspend(&emptymask); + sigprocmask(SIG_SETMASK, &emptymask, NULL); + } + readable = allsock; + if ((n = select(maxsock + 1, &readable, (fd_set *)0, + (fd_set *)0, (struct timeval *)0)) <= 0) { + if (n < 0 && errno != EINTR) + syslog(LOG_WARNING, "select: %m"); + sleep(1); + continue; + } + for (sep = servtab; n && sep; sep = sep->se_next) + if (sep->se_fd != -1 && FD_ISSET(sep->se_fd, &readable)) { + n--; + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { + /* Fixed AGC */ + fcntl(sep->se_fd, F_SETFL, O_NDELAY); + /* --------- */ + ctrl = accept(sep->se_fd, NULL, NULL); + fcntl(sep->se_fd, F_SETFL, 0); + if (ctrl < 0) { + if (errno == EINTR || errno == EWOULDBLOCK) + continue; + syslog(LOG_WARNING, "accept (for %s): %m", + sep->se_service); + continue; + } + } else + ctrl = sep->se_fd; + sigprocmask(SIG_BLOCK, &blockmask, NULL); + pid = 0; +#ifndef INETD_UNSUPPORT_BILTIN + dofork = (sep->se_bi == 0 || sep->se_bi->bi_fork); +#endif + if (dofork) { + if (sep->se_count++ == 0) + (void)gettimeofday(&sep->se_time, + (struct timezone *)0); + else if (sep->se_count >= sep->se_max) { + struct timeval now; + + (void)gettimeofday(&now, (struct timezone *)0); + if (now.tv_sec - sep->se_time.tv_sec > + CNT_INTVL) { + sep->se_time = now; + sep->se_count = 1; + } else { + syslog(LOG_ERR, + "%s/%s server failing (looping), service terminated", + sep->se_service, sep->se_proto); + FD_CLR(sep->se_fd, &allsock); + (void) close(sep->se_fd); + sep->se_fd = -1; + sep->se_count = 0; + nsock--; + sigprocmask(SIG_SETMASK, &emptymask, + NULL); + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + continue; + } + } + pid = fork(); + } + if (pid < 0) { + syslog(LOG_ERR, "fork: %m"); + if (sep->se_socktype == SOCK_STREAM) + close(ctrl); + sigprocmask(SIG_SETMASK, &emptymask, NULL); + sleep(1); + continue; + } + if (pid && sep->se_wait) { + sep->se_wait = pid; + FD_CLR(sep->se_fd, &allsock); + nsock--; + } + sigprocmask(SIG_SETMASK, &emptymask, NULL); + if (pid == 0) { +#ifndef INETD_UNSUPPORT_BILTIN + if (sep->se_bi) + (*sep->se_bi->bi_fn)(ctrl, sep); + else +#endif + { + if ((pwd = getpwnam(sep->se_user)) == NULL) { + syslog_err_and_discard_dg( + sep->se_socktype, + "getpwnam: %s: No such user", + sep->se_user); + } + if (sep->se_group && + (grp = getgrnam(sep->se_group)) == NULL) { + syslog_err_and_discard_dg( + sep->se_socktype, + "getgrnam: %s: No such group", + sep->se_group); + } + /* + * Ok. There are four cases here: + * 1. nonroot user, no group specified + * 2. nonroot user, some group specified + * 3. root user, no group specified + * 4. root user, some group specified + * In cases 2 and 4 we setgid to the specified + * group. In cases 1 and 2 we run initgroups + * to run with the groups of the given user. + * In case 4 we do setgroups to run with the + * given group. In case 3 we do nothing. + */ + if (pwd->pw_uid) { + if (sep->se_group) + pwd->pw_gid = grp->gr_gid; + setgid((gid_t)pwd->pw_gid); + initgroups(pwd->pw_name, pwd->pw_gid); + setuid((uid_t)pwd->pw_uid); + } else if (sep->se_group) { + setgid((gid_t)grp->gr_gid); + setgroups(1, &grp->gr_gid); + } + dup2(ctrl, 0); + close(ctrl); + dup2(0, 1); + dup2(0, 2); +#ifdef RLIMIT_NOFILE + if (rlim_ofile.rlim_cur != rlim_ofile_cur) { + if (setrlimit(RLIMIT_NOFILE, + &rlim_ofile) < 0) + syslog(LOG_ERR,"setrlimit: %m"); + } +#endif + for (ctrl = rlim_ofile_cur-1; --ctrl > 2; ) + (void)close(ctrl); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigaction(SIGPIPE, &sa, NULL); + + execv(sep->se_server, sep->se_argv); + syslog_err_and_discard_dg(sep->se_socktype, + "execv %s: %m", sep->se_server); + } + } + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + } + } +} + + +/* + * Internet services provided internally by inetd: + */ +#define BUFSIZE 4096 + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO +/* Echo service -- echo data back */ +static void +echo_stream(int s, struct servtab *sep) +{ + char buffer[BUFSIZE]; + int i; + + setproctitle(sep->se_service, s); + while ((i = read(s, buffer, sizeof(buffer))) > 0 && + write(s, buffer, i) > 0) + ; + exit(0); +} + +/* Echo service -- echo data back */ +static void +echo_dg(int s, struct servtab *sep) +{ + char buffer[BUFSIZE]; + int i; + size_t size; + struct sockaddr sa; + + (void)sep; + + size = sizeof(sa); + if ((i = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size)) < 0) + return; + (void) sendto(s, buffer, i, 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_ECHO */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD +/* Discard service -- ignore data */ +static void +discard_stream(int s, struct servtab *sep) +{ + char buffer[BUFSIZE]; + + setproctitle(sep->se_service, s); + while ((errno = 0, read(s, buffer, sizeof(buffer)) > 0) || + errno == EINTR) + ; + exit(0); +} + +/* Discard service -- ignore data */ +static void +discard_dg(int s, struct servtab *sep) +{ + char buffer[BUFSIZE]; + (void)sep; + read(s, buffer, sizeof(buffer)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DISCARD */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN +#include +#define LINESIZ 72 +static char ring[128]; +static char *endring; + +static void +initring(void) +{ + int i; + + endring = ring; + + for (i = 0; i <= 128; ++i) + if (isprint(i)) + *endring++ = i; +} + +/* Character generator */ +static void +chargen_stream(int s, struct servtab *sep) +{ + char *rs; + int len; + char text[LINESIZ+2]; + + setproctitle(sep->se_service, s); + + if (!endring) { + initring(); + rs = ring; + } + + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + for (rs = ring;;) { + if ((len = endring - rs) >= LINESIZ) + BCOPY(rs, text, LINESIZ); + else { + BCOPY(rs, text, len); + BCOPY(ring, text + len, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + if (write(s, text, sizeof(text)) != sizeof(text)) + break; + } + exit(0); +} + +/* Character generator */ +static void +chargen_dg(int s, struct servtab *sep) +{ + struct sockaddr sa; + static char *rs; + size_t len, size; + char text[LINESIZ+2]; + + (void)sep; + + if (endring == 0) { + initring(); + rs = ring; + } + + size = sizeof(sa); + if (recvfrom(s, text, sizeof(text), 0, &sa, &size) < 0) + return; + + if ((len = endring - rs) >= LINESIZ) + BCOPY(rs, text, LINESIZ); + else { + BCOPY(rs, text, len); + BCOPY(ring, text + len, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + (void) sendto(s, text, sizeof(text), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_CHARGEN */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_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 long +machtime(void) +{ + struct timeval tv; + + if (gettimeofday(&tv, (struct timezone *)0) < 0) { + fprintf(stderr, "Unable to get time of day\n"); + return (0L); + } + return (htonl((long)tv.tv_sec + 2208988800UL)); +} + +static void +machtime_stream(int s, struct servtab *sep) +{ + long result; + (void)sep; + + result = machtime(); + write(s, (char *) &result, sizeof(result)); +} + +static void +machtime_dg(int s, struct servtab *sep) +{ + long result; + struct sockaddr sa; + size_t size; + (void)sep; + + size = sizeof(sa); + if (recvfrom(s, (char *)&result, sizeof(result), 0, &sa, &size) < 0) + return; + result = machtime(); + (void) sendto(s, (char *) &result, sizeof(result), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_TIME */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME +/* Return human-readable time of day */ +static int +human_readable_time_sprintf(char *buffer) +{ + time_t clocc = time(NULL); + + return sprintf(buffer, "%.24s\r\n", ctime(&clocc)); +} + +static void +daytime_stream(int s, struct servtab *sep) +{ + char buffer[256]; + size_t st = human_readable_time_sprintf(buffer); + + (void)sep; + + write(s, buffer, st); +} + +/* Return human-readable time of day */ +static void +daytime_dg(int s, struct servtab *sep) +{ + char buffer[256]; + struct sockaddr sa; + size_t size; + + (void)sep; + + size = sizeof(sa); + if (recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size) < 0) + return; + size = human_readable_time_sprintf(buffer); + sendto(s, buffer, size, 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BILTIN_DAYTIME */ -- 2.25.1