diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..3a9e0aa
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,5620 @@
+
+Changes with nginx 1.0.14 15 Mar 2012
+
+ *) Security: content of previously freed memory might be sent to a
+ client if backend returned specially crafted response.
+ Thanks to Matthew Daley.
+
+
+Changes with nginx 1.0.13 05 Mar 2012
+
+ *) Feature: the "return" and "error_page" directives can now be used to
+ return 307 redirections.
+
+ *) Bugfix: a segmentation fault might occur in a worker process if the
+ "resolver" directive was used and there was no "error_log" directive
+ specified at global level.
+ Thanks to Roman Arutyunyan.
+
+ *) Bugfix: memory leaks.
+ Thanks to Lanshun Zhou.
+
+ *) Bugfix: nginx might log incorrect error "upstream prematurely closed
+ connection" instead of correct "upstream sent too big header" one.
+ Thanks to Feibo Li.
+
+ *) Bugfix: on ZFS filesystem disk cache size might be calculated
+ incorrectly; the bug had appeared in 1.0.1.
+
+ *) Bugfix: the number of internal redirects to named locations was not
+ limited.
+
+ *) Bugfix: temporary files might be not removed if the "proxy_store"
+ directive was used with SSI includes.
+
+ *) Bugfix: in some cases non-cacheable variables (such as the $args
+ variable) returned old empty cached value.
+
+ *) Bugfix: the "proxy_redirect" directives might be inherited
+ incorrectly.
+
+ *) Bugfix: nginx could not be built with the ngx_http_perl_module if the
+ --with-openssl option was used.
+
+ *) Bugfix: nginx could not be built by the icc 12.1 compiler.
+
+
+Changes with nginx 1.0.12 06 Feb 2012
+
+ *) Feature: the "TLSv1.1" and "TLSv1.2" parameters of the
+ "ssl_protocols" directive.
+
+ *) Feature: the "if" SSI command supports captures in regular
+ expressions.
+
+ *) Bugfix: the "if" SSI command did not work inside the "block" command.
+
+ *) Bugfix: in AIO error handling on FreeBSD.
+
+ *) Bugfix: in the OpenSSL library initialization.
+
+ *) Bugfix: the "worker_cpu_affinity" directive might not work.
+
+ *) Bugfix: the "limit_conn_log_level" and "limit_req_log_level"
+ directives might not work.
+
+ *) Bugfix: the "read_ahead" directive might not work combined with
+ "try_files" and "open_file_cache".
+
+ *) Bugfix: the "proxy_cache_use_stale" directive with "error" parameter
+ did not return answer from cache if there were no live upstreams.
+
+ *) Bugfix: a segmentation fault might occur in a worker process if small
+ time was used in the "inactive" parameter of the "proxy_cache_path"
+ directive.
+
+ *) Bugfix: responses from cache might hang.
+
+ *) Bugfix: in error handling while connecting to a backend.
+ Thanks to Piotr Sikora.
+
+ *) Bugfix: in the "epoll" event method.
+ Thanks to Yichun Zhang.
+
+ *) Bugfix: the $sent_http_cache_control variable might contain a wrong
+ value if the "expires" directive was used.
+ Thanks to Yichun Zhang.
+
+ *) Bugfix: the "limit_rate" directive did not allow to use full
+ throughput, even if limit value was very high.
+
+ *) Bugfix: the "sendfile_max_chunk" directive did not work, if the
+ "limit_rate" directive was used.
+
+ *) Bugfix: nginx could not be built on Solaris; the bug had appeared in
+ 1.0.11.
+
+ *) Bugfix: in the ngx_http_scgi_module.
+
+ *) Bugfix: in the ngx_http_mp4_module.
+
+
+Changes with nginx 1.0.11 15 Dec 2011
+
+ *) Change: now double quotes are encoded in an "echo" SSI-command
+ output.
+ Thanks to Zaur Abasmirzoev.
+
+ *) Feature: the "image_filter_sharpen" directive.
+
+ *) Bugfix: a segmentation fault might occur in a worker process if SNI
+ was used; the bug had appeared in 1.0.9.
+
+ *) Bugfix: SIGWINCH signal did not work after first binary upgrade; the
+ bug had appeared in 1.0.9.
+
+ *) Bugfix: the "If-Modified-Since", "If-Range", etc. client request
+ header lines might be passed to backend while caching; or not passed
+ without caching if caching was enabled in another part of the
+ configuration.
+
+ *) Bugfix: in the "scgi_param" directive, if complex parameters were
+ used.
+
+ *) Bugfix: "add_header" and "expires" directives did not work if a
+ request was proxied and response status code was 206.
+
+ *) Bugfix: in the "expires @time" directive.
+
+ *) Bugfix: in the ngx_http_flv_module.
+ Thanks to Piotr Sikora.
+
+ *) Bugfix: in the ngx_http_mp4_module.
+
+ *) Bugfix: nginx could not be built on FreeBSD 10.
+
+ *) Bugfix: nginx could not be built on AIX.
+
+
+Changes with nginx 1.0.10 15 Nov 2011
+
+ *) Bugfix: a segmentation fault might occur in a worker process if
+ resolver got a big DNS response.
+ Thanks to Ben Hawkes.
+
+ *) Bugfix: in cache key calculation if internal MD5 implementation was
+ used; the bug had appeared in 1.0.4.
+
+ *) Bugfix: the module ngx_http_mp4_module sent incorrect
+ "Content-Length" response header line if the "start" argument was
+ used.
+ Thanks to Piotr Sikora.
+
+
+Changes with nginx 1.0.9 01 Nov 2011
+
+ *) Change: now the 0x7F-0x1F characters are escaped as \xXX in an
+ access_log.
+
+ *) Change: now SIGWINCH signal works only in daemon mode.
+
+ *) Feature: "proxy/fastcgi/scgi/uwsgi_ignore_headers" directives support
+ the following additional values: X-Accel-Limit-Rate,
+ X-Accel-Buffering, X-Accel-Charset.
+
+ *) Feature: decrease of memory consumption if SSL is used.
+
+ *) Feature: accept filters are now supported on NetBSD.
+
+ *) Feature: the "uwsgi_buffering" and "scgi_buffering" directives.
+ Thanks to Peter Smit.
+
+ *) Bugfix: a segmentation fault occurred on start or while
+ reconfiguration if the "ssl" directive was used at http level and
+ there was no "ssl_certificate" defined.
+
+ *) Bugfix: some UTF-8 characters were processed incorrectly.
+ Thanks to Alexey Kuts.
+
+ *) Bugfix: the ngx_http_rewrite_module directives specified at "server"
+ level were executed twice if no matching locations were defined.
+
+ *) Bugfix: a socket leak might occurred if "aio sendfile" was used.
+
+ *) Bugfix: connections with fast clients might be closed after
+ send_timeout if file AIO was used.
+
+ *) Bugfix: in the ngx_http_autoindex_module.
+
+ *) Bugfix: the module ngx_http_mp4_module did not support seeking on
+ 32-bit platforms.
+
+ *) Bugfix: non-cacheable responses might be cached if
+ "proxy_cache_bypass" directive was used.
+ Thanks to John Ferlito.
+
+ *) Bugfix: cached responses with an empty body were returned
+ incorrectly; the bug had appeared in 0.8.31.
+
+ *) Bugfix: 201 responses of the ngx_http_dav_module were incorrect; the
+ bug had appeared in 0.8.32.
+
+ *) Bugfix: in the "return" directive.
+
+ *) Bugfix: the "ssl_verify_client", "ssl_verify_depth", and
+ "ssl_prefer_server_ciphers" directives might work incorrectly if SNI
+ was used.
+
+
+Changes with nginx 1.0.8 01 Oct 2011
+
+ *) Bugfix: nginx could not be built --with-http_mp4_module and without
+ --with-debug option.
+
+
+Changes with nginx 1.0.7 30 Sep 2011
+
+ *) Change: now if total size of all ranges is greater than source
+ response size, then nginx disables ranges and returns just the source
+ response.
+
+ *) Feature: the "max_ranges" directive.
+
+ *) Feature: the module ngx_http_mp4_module.
+
+ *) Feature: the "worker_aio_requests" directive.
+
+ *) Bugfix: if nginx was built --with-file-aio it could not be run on
+ Linux kernel which did not support AIO.
+
+ *) Bugfix: in Linux AIO error processing.
+ Thanks to Hagai Avrahami.
+
+ *) Bugfix: in Linux AIO combined with open_file_cache.
+
+ *) Bugfix: open_file_cache did not update file info on retest if file
+ was not atomically changed.
+
+ *) Bugfix: reduced memory consumption for long-lived requests.
+
+ *) Bugfix: in the "proxy/fastcgi/scgi/uwsgi_ignore_client_abort"
+ directives.
+
+ *) Bugfix: nginx could not be built on MacOSX 10.7.
+
+ *) Bugfix: request body might be processed incorrectly if client used
+ pipelining.
+
+ *) Bugfix: in the "request_body_in_single_buf" directive.
+
+ *) Bugfix: in "proxy_set_body" and "proxy_pass_request_body" directives
+ if SSL connection to backend was used.
+
+ *) Bugfix: nginx hogged CPU if all servers in an upstream were marked as
+ "down".
+
+ *) Bugfix: a segmentation fault might occur during reconfiguration if
+ ssl_session_cache was defined but not used in previous configuration.
+
+ *) Bugfix: a segmentation fault might occur in a worker process if many
+ backup servers were used in an upstream.
+
+
+Changes with nginx 1.0.6 29 Aug 2011
+
+ *) Feature: cache loader run time decrease.
+
+ *) Feature: loading time decrease of configuration with large number of
+ HTTPS sites.
+
+ *) Feature: now nginx supports ECDHE key exchange ciphers.
+ Thanks to Adrian Kotelba.
+
+ *) Feature: the "lingering_close" directive.
+
+ *) Feature: now shared zones and caches use POSIX semaphores on Solaris.
+ Thanks to Den Ivanov.
+
+ *) Bugfix: nginx could not be built on Linux 3.0.
+
+ *) Bugfix: a segmentation fault might occur in a worker process if
+ "fastcgi/scgi/uwsgi_param" directives were used with values starting
+ with "HTTP_"; the bug had appeared in 0.8.40.
+
+ *) Bugfix: in closing connection for pipelined requests.
+
+ *) Bugfix: nginx did not disable gzipping if client sent "gzip;q=0" in
+ "Accept-Encoding" request header line.
+
+ *) Bugfix: in timeout in unbuffered proxied mode.
+
+ *) Bugfix: memory leaks when a "proxy_pass" directive contains variables
+ and proxies to an HTTPS backend.
+
+ *) Bugfix: in parameter validaiton of a "proxy_pass" directive with
+ variables.
+ Thanks to Lanshun Zhou.
+
+ *) Bugfix: SSL did not work on QNX.
+
+ *) Bugfix: SSL modules could not be built by gcc 4.6 without
+ --with-debug option.
+
+
+Changes with nginx 1.0.5 19 Jul 2011
+
+ *) Change: now default SSL ciphers are "HIGH:!aNULL:!MD5".
+ Thanks to Rob Stradling.
+
+ *) Feature: the "referer_hash_max_size" and "referer_hash_bucket_size"
+ directives.
+ Thanks to Witold Filipczyk.
+
+ *) Feature: $uid_reset variable.
+
+ *) Bugfix: a segmentation fault might occur in a worker process, if a
+ caching was used.
+ Thanks to Lanshun Zhou.
+
+ *) Bugfix: worker processes may got caught in an endless loop during
+ reconfiguration, if a caching was used; the bug had appeared in
+ 0.8.48.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: "stalled cache updating" alert.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 1.0.4 01 Jun 2011
+
+ *) Change: now regular expressions case sensitivity in the "map"
+ directive is given by prefixes "~" or "~*".
+
+ *) Feature: now shared zones and caches use POSIX semaphores on Linux.
+ Thanks to Denis F. Latypoff.
+
+ *) Bugfix: "stalled cache updating" alert.
+
+ *) Bugfix: nginx could not be built --without-http_auth_basic_module;
+ the bug had appeared in 1.0.3.
+
+
+Changes with nginx 1.0.3 25 May 2011
+
+ *) Feature: the "auth_basic_user_file" directive supports "$apr1",
+ "{PLAIN}", and "{SSHA}" password encryption methods.
+ Thanks to Maxim Dounin.
+
+ *) Feature: the "geoip_org" directive and $geoip_org variable.
+ Thanks to Alexander Uskov, Arnaud Granal, and Denis F. Latypoff.
+
+ *) Feature: ngx_http_geo_module and ngx_http_geoip_module support IPv4
+ addresses mapped to IPv6 addresses.
+
+ *) Bugfix: a segmentation fault occurred in a worker process during
+ testing IPv4 address mapped to IPv6 address, if access or deny rules
+ were defined only for IPv6; the bug had appeared in 0.8.22.
+
+ *) Bugfix: a cached response may be broken if proxy/fastcgi/scgi/
+ uwsgi_cache_bypass and proxy/fastcgi/scgi/uwsgi_no_cache directive
+ values were different; the bug had appeared in 0.8.46.
+
+
+Changes with nginx 1.0.2 10 May 2011
+
+ *) Feature: now shared zones and caches use POSIX semaphores.
+
+ *) Bugfix: in the "rotate" parameter of the "image_filter" directive.
+ Thanks to Adam Bocim.
+
+ *) Bugfix: nginx could not be built on Solaris; the bug had appeared in
+ 1.0.1.
+
+
+Changes with nginx 1.0.1 03 May 2011
+
+ *) Change: now the "split_clients" directive uses MurmurHash2 algorithm
+ because of better distribution.
+ Thanks to Oleg Mamontov.
+
+ *) Change: now long strings starting with zero are not considered as
+ false values.
+ Thanks to Maxim Dounin.
+
+ *) Change: now nginx uses a default listen backlog value 511 on Linux.
+
+ *) Feature: the $upstream_... variables may be used in the SSI and perl
+ modules.
+
+ *) Bugfix: now nginx limits better disk cache size.
+ Thanks to Oleg Mamontov.
+
+ *) Bugfix: a segmentation fault might occur while parsing incorrect IPv4
+ address; the bug had appeared in 0.9.3.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx could not be built by gcc 4.6 without --with-debug
+ option.
+
+ *) Bugfix: nginx could not be built on Solaris 9 and earlier; the bug
+ had appeared in 0.9.3.
+ Thanks to Dagobert Michelsen.
+
+ *) Bugfix: $request_time variable had invalid values if subrequests were
+ used; the bug had appeared in 0.8.47.
+ Thanks to Igor A. Valcov.
+
+
+Changes with nginx 1.0.0 12 Apr 2011
+
+ *) Bugfix: a cache manager might hog CPU after reload.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: an "image_filter crop" directive worked incorrectly coupled
+ with an "image_filter rotate 180" directive.
+
+ *) Bugfix: a "satisfy any" directive disabled custom 401 error page.
+
+
+Changes with nginx 0.9.7 04 Apr 2011
+
+ *) Feature: now keepalive connections may be closed premature, if there
+ are no free worker connections.
+ Thanks to Maxim Dounin.
+
+ *) Feature: the "rotate" parameter of the "image_filter" directive.
+ Thanks to Adam Bocim.
+
+ *) Bugfix: a case when a backend in "fastcgi_pass", "scgi_pass", or
+ "uwsgi_pass" directives is given by expression and refers to a
+ defined upstream.
+
+
+Changes with nginx 0.9.6 21 Mar 2011
+
+ *) Feature: the "map" directive supports regular expressions as value of
+ the first parameter.
+
+ *) Feature: $time_iso8601 access_log variable.
+ Thanks to Michael Lustfield.
+
+
+Changes with nginx 0.9.5 21 Feb 2011
+
+ *) Change: now nginx uses a default listen backlog value -1 on Linux.
+ Thanks to Andrei Nigmatulin.
+
+ *) Feature: the "utf8" parameter of "geoip_country" and "geoip_city"
+ directives.
+ Thanks to Denis F. Latypoff.
+
+ *) Bugfix: in a default "proxy_redirect" directive if "proxy_pass"
+ directive has no URI part.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: an "error_page" directive did not work with nonstandard error
+ codes; the bug had appeared in 0.8.53.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.9.4 21 Jan 2011
+
+ *) Feature: the "server_name" directive supports the $hostname variable.
+
+ *) Feature: 494 code for "Request Header Too Large" error.
+
+
+Changes with nginx 0.9.3 13 Dec 2010
+
+ *) Bugfix: if there was a single server for given IPv6 address:port
+ pair, then captures in regular expressions in a "server_name"
+ directive did not work.
+
+ *) Bugfix: nginx could not be built on Solaris; the bug had appeared in
+ 0.9.0.
+
+
+Changes with nginx 0.9.2 06 Dec 2010
+
+ *) Feature: the "If-Unmodified-Since" client request header line
+ support.
+
+ *) Workaround: fallback to accept() syscall if accept4() was not
+ implemented; the issue had appeared in 0.9.0.
+
+ *) Bugfix: nginx could not be built on Cygwin; the bug had appeared in
+ 0.9.0.
+
+ *) Bugfix: for OpenSSL vulnerability CVE-2010-4180.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.9.1 30 Nov 2010
+
+ *) Bugfix: "return CODE message" directives did not work; the bug had
+ appeared in 0.9.0.
+
+
+Changes with nginx 0.9.0 29 Nov 2010
+
+ *) Feature: the "keepalive_disable" directive.
+
+ *) Feature: the "map" directive supports variables as value of a defined
+ variable.
+
+ *) Feature: the "map" directive supports empty strings as value of the
+ first parameter.
+
+ *) Feature: the "map" directive supports expressions as the first
+ parameter.
+
+ *) Feature: nginx(8) manual page.
+ Thanks to Sergey Osokin.
+
+ *) Feature: Linux accept4() support.
+ Thanks to Simon Liu.
+
+ *) Workaround: elimination of Linux linker warning about "sys_errlist"
+ and "sys_nerr"; the warning had appeared in 0.8.35.
+
+ *) Bugfix: a segmentation fault might occur in a worker process, if the
+ "auth_basic" directive was used.
+ Thanks to Michail Laletin.
+
+ *) Bugfix: compatibility with ngx_http_eval_module; the bug had appeared
+ in 0.8.42.
+
+
+Changes with nginx 0.8.53 18 Oct 2010
+
+ *) Feature: now the "error_page" directive allows to change a status
+ code in a redirect.
+
+ *) Feature: the "gzip_disable" directive supports special "degradation"
+ mask.
+
+ *) Bugfix: a socket leak might occurred if file AIO was used.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: if the first server had no "listen" directive and there was
+ no explicit default server, then a next server with a "listen"
+ directive became the default server; the bug had appeared in 0.8.21.
+
+
+Changes with nginx 0.8.52 28 Sep 2010
+
+ *) Bugfix: nginx used SSL mode for a listen socket if any listen option
+ was set; the bug had appeared in 0.8.51.
+
+
+Changes with nginx 0.8.51 27 Sep 2010
+
+ *) Change: the "secure_link_expires" directive has been canceled.
+
+ *) Change: a logging level of resolver errors has been lowered from
+ "alert" to "error".
+
+ *) Feature: now a listen socket "ssl" parameter may be set several
+ times.
+
+
+Changes with nginx 0.8.50 02 Sep 2010
+
+ *) Feature: the "secure_link", "secure_link_md5", and
+ "secure_link_expires" directives of the ngx_http_secure_link_module.
+
+ *) Feature: the -q switch.
+ Thanks to Gena Makhomed.
+
+ *) Bugfix: worker processes may got caught in an endless loop during
+ reconfiguration, if a caching was used; the bug had appeared in
+ 0.8.48.
+
+ *) Bugfix: in the "gzip_disable" directive.
+ Thanks to Derrick Petzold.
+
+ *) Bugfix: nginx/Windows could not send stop, quit, reopen, and reload
+ signals to a process run in other session.
+
+
+Changes with nginx 0.8.49 09 Aug 2010
+
+ *) Feature: the "image_filter_jpeg_quality" directive supports
+ variables.
+
+ *) Bugfix: a segmentation fault might occur in a worker process, if the
+ $geoip_region_name variables was used; the bug had appeared in
+ 0.8.48.
+
+ *) Bugfix: errors intercepted by error_page were cached only for next
+ request; the bug had appeared in 0.8.48.
+
+
+Changes with nginx 0.8.48 03 Aug 2010
+
+ *) Change: now the "server_name" directive default value is an empty
+ name "".
+ Thanks to Gena Makhomed.
+
+ *) Change: now the "server_name_in_redirect" directive default value is
+ "off".
+
+ *) Feature: the $geoip_dma_code, $geoip_area_code, and
+ $geoip_region_name variables.
+ Thanks to Christine McGonagle.
+
+ *) Bugfix: the "proxy_pass", "fastcgi_pass", "uwsgi_pass", and
+ "scgi_pass" directives were not inherited inside "limit_except"
+ blocks.
+
+ *) Bugfix: the "proxy_cache_min_uses", "fastcgi_cache_min_uses"
+ "uwsgi_cache_min_uses", and "scgi_cache_min_uses" directives did not
+ work; the bug had appeared in 0.8.46.
+
+ *) Bugfix: the "fastcgi_split_path_info" directive used incorrectly
+ captures, if only parts of an URI were captured.
+ Thanks to Yuriy Taraday and Frank Enderle.
+
+ *) Bugfix: the "rewrite" directive did not escape a ";" character during
+ copying from URI to query string.
+ Thanks to Daisuke Murase.
+
+ *) Bugfix: the ngx_http_image_filter_module closed a connection, if an
+ image was larger than "image_filter_buffer" size.
+
+
+Changes with nginx 0.8.47 28 Jul 2010
+
+ *) Bugfix: $request_time variable had invalid values for subrequests.
+
+ *) Bugfix: errors intercepted by error_page could not be cached.
+
+ *) Bugfix: a cache manager process may got caught in an endless loop, if
+ max_size parameter was used; the bug had appeared in 0.8.46.
+
+
+Changes with nginx 0.8.46 19 Jul 2010
+
+ *) Change: now the "proxy_no_cache", "fastcgi_no_cache",
+ "uwsgi_no_cache", and "scgi_no_cache" directives affect on a cached
+ response saving only.
+
+ *) Feature: the "proxy_cache_bypass", "fastcgi_cache_bypass",
+ "uwsgi_cache_bypass", and "scgi_cache_bypass" directives.
+
+ *) Bugfix: nginx did not free memory in cache keys zones if there was an
+ error during working with backend: the memory was freed only after
+ inactivity time or on memory low condition.
+
+
+Changes with nginx 0.8.45 13 Jul 2010
+
+ *) Feature: ngx_http_xslt_filter improvements.
+ Thanks to Laurence Rowe.
+
+ *) Bugfix: SSI response might be truncated after include with
+ wait="yes"; the bug had appeared in 0.7.25.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: the "listen" directive did not support the "setfib=0"
+ parameter.
+
+
+Changes with nginx 0.8.44 05 Jul 2010
+
+ *) Change: now nginx does not cache by default backend responses, if
+ they have a "Set-Cookie" header line.
+
+ *) Feature: the "listen" directive supports the "setfib" parameter.
+ Thanks to Andrew Filonov.
+
+ *) Bugfix: the "sub_filter" directive might change character case on
+ partial match.
+
+ *) Bugfix: compatibility with HP/UX.
+
+ *) Bugfix: compatibility with AIX xlC_r compiler.
+
+ *) Bugfix: nginx treated large SSLv2 packets as plain requests.
+ Thanks to Miroslaw Jaworski.
+
+
+Changes with nginx 0.8.43 30 Jun 2010
+
+ *) Feature: large geo ranges base loading speed-up.
+
+ *) Bugfix: an error_page redirection to "location /zero {return 204;}"
+ without changing status code kept the error body; the bug had
+ appeared in 0.8.42.
+
+ *) Bugfix: nginx might close IPv6 listen socket during reconfiguration.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: the $uid_set variable may be used at any request processing
+ stage.
+
+
+Changes with nginx 0.8.42 21 Jun 2010
+
+ *) Change: now nginx tests locations given by regular expressions, if
+ request was matched exactly by a location given by a prefix string.
+ The previous behavior has been introduced in 0.7.1.
+
+ *) Feature: the ngx_http_scgi_module.
+ Thanks to Manlio Perillo.
+
+ *) Feature: a text answer may be added to a "return" directive.
+
+
+Changes with nginx 0.8.41 15 Jun 2010
+
+ *) Security: nginx/Windows worker might be terminated abnormally if a
+ requested file name has invalid UTF-8 encoding.
+
+ *) Change: now nginx allows to use spaces in a request line.
+
+ *) Bugfix: the "proxy_redirect" directive changed incorrectly a backend
+ "Refresh" response header line.
+ Thanks to Andrey Andreew and Max Sogin.
+
+ *) Bugfix: nginx did not support path without host name in "Destination"
+ request header line.
+
+
+Changes with nginx 0.8.40 07 Jun 2010
+
+ *) Security: now nginx/Windows ignores default file stream name.
+ Thanks to Jose Antonio Vazquez Gonzalez.
+
+ *) Feature: the ngx_http_uwsgi_module.
+ Thanks to Roberto De Ioris.
+
+ *) Feature: a "fastcgi_param" directive with value starting with "HTTP_"
+ overrides a client request header line.
+
+ *) Bugfix: the "If-Modified-Since", "If-Range", etc. client request
+ header lines were passed to FastCGI-server while caching.
+
+ *) Bugfix: listen unix domain socket could not be changed during
+ reconfiguration.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.39 31 May 2010
+
+ *) Bugfix: an inherited "alias" directive worked incorrectly in
+ inclusive location.
+
+ *) Bugfix: in "alias" with variables and "try_files" directives
+ combination.
+
+ *) Bugfix: listen unix domain and IPv6 sockets did not inherit while
+ online upgrade.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.38 24 May 2010
+
+ *) Feature: the "proxy_no_cache" and "fastcgi_no_cache" directives.
+
+ *) Feature: now the "rewrite" directive does a redirect automatically if
+ the $scheme variable is used.
+ Thanks to Piotr Sikora.
+
+ *) Bugfix: now "limit_req" delay directive conforms to the described
+ algorithm.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: the $uid_got variable might not be used in the SSI and perl
+ modules.
+
+
+Changes with nginx 0.8.37 17 May 2010
+
+ *) Feature: the ngx_http_split_clients_module.
+
+ *) Feature: the "map" directive supports keys more than 255 characters.
+
+ *) Bugfix: nginx ignored the "private" and "no-store" values in the
+ "Cache-Control" backend response header line.
+
+ *) Bugfix: a "stub" parameter of an "include" SSI directive was not
+ used, if empty response has 200 status code.
+
+ *) Bugfix: if a proxied or FastCGI request was internally redirected to
+ another proxied or FastCGI location, then a segmentation fault might
+ occur in a worker process; the bug had appeared in 0.8.33.
+ Thanks to Yichun Zhang.
+
+ *) Bugfix: IMAP connections may hang until they timed out while talking
+ to Zimbra server.
+ Thanks to Alan Batie.
+
+
+Changes with nginx 0.8.36 22 Apr 2010
+
+ *) Bugfix: the ngx_http_dav_module handled incorrectly the DELETE, COPY,
+ and MOVE methods for symlinks.
+
+ *) Bugfix: values of the $query_string, $arg_..., etc. variables cached
+ in main request were used by the SSI module in subrequests.
+
+ *) Bugfix: a variable value was repeatedly encoded after each an "echo"
+ SSI-command output; the bug had appeared in 0.6.14.
+
+ *) Bugfix: a worker process hung if a FIFO file was requested.
+ Thanks to Vicente Aguilar and Maxim Dounin.
+
+ *) Bugfix: OpenSSL-1.0.0 compatibility on 64-bit Linux.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx could not be built --without-http-cache; the bug had
+ appeared in 0.8.35.
+
+
+Changes with nginx 0.8.35 01 Apr 2010
+
+ *) Change: now the charset filter runs before the SSI filter.
+
+ *) Feature: the "chunked_transfer_encoding" directive.
+
+ *) Bugfix: an "&" character was not escaped when it was copied in
+ arguments part in a rewrite rule.
+
+ *) Bugfix: nginx might be terminated abnormally while a signal
+ processing or if the directive "timer_resolution" was used on
+ platforms which do not support kqueue or eventport notification
+ methods.
+ Thanks to George Xie and Maxim Dounin.
+
+ *) Bugfix: if temporary files and permanent storage area resided at
+ different file systems, then permanent file modification times were
+ incorrect.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: ngx_http_memcached_module might issue the error message
+ "memcached sent invalid trailer".
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx could not built zlib-1.2.4 library using the library
+ sources.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a segmentation fault occurred in a worker process, if there
+ was large stderr output before FastCGI response; the bug had appeared
+ in 0.8.34.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.34 03 Mar 2010
+
+ *) Bugfix: nginx did not support all ciphers and digests used in client
+ certificates.
+ Thanks to Innocenty Enikeew.
+
+ *) Bugfix: nginx cached incorrectly FastCGI responses if there was large
+ stderr output before response.
+
+ *) Bugfix: nginx did not support HTTPS referrers.
+
+ *) Bugfix: nginx/Windows might not find file if path in configuration
+ was given in other character case; the bug had appeared in 0.8.33.
+
+ *) Bugfix: the $date_local variable has an incorrect value, if the "%s"
+ format was used.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: if ssl_session_cache was not set or was set to "none", then
+ during client certificate verify the error "session id context
+ uninitialized" might occur; the bug had appeared in 0.7.1.
+
+ *) Bugfix: a geo range returned default value if the range included two
+ or more /16 networks and did not begin at /16 network boundary.
+
+ *) Bugfix: a block used in a "stub" parameter of an "include" SSI
+ directive was output with "text/plain" MIME type.
+
+ *) Bugfix: $r->sleep() did not work; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.33 01 Feb 2010
+
+ *) Security: now nginx/Windows ignores trailing spaces in URI.
+ Thanks to Dan Crowley, Core Security Technologies.
+
+ *) Security: now nginx/Windows ignores short files names.
+ Thanks to Dan Crowley, Core Security Technologies.
+
+ *) Change: now keepalive connections after POST requests are not
+ disabled for MSIE 7.0+.
+ Thanks to Adam Lounds.
+
+ *) Workaround: now keepalive connections are disabled for Safari.
+ Thanks to Joshua Sierles.
+
+ *) Bugfix: if a proxied or FastCGI request was internally redirected to
+ another proxied or FastCGI location, then $upstream_response_time
+ variable may have abnormally large value; the bug had appeared in
+ 0.8.7.
+
+ *) Bugfix: a segmentation fault might occur in a worker process, while
+ discarding a request body; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.32 11 Jan 2010
+
+ *) Bugfix: UTF-8 encoding usage in the ngx_http_autoindex_module.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: regular expression named captures worked for two names only.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: now the "localhost" name is used in the "Host" request header
+ line, if an unix domain socket is defined in the "auth_http"
+ directive.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx did not support chunked transfer encoding for 201
+ responses.
+ Thanks to Julian Reich.
+
+ *) Bugfix: if the "expires modified" set date in the past, then a
+ negative number was set in the "Cache-Control" response header line.
+ Thanks to Alex Kapranoff.
+
+
+Changes with nginx 0.8.31 23 Dec 2009
+
+ *) Feature: now the "error_page" directive may redirect the 301 and 302
+ responses.
+
+ *) Feature: the $geoip_city_continent_code, $geoip_latitude, and
+ $geoip_longitude variables.
+ Thanks to Arvind Sundararajan.
+
+ *) Feature: now the ngx_http_image_filter_module deletes always EXIF and
+ other application specific data if the data consume more than 5% of a
+ JPEG file.
+
+ *) Bugfix: nginx closed a connection if a cached response had an empty
+ body.
+ Thanks to Piotr Sikora.
+
+ *) Bugfix: nginx might not be built by gcc 4.x if the -O2 or higher
+ optimization option was used.
+ Thanks to Maxim Dounin and Denis F. Latypoff.
+
+ *) Bugfix: regular expressions in location were always tested in
+ case-sensitive mode; the bug had appeared in 0.8.25.
+
+ *) Bugfix: nginx cached a 304 response if there was the "If-None-Match"
+ header line in a proxied request.
+ Thanks to Tim Dettrick and David Kostal.
+
+ *) Bugfix: nginx/Windows tried to delete a temporary file twice if the
+ file should replace an already existent file.
+
+
+Changes with nginx 0.8.30 15 Dec 2009
+
+ *) Change: now the default buffer size of the
+ "large_client_header_buffers" directive is 8K.
+ Thanks to Andrew Cholakian.
+
+ *) Feature: the conf/fastcgi.conf for simple FastCGI configurations.
+
+ *) Bugfix: nginx/Windows tried to rename a temporary file twice if the
+ file should replace an already existent file.
+
+ *) Bugfix: of "double free or corruption" error issued if host could not
+ be resolved; the bug had appeared in 0.8.22.
+ Thanks to Konstantin Svist.
+
+ *) Bugfix: in libatomic usage on some platforms.
+ Thanks to W-Mark Kubacki.
+
+
+Changes with nginx 0.8.29 30 Nov 2009
+
+ *) Change: now the "009" status code is written to an access log for
+ proxied HTTP/0.9 responses.
+
+ *) Feature: the "addition_types", "charset_types", "gzip_types",
+ "ssi_types", "sub_filter_types", and "xslt_types" directives support
+ an "*" parameter.
+
+ *) Feature: GCC 4.1+ built-in atomic operations usage.
+ Thanks to W-Mark Kubacki.
+
+ *) Feature: the --with-libatomic[=DIR] option in the configure.
+ Thanks to W-Mark Kubacki.
+
+ *) Bugfix: listen unix domain socket had limited access rights.
+
+ *) Bugfix: cached HTTP/0.9 responses were handled incorrectly.
+
+ *) Bugfix: regular expression named captures given by "?P<...>" did not
+ work in a "server_name" directive.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.28 23 Nov 2009
+
+ *) Bugfix: nginx could not be built with the --without-pcre parameter;
+ the bug had appeared in 0.8.25.
+
+
+Changes with nginx 0.8.27 17 Nov 2009
+
+ *) Bugfix: regular expressions did not work in nginx/Windows; the bug
+ had appeared in 0.8.25.
+
+
+Changes with nginx 0.8.26 16 Nov 2009
+
+ *) Bugfix: in captures usage in "rewrite" directive; the bug had
+ appeared in 0.8.25.
+
+ *) Bugfix: nginx could not be built without the --with-debug option; the
+ bug had appeared in 0.8.25.
+
+
+Changes with nginx 0.8.25 16 Nov 2009
+
+ *) Change: now no message is written in an error log if a variable is
+ not found by $r->variable() method.
+
+ *) Feature: the ngx_http_degradation_module.
+
+ *) Feature: regular expression named captures.
+
+ *) Feature: now URI part is not required a "proxy_pass" directive if
+ variables are used.
+
+ *) Feature: now the "msie_padding" directive works for Chrome too.
+
+ *) Bugfix: a segmentation fault occurred in a worker process on low
+ memory condition; the bug had appeared in 0.8.18.
+
+ *) Bugfix: nginx sent gzipped responses to clients those do not support
+ gzip, if "gzip_static on" and "gzip_vary off"; the bug had appeared
+ in 0.8.16.
+
+
+Changes with nginx 0.8.24 11 Nov 2009
+
+ *) Bugfix: nginx always added "Content-Encoding: gzip" response header
+ line in 304 responses sent by ngx_http_gzip_static_module.
+
+ *) Bugfix: nginx could not be built without the --with-debug option; the
+ bug had appeared in 0.8.23.
+
+ *) Bugfix: the "unix:" parameter of the "set_real_ip_from" directive
+ inherited incorrectly from previous level.
+
+ *) Bugfix: in resolving empty name.
+
+
+Changes with nginx 0.8.23 11 Nov 2009
+
+ *) Security: now SSL/TLS renegotiation is disabled.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: listen unix domain socket did not inherit while online
+ upgrade.
+
+ *) Bugfix: the "unix:" parameter of the "set_real_ip_from" directive did
+ not without yet another directive with any IP address.
+
+ *) Bugfix: segmentation fault and infinite looping in resolver.
+
+ *) Bugfix: in resolver.
+ Thanks to Artem Bokhan.
+
+
+Changes with nginx 0.8.22 03 Nov 2009
+
+ *) Feature: the "proxy_bind", "fastcgi_bind", and "memcached_bind"
+ directives.
+
+ *) Feature: the "access" and the "deny" directives support IPv6.
+
+ *) Feature: the "set_real_ip_from" directive supports IPv6 addresses in
+ request headers.
+
+ *) Feature: the "unix:" parameter of the "set_real_ip_from" directive.
+
+ *) Bugfix: nginx did not delete unix domain socket after configuration
+ testing.
+
+ *) Bugfix: nginx deleted unix domain socket while online upgrade.
+
+ *) Bugfix: the "!-x" operator did not work.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a segmentation fault might occur in a worker process, if
+ limit_rate was used in HTTPS server.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a segmentation fault might occur in a worker process while
+ $limit_rate logging.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a segmentation fault might occur in a worker process, if
+ there was no "listen" directive in "server" block; the bug had
+ appeared in 0.8.21.
+
+
+Changes with nginx 0.8.21 26 Oct 2009
+
+ *) Feature: now the "-V" switch shows TLS SNI support.
+
+ *) Feature: the "listen" directive of the HTTP module supports unix
+ domain sockets.
+ Thanks to Hongli Lai.
+
+ *) Feature: the "default_server" parameter of the "listen" directive.
+
+ *) Feature: now a "default" parameter is not required to set listen
+ socket options.
+
+ *) Bugfix: nginx did not support dates in 2038 year on 32-bit platforms;
+
+ *) Bugfix: socket leak; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.20 14 Oct 2009
+
+ *) Change: now default SSL ciphers are "HIGH:!ADH:!MD5".
+
+ *) Bugfix: the ngx_http_autoindex_module did not show the trailing slash
+ in links to a directory; the bug had appeared in 0.7.15.
+
+ *) Bugfix: nginx did not close a log file set by the --error-log-path
+ configuration option; the bug had appeared in 0.7.53.
+
+ *) Bugfix: nginx did not treat a comma as separator in the
+ "Cache-Control" backend response header line.
+
+ *) Bugfix: nginx/Windows might not create temporary file, a cache file,
+ or "proxy/fastcgi_store"d file if a worker had no enough access
+ rights for top level directories.
+
+ *) Bugfix: the "Set-Cookie" and "P3P" FastCGI response header lines were
+ not hidden while caching if no "fastcgi_hide_header" directives were
+ used with any parameters.
+
+ *) Bugfix: nginx counted incorrectly disk cache size.
+
+
+Changes with nginx 0.8.19 06 Oct 2009
+
+ *) Change: now SSLv2 protocol is disabled by default.
+
+ *) Change: now default SSL ciphers are "ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM".
+
+ *) Bugfix: a "limit_req" directive did not work; the bug had appeared in
+ 0.8.18.
+
+
+Changes with nginx 0.8.18 06 Oct 2009
+
+ *) Feature: the "read_ahead" directive.
+
+ *) Feature: now several "perl_modules" directives may be used.
+
+ *) Feature: the "limit_req_log_level" and "limit_conn_log_level"
+ directives.
+
+ *) Bugfix: now "limit_req" directive conforms to the leaky bucket
+ algorithm.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx did not work on Linux/sparc.
+ Thanks to Marcus Ramberg.
+
+ *) Bugfix: nginx sent '\0' in a "Location" response header line on MKCOL
+ request.
+ Thanks to Xie Zhenye.
+
+ *) Bugfix: zero status code was logged instead of 499 status code; the
+ bug had appeared in 0.8.11.
+
+ *) Bugfix: socket leak; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.17 28 Sep 2009
+
+ *) Security: now "/../" are disabled in "Destination" request header
+ line.
+
+ *) Change: now $host variable value is always low case.
+
+ *) Feature: the $ssl_session_id variable.
+
+ *) Bugfix: socket leak; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.16 22 Sep 2009
+
+ *) Feature: the "image_filter_transparency" directive.
+
+ *) Bugfix: "addition_types" directive was incorrectly named
+ "addtion_types".
+
+ *) Bugfix: resolver cache poisoning.
+ Thanks to Matthew Dempsky.
+
+ *) Bugfix: memory leak in resolver.
+ Thanks to Matthew Dempsky.
+
+ *) Bugfix: invalid request line in $request variable was written in
+ access_log only if error_log was set to "info" or "debug" level.
+
+ *) Bugfix: in PNG alpha-channel support in the
+ ngx_http_image_filter_module.
+
+ *) Bugfix: nginx always added "Vary: Accept-Encoding" response header
+ line, if both "gzip_static" and "gzip_vary" were on.
+
+ *) Bugfix: in UTF-8 encoding support by "try_files" directive in
+ nginx/Windows.
+
+ *) Bugfix: in "post_action" directive usage; the bug had appeared in
+ 0.8.11.
+ Thanks to Igor Artemiev.
+
+
+Changes with nginx 0.8.15 14 Sep 2009
+
+ *) Security: a segmentation fault might occur in worker process while
+ specially crafted request handling.
+ Thanks to Chris Ries.
+
+ *) Bugfix: if names .domain.tld, .sub.domain.tld, and .domain-some.tld
+ were defined, then the name .sub.domain.tld was matched by
+ .domain.tld.
+
+ *) Bugfix: in transparency support in the ngx_http_image_filter_module.
+
+ *) Bugfix: in file AIO.
+
+ *) Bugfix: in X-Accel-Redirect usage; the bug had appeared in 0.8.11.
+
+ *) Bugfix: in embedded perl module; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.14 07 Sep 2009
+
+ *) Bugfix: an expired cached response might stick in the "UPDATING"
+ state.
+
+ *) Bugfix: a segmentation fault might occur in worker process, if
+ error_log was set to info or debug level.
+ Thanks to Sergey Bochenkov.
+
+ *) Bugfix: in embedded perl module; the bug had appeared in 0.8.11.
+
+ *) Bugfix: an "error_page" directive did not redirect a 413 error; the
+ bug had appeared in 0.6.10.
+
+
+Changes with nginx 0.8.13 31 Aug 2009
+
+ *) Bugfix: in the "aio sendfile" directive; the bug had appeared in
+ 0.8.12.
+
+ *) Bugfix: nginx could not be built without the --with-file-aio option
+ on FreeBSD; the bug had appeared in 0.8.12.
+
+
+Changes with nginx 0.8.12 31 Aug 2009
+
+ *) Feature: the "sendfile" parameter in the "aio" directive on FreeBSD.
+
+ *) Bugfix: in try_files; the bug had appeared in 0.8.11.
+
+ *) Bugfix: in memcached; the bug had appeared in 0.8.11.
+
+
+Changes with nginx 0.8.11 28 Aug 2009
+
+ *) Change: now directive "gzip_disable msie6" does not disable gzipping
+ for MSIE 6.0 SV1.
+
+ *) Feature: file AIO support on FreeBSD and Linux.
+
+ *) Feature: the "directio_alignment" directive.
+
+
+Changes with nginx 0.8.10 24 Aug 2009
+
+ *) Bugfix: memory leaks if GeoIP City database was used.
+
+ *) Bugfix: in copying temporary files to permanent storage area; the bug
+ had appeared in 0.8.9.
+
+
+Changes with nginx 0.8.9 17 Aug 2009
+
+ *) Feature: now the start cache loader runs in a separate process; this
+ should improve large caches handling.
+
+ *) Feature: now temporary files and permanent storage area may reside at
+ different file systems.
+
+
+Changes with nginx 0.8.8 10 Aug 2009
+
+ *) Bugfix: in handling FastCGI headers split in records.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if a request
+ was handled in two proxied or FastCGIed locations and a caching was
+ enabled in the first location; the bug had appeared in 0.8.7.
+
+
+Changes with nginx 0.8.7 27 Jul 2009
+
+ *) Change: minimum supported OpenSSL version is 0.9.7.
+
+ *) Change: the "ask" parameter of the "ssl_verify_client" directive was
+ changed to the "optional" parameter and now it checks a client
+ certificate if it was offered.
+ Thanks to Brice Figureau.
+
+ *) Feature: the $ssl_client_verify variable.
+ Thanks to Brice Figureau.
+
+ *) Feature: the "ssl_crl" directive.
+ Thanks to Brice Figureau.
+
+ *) Feature: the "proxy" parameter of the "geo" directive.
+
+ *) Feature: the "image_filter" directive supports variables for setting
+ size.
+
+ *) Bugfix: the $ssl_client_cert variable usage corrupted memory; the bug
+ had appeared in 0.7.7.
+ Thanks to Sergey Zhuravlev.
+
+ *) Bugfix: "proxy_pass_header" and "fastcgi_pass_header" directives did
+ not pass to a client the "X-Accel-Redirect", "X-Accel-Limit-Rate",
+ "X-Accel-Buffering", and "X-Accel-Charset" lines from backend
+ response header.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: in handling "Last-Modified" and "Accept-Ranges" backend
+ response header lines; the bug had appeared in 0.7.44.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: the "[alert] zero size buf" error if subrequest returns an
+ empty response; the bug had appeared in 0.8.5.
+
+
+Changes with nginx 0.8.6 20 Jul 2009
+
+ *) Feature: the ngx_http_geoip_module.
+
+ *) Bugfix: XSLT filter may fail with message "not well formed XML
+ document" for valid XML document.
+ Thanks to Kuramoto Eiji.
+
+ *) Bugfix: now in MacOSX, Cygwin, and nginx/Windows locations given by a
+ regular expression are always tested in case insensitive mode.
+
+ *) Bugfix: now nginx/Windows ignores trailing dots in URI.
+ Thanks to Hugo Leisink.
+
+ *) Bugfix: name of file specified in --conf-path was not honored during
+ installation; the bug had appeared in 0.6.6.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.5 13 Jul 2009
+
+ *) Bugfix: now nginx allows underscores in a request method.
+
+ *) Bugfix: a 500 error code was returned for invalid login/password
+ while HTTP Basic authentication on Windows.
+
+ *) Bugfix: ngx_http_perl_module responses did not work in subrequests.
+
+ *) Bugfix: in ngx_http_limit_req_module.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.8.4 22 Jun 2009
+
+ *) Bugfix: nginx could not be built --without-http-cache; the bug had
+ appeared in 0.8.3.
+
+
+Changes with nginx 0.8.3 19 Jun 2009
+
+ *) Feature: the $upstream_cache_status variable.
+
+ *) Bugfix: nginx could not be built on MacOSX 10.6.
+
+ *) Bugfix: nginx could not be built --without-http-cache; the bug had
+ appeared in 0.8.2.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if a backend
+ 401 error was intercepted and the backend did not set the
+ "WWW-Authenticate" response header line.
+ Thanks to Eugene Mychlo.
+
+
+Changes with nginx 0.8.2 15 Jun 2009
+
+ *) Bugfix: in open_file_cache and proxy/fastcgi cache interaction on
+ start up.
+
+ *) Bugfix: open_file_cache might cache open file descriptors too long;
+ the bug had appeared in 0.7.4.
+
+
+Changes with nginx 0.8.1 08 Jun 2009
+
+ *) Feature: the "updating" parameter in "proxy_cache_use_stale" and
+ "fastcgi_cache_use_stale" directives.
+
+ *) Bugfix: the "If-Modified-Since", "If-Range", etc. client request
+ header lines were passed to backend while caching if no
+ "proxy_set_header" directive was used with any parameters.
+
+ *) Bugfix: the "Set-Cookie" and "P3P" response header lines were not
+ hidden while caching if no "proxy_hide_header/fastcgi_hide_header"
+ directives were used with any parameters.
+
+ *) Bugfix: the ngx_http_image_filter_module did not support GIF87a
+ format.
+ Thanks to Denis Ilyinyh.
+
+ *) Bugfix: nginx could not be built modules on Solaris 10 and early; the
+ bug had appeared in 0.7.56.
+
+
+Changes with nginx 0.8.0 02 Jun 2009
+
+ *) Feature: the "keepalive_requests" directive.
+
+ *) Feature: the "limit_rate_after" directive.
+ Thanks to Ivan Debnar.
+
+ *) Bugfix: XLST filter did not work in subrequests.
+
+ *) Bugfix: in relative paths handling in nginx/Windows.
+
+ *) Bugfix: in proxy_store, fastcgi_store, proxy_cache, and fastcgi_cache
+ in nginx/Windows.
+
+ *) Bugfix: in memory allocation error handling.
+ Thanks to Maxim Dounin and Kirill A. Korinskiy.
+
+
+Changes with nginx 0.7.59 25 May 2009
+
+ *) Feature: the "proxy_cache_methods" and "fastcgi_cache_methods"
+ directives.
+
+ *) Bugfix: socket leak; the bug had appeared in 0.7.25.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if a request
+ had no body and the $request_body variable was used;
+ the bug had appeared in 0.7.58.
+
+ *) Bugfix: the SSL modules might not built on Solaris and Linux;
+ the bug had appeared in 0.7.56.
+
+ *) Bugfix: ngx_http_xslt_filter_module responses were not handled by
+ SSI, charset, and gzip filters.
+
+ *) Bugfix: a "charset" directive did not set a charset to
+ ngx_http_gzip_static_module responses.
+
+
+Changes with nginx 0.7.58 18 May 2009
+
+ *) Feature: a "listen" directive of the mail proxy module supports IPv6.
+
+ *) Feature: the "image_filter_jpeg_quality" directive.
+
+ *) Feature: the "client_body_in_single_buffer" directive.
+
+ *) Feature: the $request_body variable.
+
+ *) Bugfix: in ngx_http_autoindex_module in file name links having a ":"
+ symbol in the name.
+
+ *) Bugfix: "make upgrade" procedure did not work; the bug had appeared
+ in 0.7.53.
+ Thanks to Denis F. Latypoff.
+
+
+Changes with nginx 0.7.57 12 May 2009
+
+ *) Bugfix: a floating-point fault occurred in worker process, if the
+ ngx_http_image_filter_module errors were redirected to named
+ location; the bug had appeared in 0.7.56.
+
+
+Changes with nginx 0.7.56 11 May 2009
+
+ *) Feature: nginx/Windows supports IPv6 in a "listen" directive of the
+ HTTP module.
+
+ *) Bugfix: in ngx_http_image_filter_module.
+
+
+Changes with nginx 0.7.55 06 May 2009
+
+ *) Bugfix: the http_XXX parameters in "proxy_cache_use_stale" and
+ "fastcgi_cache_use_stale" directives did not work.
+
+ *) Bugfix: fastcgi cache did not cache header only responses.
+
+ *) Bugfix: of "select() failed (9: Bad file descriptor)" error in
+ nginx/Unix and "select() failed (10038: ...)" error in nginx/Windows.
+
+ *) Bugfix: a segmentation fault might occur in worker process, if an
+ "debug_connection" directive was used; the bug had appeared in
+ 0.7.54.
+
+ *) Bugfix: fix ngx_http_image_filter_module building errors.
+
+ *) Bugfix: the files bigger than 2G could not be transferred using
+ $r->sendfile.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.7.54 01 May 2009
+
+ *) Feature: the ngx_http_image_filter_module.
+
+ *) Feature: the "proxy_ignore_headers" and "fastcgi_ignore_headers"
+ directives.
+
+ *) Bugfix: a segmentation fault might occur in worker process, if an
+ "open_file_cache_errors off" directive was used; the bug had appeared
+ in 0.7.53.
+
+ *) Bugfix: the "port_in_redirect off" directive did not work; the bug
+ had appeared in 0.7.39.
+
+ *) Bugfix: improve handling of "select" method errors.
+
+ *) Bugfix: of "select() failed (10022: ...)" error in nginx/Windows.
+
+ *) Bugfix: in error text descriptions in nginx/Windows; the bug had
+ appeared in 0.7.53.
+
+
+Changes with nginx 0.7.53 27 Apr 2009
+
+ *) Change: now a log set by --error-log-path is created from the very
+ start-up.
+
+ *) Feature: now the start up errors and warnings are outputted to an
+ error_log and stderr.
+
+ *) Feature: the empty --prefix= configure parameter forces nginx to use
+ a directory where it was run as prefix.
+
+ *) Feature: the -p switch.
+
+ *) Feature: the -s switch on Unix platforms.
+
+ *) Feature: the -? and -h switches.
+ Thanks to Jerome Loyet.
+
+ *) Feature: now switches may be set in condensed form.
+
+ *) Bugfix: nginx/Windows did not work if configuration file was given by
+ the -c switch.
+
+ *) Bugfix: temporary files might be not removed if the "proxy_store",
+ "fastcgi_store", "proxy_cache", or "fastcgi_cache" were used.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: an incorrect value was passed to mail proxy authentication
+ server in "Auth-Method" header line; the bug had appeared
+ in 0.7.34.
+ Thanks to Simon Lecaille.
+
+ *) Bugfix: system error text descriptions were not logged on Linux;
+ the bug had appeared in 0.7.45.
+
+ *) Bugfix: the "fastcgi_cache_min_uses" directive did not work.
+ Thanks to Andrew Vorobyoff.
+
+
+Changes with nginx 0.7.52 20 Apr 2009
+
+ *) Feature: the first native Windows binary release.
+
+ *) Bugfix: in processing HEAD method while caching.
+
+ *) Bugfix: in processing the "If-Modified-Since", "If-Range", etc.
+ client request header lines while caching.
+
+ *) Bugfix: now the "Set-Cookie" and "P3P" header lines are hidden in
+ cacheable responses.
+
+ *) Bugfix: if nginx was built with the ngx_http_perl_module and with a
+ perl which supports threads, then during a master process exit the
+ message "panic: MUTEX_LOCK" might be issued.
+
+ *) Bugfix: nginx could not be built --without-http-cache; the bug had
+ appeared in 0.7.48.
+
+ *) Bugfix: nginx could not be built on platforms different from i386,
+ amd64, sparc, and ppc; the bug had appeared in 0.7.42.
+
+
+Changes with nginx 0.7.51 12 Apr 2009
+
+ *) Feature: the "try_files" directive supports a response code in the
+ fallback parameter.
+
+ *) Feature: now any response code can be used in the "return" directive.
+
+ *) Bugfix: the "error_page" directive made an external redirect without
+ query string; the bug had appeared in 0.7.44.
+
+ *) Bugfix: if servers listened on several defined explicitly addresses,
+ then virtual servers might not work; the bug had appeared in 0.7.39.
+
+
+Changes with nginx 0.7.50 06 Apr 2009
+
+ *) Bugfix: the $arg_... variables did not work; the bug had appeared in
+ 0.7.49.
+
+
+Changes with nginx 0.7.49 06 Apr 2009
+
+ *) Bugfix: a segmentation fault might occur in worker process, if the
+ $arg_... variables were used; the bug had appeared in 0.7.48.
+
+
+Changes with nginx 0.7.48 06 Apr 2009
+
+ *) Feature: the "proxy_cache_key" directive.
+
+ *) Bugfix: now nginx takes into account the "X-Accel-Expires",
+ "Expires", and "Cache-Control" header lines in a backend response.
+
+ *) Bugfix: now nginx caches responses for the GET requests only.
+
+ *) Bugfix: the "fastcgi_cache_key" directive was not inherited.
+
+ *) Bugfix: the $arg_... variables did not work with SSI subrequests.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx could not be built with uclibc library.
+ Thanks to Timothy Redaelli.
+
+ *) Bugfix: nginx could not be built on OpenBSD; the bug had
+ appeared in 0.7.46.
+
+
+Changes with nginx 0.7.47 01 Apr 2009
+
+ *) Bugfix: nginx could not be built on FreeBSD 6 and early versions; the
+ bug had appeared in 0.7.46.
+
+ *) Bugfix: nginx could not be built on MacOSX; the bug had
+ appeared in 0.7.46.
+
+ *) Bugfix: if the "max_size" parameter was set, then the cache manager
+ might purge a whole cache; the bug had appeared in 0.7.46.
+
+ *) Change: a segmentation fault might occur in worker process, if the
+ "proxy_cache"/"fastcgi_cache" and the "proxy_cache_valid"/
+ "fastcgi_cache_valid" were set on different levels; the bug had
+ appeared in 0.7.46.
+
+ *) Bugfix: a segmentation fault might occur in worker process, if a
+ request was redirected to a proxied or FastCGI server via error_page
+ or try_files; the bug had appeared in 0.7.44.
+
+
+Changes with nginx 0.7.46 30 Mar 2009
+
+ *) Bugfix: the previous release tarball was incorrect.
+
+
+Changes with nginx 0.7.45 30 Mar 2009
+
+ *) Change: now the "proxy_cache" and the "proxy_cache_valid" directives
+ can be set on different levels.
+
+ *) Change: the "clean_time" parameter of the "proxy_cache_path"
+ directive is canceled.
+
+ *) Feature: the "max_size" parameter of the "proxy_cache_path"
+ directive.
+
+ *) Feature: the ngx_http_fastcgi_module preliminary cache support.
+
+ *) Feature: now on shared memory allocation errors directive and zone
+ names are logged.
+
+ *) Bugfix: the directive "add_header last-modified ''" did not delete a
+ "Last-Modified" response header line; the bug had appeared in 0.7.44.
+
+ *) Bugfix: a relative path in the "auth_basic_user_file" directive given
+ without variables did not work; the bug had appeared in 0.7.44.
+ Thanks to Jerome Loyet.
+
+ *) Bugfix: in an "alias" directive given using variables without
+ references to captures of regular expressions; the bug had appeared
+ in 0.7.42.
+
+
+Changes with nginx 0.7.44 23 Mar 2009
+
+ *) Feature: the ngx_http_proxy_module preliminary cache support.
+
+ *) Feature: the --with-pcre option in the configure.
+
+ *) Feature: the "try_files" directive is now allowed on the server block
+ level.
+
+ *) Bugfix: the "try_files" directive handled incorrectly a query string
+ in a fallback parameter.
+
+ *) Bugfix: the "try_files" directive might test incorrectly directories.
+
+ *) Bugfix: if there was a single server for given address:port pair,
+ then captures in regular expressions in a "server_name" directive did
+ not work.
+
+
+Changes with nginx 0.7.43 18 Mar 2009
+
+ *) Bugfix: a request was handled incorrectly, if a "root" directive used
+ variables; the bug had appeared in 0.7.42.
+
+ *) Bugfix: if a server listened on wildcard address, then the
+ $server_addr variable value was "0.0.0.0"; the bug had appeared in
+ 0.7.36.
+
+
+Changes with nginx 0.7.42 16 Mar 2009
+
+ *) Change: now the "Invalid argument" error returned by
+ setsockopt(TCP_NODELAY) on Solaris, is ignored.
+
+ *) Change: now if a file specified in a "auth_basic_user_file" directive
+ is absent, then the 403 error is returned instead of the 500 one.
+
+ *) Feature: the "auth_basic_user_file" directive supports variables.
+ Thanks to Kirill A. Korinskiy.
+
+ *) Feature: the "listen" directive supports the "ipv6only" parameter.
+ Thanks to Zhang Hua.
+
+ *) Bugfix: in an "alias" directive with references to captures of
+ regular expressions; the bug had appeared in 0.7.40.
+
+ *) Bugfix: compatibility with Tru64 UNIX.
+ Thanks to Dustin Marquess.
+
+ *) Bugfix: nginx could not be built without PCRE library; the bug had
+ appeared in 0.7.41.
+
+
+Changes with nginx 0.7.41 11 Mar 2009
+
+ *) Bugfix: a segmentation fault might occur in worker process, if a
+ "server_name" or a "location" directives had captures in regular
+ expressions; the issue had appeared in 0.7.40.
+ Thanks to Vladimir Sopot.
+
+
+Changes with nginx 0.7.40 09 Mar 2009
+
+ *) Feature: the "location" directive supports captures in regular
+ expressions.
+
+ *) Feature: an "alias" directive with capture references may be used
+ inside a location given by a regular expression with captures.
+
+ *) Feature: the "server_name" directive supports captures in regular
+ expressions.
+
+ *) Workaround: the ngx_http_autoindex_module did not show the trailing
+ slash in directories on XFS filesystem; the issue had appeared in
+ 0.7.15.
+ Thanks to Dmitry Kuzmenko.
+
+
+Changes with nginx 0.7.39 02 Mar 2009
+
+ *) Bugfix: large response with SSI might hang, if gzipping was enabled;
+ the bug had appeared in 0.7.28.
+ Thanks to Artem Bokhan.
+
+ *) Bugfix: a segmentation fault might occur in worker process, if short
+ static variants are used in a "try_files" directive.
+
+
+Changes with nginx 0.7.38 23 Feb 2009
+
+ *) Feature: authentication failures logging.
+
+ *) Bugfix: name/password in auth_basic_user_file were ignored after odd
+ number of empty lines.
+ Thanks to Alexander Zagrebin.
+
+ *) Bugfix: a segmentation fault occurred in a master process, if long
+ path was used in unix domain socket; the bug had appeared in 0.7.36.
+
+
+Changes with nginx 0.7.37 21 Feb 2009
+
+ *) Bugfix: directives using upstreams did not work; the bug had appeared
+ in 0.7.36.
+
+
+Changes with nginx 0.7.36 21 Feb 2009
+
+ *) Feature: a preliminary IPv6 support; the "listen" directive of the
+ HTTP module supports IPv6.
+
+ *) Bugfix: the $ancient_browser variable did not work for browsers
+ preset by a "modern_browser" directives.
+
+
+Changes with nginx 0.7.35 16 Feb 2009
+
+ *) Bugfix: a "ssl_engine" directive did not use a SSL-accelerator for
+ asymmetric ciphers.
+ Thanks to Marcin Gozdalik.
+
+ *) Bugfix: a "try_files" directive set MIME type depending on an
+ original request extension.
+
+ *) Bugfix: "*domain.tld" names were handled incorrectly in
+ "server_name", "valid_referers", and "map" directives, if
+ ".domain.tld" and ".subdomain.domain.tld" wildcards were used;
+ the bug had appeared in 0.7.9.
+
+
+Changes with nginx 0.7.34 10 Feb 2009
+
+ *) Feature: the "off" parameter of the "if_modified_since" directive.
+
+ *) Feature: now nginx sends an HELO/EHLO command after a XCLIENT
+ command.
+ Thanks to Maxim Dounin.
+
+ *) Feature: Microsoft specific "AUTH LOGIN with User Name" mode support
+ in mail proxy server.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: in a redirect rewrite directive original arguments were
+ concatenated with new arguments by a "?" rather than an "&";
+ the bug had appeared in 0.1.18.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx could not be built on AIX.
+
+
+Changes with nginx 0.7.33 02 Feb 2009
+
+ *) Bugfix: a double response might be returned if the epoll or rtsig
+ methods are used and a redirect was returned to a request with body.
+ Thanks to Eden Li.
+
+ *) Bugfix: the $sent_http_location variable was empty for some redirects
+ types.
+
+ *) Bugfix: a segmentation fault might occur in worker process if
+ "resolver" directive was used in SMTP proxy.
+
+
+Changes with nginx 0.7.32 26 Jan 2009
+
+ *) Feature: now a directory existence testing can be set explicitly in
+ the "try_files" directive.
+
+ *) Bugfix: fastcgi_store stored files not always.
+
+ *) Bugfix: in geo ranges.
+
+ *) Bugfix: in shared memory allocations if nginx was built without
+ debugging.
+ Thanks to Andrey Kvasov.
+
+
+Changes with nginx 0.7.31 19 Jan 2009
+
+ *) Change: now the "try_files" directive tests files only and ignores
+ directories.
+
+ *) Feature: the "fastcgi_split_path_info" directive.
+
+ *) Bugfixes in an "Expect" request header line support.
+
+ *) Bugfixes in geo ranges.
+
+ *) Bugfix: in a miss case ngx_http_memcached_module returned the "END"
+ line as response body instead of default 404 page body; the bug had
+ appeared in 0.7.18.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: while SMTP proxying nginx issued message "250 2.0.0 OK"
+ instead of "235 2.0.0 OK"; the bug had appeared in 0.7.22.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.7.30 24 Dec 2008
+
+ *) Bugfix: a segmentation fault occurred in worker process, if variables
+ were used in the "fastcgi_pass" or "proxy_pass" directives and host
+ name must be resolved; the bug had appeared in 0.7.29.
+
+
+Changes with nginx 0.7.29 24 Dec 2008
+
+ *) Bugfix: the "fastcgi_pass" and "proxy_pass" directives did not
+ support variables if unix domain sockets were used.
+
+ *) Bugfixes in subrequest processing; the bugs had appeared in 0.7.25.
+
+ *) Bugfix: a "100 Continue" response was issued for HTTP/1.0 requests;
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: in memory allocation in the ngx_http_gzip_filter_module on
+ Cygwin.
+
+
+Changes with nginx 0.7.28 22 Dec 2008
+
+ *) Change: in memory allocation in the ngx_http_gzip_filter_module.
+
+ *) Change: the default "gzip_buffers" directive values have been changed
+ to 32 4k or 16 8k from 4 4k/8k.
+
+
+Changes with nginx 0.7.27 15 Dec 2008
+
+ *) Feature: the "try_files" directive.
+
+ *) Feature: variables support in the "fastcgi_pass" directive.
+
+ *) Feature: now the $geo variable may get an address from a variable.
+ Thanks to Andrei Nigmatulin.
+
+ *) Feature: now a location's modifier may be used without space before
+ name.
+
+ *) Feature: the $upstream_response_length variable.
+
+ *) Bugfix: now a "add_header" directive does not add an empty value.
+
+ *) Bugfix: if zero length static file was requested, then nginx just
+ closed connection; the bug had appeared in 0.7.25.
+
+ *) Bugfix: a MOVE method could not move file in non-existent directory.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if no one
+ named location was defined in server, but some one was used in an
+ error_page directive.
+ Thanks to Sergey Bochenkov.
+
+
+Changes with nginx 0.7.26 08 Dec 2008
+
+ *) Bugfix: in subrequest processing; the bug had appeared in 0.7.25.
+
+
+Changes with nginx 0.7.25 08 Dec 2008
+
+ *) Change: in subrequest processing.
+
+ *) Change: now POSTs without "Content-Length" header line are allowed.
+
+ *) Bugfix: now the "limit_req" and "limit_conn" directives log a
+ prohibition reason.
+
+ *) Bugfix: in the "delete" parameter of the "geo" directive.
+
+
+Changes with nginx 0.7.24 01 Dec 2008
+
+ *) Feature: the "if_modified_since" directive.
+
+ *) Bugfix: nginx did not process a FastCGI server response, if the
+ server send too many messages to stderr before response.
+
+ *) Bugfix: the "$cookie_..." variables did not work in the SSI and the
+ perl module.
+
+
+Changes with nginx 0.7.23 27 Nov 2008
+
+ *) Feature: the "delete" and "ranges" parameters in the "geo" directive.
+
+ *) Feature: speeding up loading of geo base with large number of values.
+
+ *) Feature: decrease of memory required for geo base load.
+
+
+Changes with nginx 0.7.22 20 Nov 2008
+
+ *) Feature: the "none" parameter in the "smtp_auth" directive.
+ Thanks to Maxim Dounin.
+
+ *) Feature: the "$cookie_..." variables.
+
+ *) Bugfix: the "directio" directive did not work in XFS filesystem.
+
+ *) Bugfix: the resolver did not understand big DNS responses.
+ Thanks to Zyb.
+
+
+Changes with nginx 0.7.21 11 Nov 2008
+
+ *) Changes in the ngx_http_limit_req_module.
+
+ *) Feature: the EXSLT support in the ngx_http_xslt_module.
+ Thanks to Denis F. Latypoff.
+
+ *) Workaround: compatibility with glibc 2.3.
+ Thanks to Eric Benson and Maxim Dounin.
+
+ *) Bugfix: nginx could not run on MacOSX 10.4 and earlier; the bug had
+ appeared in 0.7.6.
+
+
+Changes with nginx 0.7.20 10 Nov 2008
+
+ *) Changes in the ngx_http_gzip_filter_module.
+
+ *) Feature: the ngx_http_limit_req_module.
+
+ *) Bugfix: worker processes might exit on a SIGBUS signal on sparc and
+ ppc platforms; the bug had appeared in 0.7.3.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: the "proxy_pass http://host/some:uri" directives did not
+ work; the bug had appeared in 0.7.12.
+
+ *) Bugfix: in HTTPS mode requests might fail with the "bad write retry"
+ error.
+
+ *) Bugfix: the ngx_http_secure_link_module did not work inside
+ locations, whose names are less than 3 characters.
+
+ *) Bugfix: $server_addr variable might have no value.
+
+
+Changes with nginx 0.7.19 13 Oct 2008
+
+ *) Bugfix: version number update.
+
+
+Changes with nginx 0.7.18 13 Oct 2008
+
+ *) Change: the "underscores_in_headers" directive; now nginx does not
+ allows underscores in a client request header line names.
+
+ *) Feature: the ngx_http_secure_link_module.
+
+ *) Feature: the "real_ip_header" directive supports any header.
+
+ *) Feature: the "log_subrequest" directive.
+
+ *) Feature: the $realpath_root variable.
+
+ *) Feature: the "http_502" and "http_504" parameters of the
+ "proxy_next_upstream" directive.
+
+ *) Bugfix: the "http_503" parameter of the "proxy_next_upstream" or
+ "fastcgi_next_upstream" directives did not work.
+
+ *) Bugfix: nginx might send a "Transfer-Encoding: chunked" header line
+ for HEAD requests.
+
+ *) Bugfix: now accept threshold depends on worker_connections.
+
+
+Changes with nginx 0.7.17 15 Sep 2008
+
+ *) Feature: now the "directio" directive works on Linux.
+
+ *) Feature: the $pid variable.
+
+ *) Bugfix: the "directio" optimization that had appeared in 0.7.15 did
+ not work with open_file_cache.
+
+ *) Bugfix: the "access_log" with variables did not work on Linux; the
+ bug had appeared in 0.7.7.
+
+ *) Bugfix: the ngx_http_charset_module did not understand quoted charset
+ name received from backend.
+
+
+Changes with nginx 0.7.16 08 Sep 2008
+
+ *) Bugfix: nginx could not be built on 64-bit platforms; the bug had
+ appeared in 0.7.15.
+
+
+Changes with nginx 0.7.15 08 Sep 2008
+
+ *) Feature: the ngx_http_random_index_module.
+
+ *) Feature: the "directio" directive has been optimized for file
+ requests starting from arbitrary position.
+
+ *) Feature: the "directio" directive turns off sendfile if it is
+ necessary.
+
+ *) Feature: now nginx allows underscores in a client request header line
+ names.
+
+
+Changes with nginx 0.7.14 01 Sep 2008
+
+ *) Change: now the ssl_certificate and ssl_certificate_key directives
+ have not default values.
+
+ *) Feature: the "listen" directive supports the "ssl" parameter.
+
+ *) Feature: now nginx takes into account a time zone change while
+ reconfiguration on FreeBSD and Linux.
+
+ *) Bugfix: the "listen" directive parameters such as "backlog",
+ "rcvbuf", etc. were not set, if a default server was not the first
+ one.
+
+ *) Bugfix: if URI part captured by a "rewrite" directive was used as a
+ query string, then the query string was not escaped.
+
+ *) Bugfix: configuration file validity test improvements.
+
+
+Changes with nginx 0.7.13 26 Aug 2008
+
+ *) Bugfix: nginx could not be built on Linux and Solaris; the bug had
+ appeared in 0.7.12.
+
+
+Changes with nginx 0.7.12 26 Aug 2008
+
+ *) Feature: the "server_name" directive supports empty name "".
+
+ *) Feature: the "gzip_disable" directive supports special "msie6" mask.
+
+ *) Bugfix: if the "max_fails=0" parameter was used in upstream with
+ several servers, then a worker process exited on a SIGFPE signal.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a request body was dropped while redirection via an
+ "error_page" directive.
+
+ *) Bugfix: a full response was returned for request method HEAD while
+ redirection via an "error_page" directive.
+
+ *) Bugfix: the $r->header_in() method did not return value of the
+ "Host", "User-Agent", and "Connection" request header lines; the bug
+ had appeared in 0.7.0.
+
+
+Changes with nginx 0.7.11 18 Aug 2008
+
+ *) Change: now ngx_http_charset_module does not work by default with
+ text/css MIME type.
+
+ *) Feature: now nginx returns the 405 status code for POST method
+ requesting a static file only if the file exists.
+
+ *) Feature: the "proxy_ssl_session_reuse" directive.
+
+ *) Bugfix: a "proxy_pass" directive without URI part might use original
+ request after the "X-Accel-Redirect" redirection was used;
+
+ *) Bugfix: if a directory has search only rights and the first index
+ file was absent, then nginx returned the 500 status code.
+
+ *) Bugfix: in inclusive locations; the bugs had appeared in 0.7.1.
+
+
+Changes with nginx 0.7.10 13 Aug 2008
+
+ *) Bugfix: in the "addition_types", "charset_types", "gzip_types",
+ "ssi_types", "sub_filter_types", and "xslt_types" directives; the
+ bugs had appeared in 0.7.9.
+
+ *) Bugfix: of recursive error_page for 500 status code.
+
+ *) Bugfix: now the ngx_http_realip_module sets address not for whole
+ keepalive connection, but for each request passed via the connection.
+
+
+Changes with nginx 0.7.9 12 Aug 2008
+
+ *) Change: now ngx_http_charset_module works by default with following
+ MIME types: text/html, text/css, text/xml, text/plain,
+ text/vnd.wap.wml, application/x-javascript, and application/rss+xml.
+
+ *) Feature: the "charset_types" and "addition_types" directives.
+
+ *) Feature: now the "gzip_types", "ssi_types", and "sub_filter_types"
+ directives use hash.
+
+ *) Feature: the ngx_cpp_test_module.
+
+ *) Feature: the "expires" directive supports daily time.
+
+ *) Feature: the ngx_http_xslt_module improvements and bug fixing.
+ Thanks to Denis F. Latypoff and Maxim Dounin.
+
+ *) Bugfix: the "log_not_found" directive did not work for index files
+ tests.
+
+ *) Bugfix: HTTPS connections might hang, if kqueue, epoll, rtsig, or
+ eventport methods were used; the bug had appeared in 0.7.7.
+
+ *) Bugfix: if the "server_name", "valid_referers", and "map" directives
+ used an "*.domain.tld" wildcard and exact name "domain.tld" was not
+ set, then the exact name was matched by the wildcard; the bug had
+ appeared in 0.3.18.
+
+
+Changes with nginx 0.7.8 04 Aug 2008
+
+ *) Feature: the ngx_http_xslt_module.
+
+ *) Feature: the "$arg_..." variables.
+
+ *) Feature: Solaris directio support.
+ Thanks to Ivan Debnar.
+
+ *) Bugfix: now if FastCGI server sends a "Location" header line without
+ status line, then nginx uses 302 status code.
+ Thanks to Maxim Dounin.
+
+
+Changes with nginx 0.7.7 30 Jul 2008
+
+ *) Change: now the EAGAIN error returned by connect() is not considered
+ as temporary error.
+
+ *) Change: now the $ssl_client_cert variable value is a certificate with
+ TAB character intended before each line except first one; an
+ unchanged certificate is available in the $ssl_client_raw_cert
+ variable.
+
+ *) Feature: the "ask" parameter in the "ssl_verify_client" directive.
+
+ *) Feature: byte-range processing improvements.
+ Thanks to Maxim Dounin.
+
+ *) Feature: the "directio" directive.
+ Thanks to Jiang Hong.
+
+ *) Feature: MacOSX 10.5 sendfile() support.
+
+ *) Bugfix: now in MacOSX and Cygwin locations are tested in case
+ insensitive mode; however, the compare is provided by single-byte
+ locales only.
+
+ *) Bugfix: mail proxy SSL connections hanged, if select, poll, or
+ /dev/poll methods were used.
+
+ *) Bugfix: UTF-8 encoding usage in the ngx_http_autoindex_module.
+
+
+Changes with nginx 0.7.6 07 Jul 2008
+
+ *) Bugfix: now if variables are used in the "access_log" directive a
+ request root existence is always tested.
+
+ *) Bugfix: the ngx_http_flv_module did not support several values in a
+ query string.
+
+
+Changes with nginx 0.7.5 01 Jul 2008
+
+ *) Bugfixes in variables support in the "access_log" directive; the bugs
+ had appeared in 0.7.4.
+
+ *) Bugfix: nginx could not be built --without-http_gzip_module; the bug
+ had appeared in 0.7.3.
+ Thanks to Kirill A. Korinskiy.
+
+ *) Bugfix: if sub_filter and SSI were used together, then responses
+ might were transferred incorrectly.
+
+
+Changes with nginx 0.7.4 30 Jun 2008
+
+ *) Feature: variables support in the "access_log" directive.
+
+ *) Feature: the "open_log_file_cache" directive.
+
+ *) Feature: the -g switch.
+
+ *) Feature: the "Expect" request header line support.
+
+ *) Bugfix: large SSI inclusions might be truncated.
+
+
+Changes with nginx 0.7.3 23 Jun 2008
+
+ *) Change: the "rss" extension MIME type has been changed to
+ "application/rss+xml".
+
+ *) Change: now the "gzip_vary" directive turned on issues a
+ "Vary: Accept-Encoding" header line for uncompressed responses too.
+
+ *) Feature: now the "rewrite" directive does a redirect automatically if
+ the "https://" protocol is used.
+
+ *) Bugfix: the "proxy_pass" directive did not work with the HTTPS
+ protocol; the bug had appeared in 0.6.9.
+
+
+Changes with nginx 0.7.2 16 Jun 2008
+
+ *) Feature: now nginx supports EDH key exchange ciphers.
+
+ *) Feature: the "ssl_dhparam" directive.
+
+ *) Feature: the $ssl_client_cert variable.
+ Thanks to Manlio Perillo.
+
+ *) Bugfix: after changing URI via a "rewrite" directive nginx did not
+ search a new location; the bug had appeared in 0.7.1.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: nginx could not be built without PCRE library; the bug had
+ appeared in 0.7.1.
+
+ *) Bugfix: when a request to a directory was redirected with the slash
+ added, nginx dropped a query string from the original request.
+
+
+Changes with nginx 0.7.1 26 May 2008
+
+ *) Change: now locations are searched in a tree.
+
+ *) Change: the "optimize_server_names" directive was canceled due to the
+ "server_name_in_redirect" directive introduction.
+
+ *) Change: some long deprecated directives are not supported anymore.
+
+ *) Change: the "none" parameter in the "ssl_session_cache" directive;
+ now this is default parameter.
+ Thanks to Rob Mueller.
+
+ *) Bugfix: worker processes might not catch reconfiguration and log
+ rotation signals.
+
+ *) Bugfix: nginx could not be built on latest Fedora 9 Linux.
+ Thanks to Roxis.
+
+
+Changes with nginx 0.7.0 19 May 2008
+
+ *) Change: now the 0x00-0x1F, '"' and '\' characters are escaped as \xXX
+ in an access_log.
+ Thanks to Maxim Dounin.
+
+ *) Change: now nginx allows several "Host" request header line.
+
+ *) Feature: the "modified" flag in the "expires" directive.
+
+ *) Feature: the $uid_got and $uid_set variables may be used at any
+ request processing stage.
+
+ *) Feature: the $hostname variable.
+ Thanks to Andrei Nigmatulin.
+
+ *) Feature: DESTDIR support.
+ Thanks to Todd A. Fisher and Andras Voroskoi.
+
+ *) Bugfix: a segmentation fault might occur in worker process on Linux,
+ if keepalive was enabled.
+
+
+Changes with nginx 0.6.31 12 May 2008
+
+ *) Bugfix: nginx did not process FastCGI response if header was at the
+ end of FastCGI record; the bug had appeared in 0.6.2.
+ Thanks to Sergey Serov.
+
+ *) Bugfix: a segmentation fault might occur in worker process if a file
+ was deleted and the "open_file_cache_errors" directive was off.
+
+
+Changes with nginx 0.6.30 29 Apr 2008
+
+ *) Change: now if an "include" directive pattern does not match any
+ file, then nginx does not issue an error.
+
+ *) Feature: now the time in directives may be specified without spaces,
+ for example, "1h50m".
+
+ *) Bugfix: memory leaks if the "ssl_verify_client" directive was on.
+ Thanks to Chavelle Vincent.
+
+ *) Bugfix: the "sub_filter" directive might set text to change into
+ output.
+
+ *) Bugfix: the "error_page" directive did not take into account
+ arguments in redirected URI.
+
+ *) Bugfix: now nginx always opens files in binary mode under Cygwin.
+
+ *) Bugfix: nginx could not be built on OpenBSD; the bug had appeared in
+ 0.6.15.
+
+
+Changes with nginx 0.6.29 18 Mar 2008
+
+ *) Feature: the ngx_google_perftools_module.
+
+ *) Bugfix: the ngx_http_perl_module could not be built on 64-bit
+ platforms; the bug had appeared in 0.6.27.
+
+
+Changes with nginx 0.6.28 13 Mar 2008
+
+ *) Bugfix: the rtsig method could not be built; the bug had appeared in
+ 0.6.27.
+
+
+Changes with nginx 0.6.27 12 Mar 2008
+
+ *) Change: now by default the rtsig method is not built on
+ Linux 2.6.18+.
+
+ *) Change: now a request method is not changed while redirection to a
+ named location via an "error_page" directive.
+
+ *) Feature: the "resolver" and "resolver_timeout" directives in SMTP
+ proxy.
+
+ *) Feature: the "post_action" directive supports named locations.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if a request
+ was redirected from proxy, FastCGI, or memcached location to static
+ named locations.
+
+ *) Bugfix: browsers did not repeat SSL handshake if there is no valid
+ client certificate in first handshake.
+ Thanks to Alexander V. Inyukhin.
+
+ *) Bugfix: if response code 495-497 was redirected via an "error_page"
+ directive without code change, then nginx tried to allocate too many
+ memory.
+
+ *) Bugfix: memory leak in long-lived non buffered connections.
+
+ *) Bugfix: memory leak in resolver.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if a request
+ was redirected from proxy, FastCGI, or memcached location to static
+ named locations.
+
+ *) Bugfix: in the $proxy_host and $proxy_port variables caching.
+ Thanks to Sergey Bochenkov.
+
+ *) Bugfix: a "proxy_pass" directive with variables used incorrectly the
+ same port as in another "proxy_pass" directive with the same host
+ name and without variables.
+ Thanks to Sergey Bochenkov.
+
+ *) Bugfix: an alert "sendmsg() failed (9: Bad file descriptor)" on some
+ 64-bit platforms while reconfiguration.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if empty
+ stub block was used second time in SSI.
+
+ *) Bugfix: in copying URI part contained escaped symbols into arguments.
+
+
+Changes with nginx 0.6.26 11 Feb 2008
+
+ *) Bugfix: the "proxy_store" and "fastcgi_store" directives did not
+ check a response length.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if big value
+ was used in a "expires" directive.
+ Thanks to Joaquin Cuenca Abela.
+
+ *) Bugfix: nginx incorrectly detected cache line size on Pentium 4.
+ Thanks to Gena Makhomed.
+
+ *) Bugfix: in proxied or FastCGI subrequests a client original method
+ was used instead of the GET method.
+
+ *) Bugfix: socket leak in HTTPS mode if deferred accept was used.
+ Thanks to Ben Maurer.
+
+ *) Bugfix: nginx issued the bogus error message "SSL_shutdown() failed
+ (SSL: )"; the bug had appeared in 0.6.23.
+
+ *) Bugfix: in HTTPS mode requests might fail with the "bad write retry"
+ error; the bug had appeared in 0.6.23.
+
+
+Changes with nginx 0.6.25 08 Jan 2008
+
+ *) Change: now the "server_name_in_redirect" directive is used instead
+ of the "server_name" directive's special "*" parameter.
+
+ *) Change: now wildcard and regex names can be used as main name in a
+ "server_name" directive.
+
+ *) Change: the "satisfy_any" directive was replaced by the "satisfy"
+ directive.
+
+ *) Workaround: old worker processes might hog CPU after reconfiguration
+ if they was run under Linux OpenVZ.
+
+ *) Feature: the "min_delete_depth" directive.
+
+ *) Bugfix: the COPY and MOVE methods did not work with single files.
+
+ *) Bugfix: the ngx_http_gzip_static_module did not allow the
+ ngx_http_dav_module to work; the bug had appeared in 0.6.23.
+
+ *) Bugfix: socket leak in HTTPS mode if deferred accept was used.
+ Thanks to Ben Maurer.
+
+ *) Bugfix: nginx could not be built without PCRE library; the bug had
+ appeared in 0.6.23.
+
+
+Changes with nginx 0.6.24 27 Dec 2007
+
+ *) Bugfix: a segmentation fault might occur in worker process if HTTPS
+ was used; the bug had appeared in 0.6.23.
+
+
+Changes with nginx 0.6.23 27 Dec 2007
+
+ *) Change: the "off" parameter in the "ssl_session_cache" directive; now
+ this is default parameter.
+
+ *) Change: the "open_file_cache_retest" directive was renamed to the
+ "open_file_cache_valid".
+
+ *) Feature: the "open_file_cache_min_uses" directive.
+
+ *) Feature: the ngx_http_gzip_static_module.
+
+ *) Feature: the "gzip_disable" directive.
+
+ *) Feature: the "memcached_pass" directive may be used inside the "if"
+ block.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if the
+ "memcached_pass" and "if" directives were used in the same location.
+
+ *) Bugfix: if a "satisfy_any on" directive was used and not all access
+ and auth modules directives were set, then other given access and
+ auth directives were not tested;
+
+ *) Bugfix: regex parameters in a "valid_referers" directive were not
+ inherited from previous level.
+
+ *) Bugfix: a "post_action" directive did run if a request was completed
+ with 499 status code.
+
+ *) Bugfix: optimization of 16K buffer usage in a SSL connection.
+ Thanks to Ben Maurer.
+
+ *) Bugfix: the STARTTLS in SMTP mode did not work.
+ Thanks to Oleg Motienko.
+
+ *) Bugfix: in HTTPS mode requests might fail with the "bad write retry"
+ error; the bug had appeared in 0.5.13.
+
+
+Changes with nginx 0.6.22 19 Dec 2007
+
+ *) Change: now all ngx_http_perl_module methods return values copied to
+ perl's allocated memory.
+
+ *) Bugfix: if nginx was built with ngx_http_perl_module, the perl before
+ 5.8.6 was used, and perl supported threads, then during
+ reconfiguration the master process aborted; the bug had appeared in
+ 0.5.9.
+ Thanks to Boris Zhmurov.
+
+ *) Bugfix: the ngx_http_perl_module methods may get invalid values of
+ the regex captures.
+
+ *) Bugfix: a segmentation fault occurred in worker process, if the
+ $r->has_request_body() method was called for a request whose small
+ request body was already received.
+
+ *) Bugfix: large_client_header_buffers did not freed before going to
+ keep-alive state.
+ Thanks to Olexander Shtepa.
+
+ *) Bugfix: the last address was missed in the $upstream_addr variable;
+ the bug had appeared in 0.6.18.
+
+ *) Bugfix: the "fastcgi_catch_stderr" directive did return error code;
+ now it returns 502 code, that can be rerouted to a next server using
+ the "fastcgi_next_upstream invalid_header" directive.
+
+ *) Bugfix: a segmentation fault occurred in master process if the
+ "fastcgi_catch_stderr" directive was used; the bug had appeared in
+ 0.6.10.
+ Thanks to Manlio Perillo.
+
+
+Changes with nginx 0.6.21 03 Dec 2007
+
+ *) Change: if variable values used in a "proxy_pass" directive contain
+ IP-addresses only, then a "resolver" directive is not mandatory.
+
+ *) Bugfix: a segmentation fault might occur in worker process if a
+ "proxy_pass" directive with URI-part was used; the bug had appeared
+ in 0.6.19.
+
+ *) Bugfix: if resolver was used on platform that does not support
+ kqueue, then nginx issued an alert "name is out of response".
+ Thanks to Andrei Nigmatulin.
+
+ *) Bugfix: if the $server_protocol was used in FastCGI parameters and a
+ request line length was near to the "client_header_buffer_size"
+ directive value, then nginx issued an alert "fastcgi: the request
+ record is too big".
+
+ *) Bugfix: if a plain text HTTP/0.9 version request was made to HTTPS
+ server, then nginx returned usual response.
+
+
+Changes with nginx 0.6.20 28 Nov 2007
+
+ *) Bugfix: a segmentation fault might occur in worker process if a
+ "proxy_pass" directive with URI-part was used; the bug had appeared
+ in 0.6.19.
+
+
+Changes with nginx 0.6.19 27 Nov 2007
+
+ *) Bugfix: the 0.6.18 version could not be built.
+
+
+Changes with nginx 0.6.18 27 Nov 2007
+
+ *) Change: now the ngx_http_userid_module adds start time microseconds
+ to the cookie field contains a pid value.
+
+ *) Change: now the full request line instead of URI only is written to
+ error_log.
+
+ *) Feature: variables support in the "proxy_pass" directive.
+
+ *) Feature: the "resolver" and "resolver_timeout" directives.
+
+ *) Feature: now the directive "add_header last-modified ''" deletes a
+ "Last-Modified" response header line.
+
+ *) Bugfix: the "limit_rate" directive did not allow to use full
+ throughput, even if limit value was very high.
+
+
+Changes with nginx 0.6.17 15 Nov 2007
+
+ *) Feature: the "If-Range" request header line support.
+ Thanks to Alexander V. Inyukhin.
+
+ *) Bugfix: URL double escaping in a redirect of the "msie_refresh"
+ directive; the bug had appeared in 0.6.4.
+
+ *) Bugfix: the "autoindex" directive did not work with the "alias /"
+ directive.
+
+ *) Bugfix: a segmentation fault might occur in worker process if
+ subrequests were used.
+
+ *) Bugfix: the big responses may be transferred truncated if SSL and
+ gzip were used.
+
+ *) Bugfix: the $status variable was equal to 0 if a proxied server
+ returned response in HTTP/0.9 version.
+
+
+Changes with nginx 0.6.16 29 Oct 2007
+
+ *) Change: now the uname(2) is used on Linux instead of procfs.
+ Thanks to Ilya Novikov.
+
+ *) Bugfix: if the "?" character was in a "error_page" directive, then it
+ was escaped in a proxied request; the bug had appeared in 0.6.11.
+
+ *) Bugfix: compatibility with mget.
+
+
+Changes with nginx 0.6.15 22 Oct 2007
+
+ *) Feature: Cygwin compatibility.
+ Thanks to Vladimir Kutakov.
+
+ *) Feature: the "merge_slashes" directive.
+
+ *) Feature: the "gzip_vary" directive.
+
+ *) Feature: the "server_tokens" directive.
+
+ *) Bugfix: nginx did not unescape URI in the "include" SSI command.
+
+ *) Bugfix: the segmentation fault was occurred on start or while
+ reconfiguration if variable was used in the "charset" or
+ "source_charset" directives.
+
+ *) Bugfix: nginx returned the 400 response on requests like
+ "GET http://www.domain.com HTTP/1.0".
+ Thanks to James Oakley.
+
+ *) Bugfix: if request with request body was redirected using the
+ "error_page" directive, then nginx tried to read the request body
+ again; the bug had appeared in 0.6.7.
+
+ *) Bugfix: a segmentation fault occurred in worker process if no
+ server_name was explicitly defined for server processing request; the
+ bug had appeared in 0.6.7.
+
+
+Changes with nginx 0.6.14 15 Oct 2007
+
+ *) Change: now by default the "echo" SSI command uses entity encoding.
+
+ *) Feature: the "encoding" parameter in the "echo" SSI command.
+
+ *) Feature: the "access_log" directive may be used inside the
+ "limit_except" block.
+
+ *) Bugfix: if all upstream servers were failed, then all servers had got
+ weight the was equal one until servers became alive; the bug had
+ appeared in 0.6.6.
+
+ *) Bugfix: a segmentation fault occurred in worker process if
+ $date_local and $date_gmt were used outside the
+ ngx_http_ssi_filter_module.
+
+ *) Bugfix: a segmentation fault might occur in worker process if debug
+ log was enabled.
+ Thanks to Andrei Nigmatulin.
+
+ *) Bugfix: ngx_http_memcached_module did not set
+ $upstream_response_time.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a worker process may got caught in an endless loop, if the
+ memcached was used.
+
+ *) Bugfix: nginx supported low case only "close" and "keep-alive" values
+ in the "Connection" request header line; the bug had appeared in
+ 0.6.11.
+
+ *) Bugfix: sub_filter did not work with empty substitution.
+
+ *) Bugfix: in sub_filter parsing.
+
+
+Changes with nginx 0.6.13 24 Sep 2007
+
+ *) Bugfix: nginx did not close directory file on HEAD request if
+ autoindex was used.
+ Thanks to Arkadiusz Patyk.
+
+
+Changes with nginx 0.6.12 21 Sep 2007
+
+ *) Change: mail proxy was split on three modules: pop3, imap and smtp.
+
+ *) Feature: the --without-mail_pop3_module, --without-mail_imap_module,
+ and --without-mail_smtp_module configuration parameters.
+
+ *) Feature: the "smtp_greeting_delay" and "smtp_client_buffer"
+ directives of the ngx_mail_smtp_module.
+
+ *) Bugfix: the trailing wildcards did not work; the bug had appeared in
+ 0.6.9.
+
+ *) Bugfix: nginx could not start on Solaris if the shared PCRE library
+ located in non-standard place was used.
+
+ *) Bugfix: the "proxy_hide_header" and "fastcgi_hide_header" directives
+ did not hide response header lines whose name was longer than 32
+ characters.
+ Thanks to Manlio Perillo.
+
+
+Changes with nginx 0.6.11 11 Sep 2007
+
+ *) Bugfix: active connection counter always increased if mail proxy was
+ used.
+
+ *) Bugfix: if backend returned response header only using non-buffered
+ proxy, then nginx closed backend connection on timeout.
+
+ *) Bugfix: nginx did not support several "Connection" request header
+ lines.
+
+ *) Bugfix: if the "max_fails" was set for upstream server, then after
+ first failure server weight was always one; the bug had appeared in
+ 0.6.6.
+
+
+Changes with nginx 0.6.10 03 Sep 2007
+
+ *) Feature: the "open_file_cache", "open_file_cache_retest", and
+ "open_file_cache_errors" directives.
+
+ *) Bugfix: socket leak; the bug had appeared in 0.6.7.
+
+ *) Bugfix: a charset set by the "charset" directive was not appended to
+ the "Content-Type" header set by $r->send_http_header().
+
+ *) Bugfix: a segmentation fault might occur in worker process if
+ /dev/poll method was used.
+
+
+Changes with nginx 0.6.9 28 Aug 2007
+
+ *) Bugfix: a worker process may got caught in an endless loop, if the
+ HTTPS protocol was used; the bug had appeared in 0.6.7.
+
+ *) Bugfix: if server listened on two addresses or ports and trailing
+ wildcard was used, then nginx did not run.
+
+ *) Bugfix: the "ip_hash" directive might incorrectly mark servers as
+ down.
+
+ *) Bugfix: nginx could not be built on amd64; the bug had appeared in
+ 0.6.8.
+
+
+Changes with nginx 0.6.8 20 Aug 2007
+
+ *) Change: now nginx tries to set the "worker_priority",
+ "worker_rlimit_nofile", "worker_rlimit_core", and
+ "worker_rlimit_sigpending" without super-user privileges.
+
+ *) Change: now nginx escapes space and "%" in request to a mail proxy
+ authentication server.
+
+ *) Change: now nginx escapes "%" in $memcached_key variable.
+
+ *) Bugfix: nginx used path relative to configuration prefix for
+ non-absolute configuration file path specified in the "-c" key; the
+ bug had appeared in 0.6.6.
+
+ *) Bugfix: nginx did not work on FreeBSD/sparc64.
+
+
+Changes with nginx 0.6.7 15 Aug 2007
+
+ *) Change: now the paths specified in the "include",
+ "auth_basic_user_file", "perl_modules", "ssl_certificate",
+ "ssl_certificate_key", and "ssl_client_certificate" directives are
+ relative to directory of nginx configuration file nginx.conf, but not
+ to nginx prefix directory.
+
+ *) Change: the --sysconfdir=PATH option in configure was canceled.
+
+ *) Change: the special make target "upgrade1" was defined for online
+ upgrade of 0.1.x versions.
+
+ *) Feature: the "server_name" and "valid_referers" directives support
+ regular expressions.
+
+ *) Feature: the "server" directive in the "upstream" context supports
+ the "backup" parameter.
+
+ *) Feature: the ngx_http_perl_module supports the
+ $r->discard_request_body.
+
+ *) Feature: the "add_header Last-Modified ..." directive changes the
+ "Last-Modified" response header line.
+
+ *) Bugfix: if a response different than 200 was returned to a request
+ with body and connection went to the keep-alive state after the
+ request, then nginx returned 400 for the next request.
+
+ *) Bugfix: a segmentation fault occurred in worker process if invalid
+ address was set in the "auth_http" directive.
+
+ *) Bugfix: now nginx uses default listen backlog value 511 on all
+ platforms except FreeBSD.
+ Thanks to Jiang Hong.
+
+ *) Bugfix: a worker process may got caught in an endless loop, if a
+ "server" inside "upstream" block was marked as "down"; the bug had
+ appeared in 0.6.6.
+
+ *) Bugfix: now Solaris sendfilev() is not used to transfer the client
+ request body to FastCGI-server via the unix domain socket.
+
+
+Changes with nginx 0.6.6 30 Jul 2007
+
+ *) Feature: the --sysconfdir=PATH option in configure.
+
+ *) Feature: named locations.
+
+ *) Feature: the $args variable can be set with the "set" directive.
+
+ *) Feature: the $is_args variable.
+
+ *) Bugfix: fair big weight upstream balancer.
+
+ *) Bugfix: if a client has closed connection to mail proxy then nginx
+ might not close connection to backend.
+
+ *) Bugfix: if the same host without specified port was used as backend
+ for HTTP and HTTPS, then nginx used only one port - 80 or 443.
+
+ *) Bugfix: fix building on Solaris/amd64 by Sun Studio 11 and early
+ versions; the bug had appeared in 0.6.4.
+
+
+Changes with nginx 0.6.5 23 Jul 2007
+
+ *) Feature: $nginx_version variable.
+ Thanks to Nick S. Grechukh.
+
+ *) Feature: the mail proxy supports AUTHENTICATE in IMAP mode.
+ Thanks to Maxim Dounin.
+
+ *) Feature: the mail proxy supports STARTTLS in SMTP mode.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: now nginx escapes space in $memcached_key variable.
+
+ *) Bugfix: nginx was incorrectly built by Sun Studio on Solaris/amd64.
+ Thanks to Jiang Hong.
+
+ *) Bugfix: of minor potential bugs.
+ Thanks to Coverity's Scan.
+
+
+Changes with nginx 0.6.4 17 Jul 2007
+
+ *) Security: the "msie_refresh" directive allowed XSS.
+ Thanks to Maxim Boguk.
+
+ *) Change: the "proxy_store" and "fastcgi_store" directives were
+ changed.
+
+ *) Feature: the "proxy_store_access" and "fastcgi_store_access"
+ directives.
+
+ *) Bugfix: nginx did not work on Solaris/sparc64 if it was built by Sun
+ Studio.
+ Thanks to Andrei Nigmatulin.
+
+ *) Workaround: for Sun Studio 12.
+ Thanks to Jiang Hong.
+
+
+Changes with nginx 0.6.3 12 Jul 2007
+
+ *) Feature: the "proxy_store" and "fastcgi_store" directives.
+
+ *) Bugfix: a segmentation fault might occur in worker process if the
+ "auth_http_header" directive was used.
+ Thanks to Maxim Dounin.
+
+ *) Bugfix: a segmentation fault occurred in worker process if the
+ CRAM-MD5 authentication method was used, but it was not enabled.
+
+ *) Bugfix: a segmentation fault might occur in worker process when the
+ HTTPS protocol was used in the "proxy_pass" directive.
+
+ *) Bugfix: a segmentation fault might occur in worker process if the
+ eventport method was used.
+
+ *) Bugfix: the "proxy_ignore_client_abort" and
+ "fastcgi_ignore_client_abort" directives did not work; the bug had
+ appeared in 0.5.13.
+
+
+Changes with nginx 0.6.2 09 Jul 2007
+
+ *) Bugfix: if the FastCGI header was split in records, then nginx passed
+ garbage in the header to a client.
+
+
+Changes with nginx 0.6.1 17 Jun 2007
+
+ *) Bugfix: in SSI parsing.
+
+ *) Bugfix: if remote SSI subrequest was used, then posterior local file
+ subrequest might transferred to client in wrong order.
+
+ *) Bugfix: large SSI inclusions buffered in temporary files were
+ truncated.
+
+ *) Bugfix: the perl $$ variable value in ngx_http_perl_module was equal
+ to the master process identification number.
+
+
+Changes with nginx 0.6.0 14 Jun 2007
+
+ *) Feature: the "server_name", "map", and "valid_referers" directives
+ support the "www.example.*" wildcards.
+
+
+Changes with nginx 0.5.25 11 Jun 2007
+
+ *) Bugfix: nginx could not be built with the
+ --without-http_rewrite_module parameter; the bug had appeared in
+ 0.5.24.
+
+
+Changes with nginx 0.5.24 06 Jun 2007
+
+ *) Security: the "ssl_verify_client" directive did not work if request
+ was made using HTTP/0.9.
+
+ *) Bugfix: a part of response body might be passed uncompressed if gzip
+ was used; the bug had appeared in 0.5.23.
+
+
+Changes with nginx 0.5.23 04 Jun 2007
+
+ *) Feature: the ngx_http_ssl_module supports Server Name Indication TLS
+ extension.
+
+ *) Feature: the "fastcgi_catch_stderr" directive.
+ Thanks to Nick S. Grechukh, OWOX project.
+
+ *) Bugfix: a segmentation fault occurred in master process if two
+ virtual servers should bind() to the overlapping ports.
+
+ *) Bugfix: if nginx was built with ngx_http_perl_module and perl
+ supported threads, then during second reconfiguration the error
+ messages "panic: MUTEX_LOCK" and "perl_parse() failed" were issued.
+
+ *) Bugfix: in the HTTPS protocol in the "proxy_pass" directive.
+
+
+Changes with nginx 0.5.22 29 May 2007
+
+ *) Bugfix: a big request body might not be passed to backend; the bug
+ had appeared in 0.5.21.
+
+
+Changes with nginx 0.5.21 28 May 2007
+
+ *) Bugfix: if server has more than about ten locations, then regex
+ locations might be choosen not in that order as they were specified.
+
+ *) Bugfix: a worker process may got caught in an endless loop on 64-bit
+ platform, if the 33-rd or next in succession backend has failed.
+ Thanks to Anton Povarov.
+
+ *) Bugfix: a bus error might occur on Solaris/sparc64 if the PCRE
+ library was used.
+ Thanks to Andrei Nigmatulin.
+
+ *) Bugfix: in the HTTPS protocol in the "proxy_pass" directive.
+
+
+Changes with nginx 0.5.20 07 May 2007
+
+ *) Feature: the "sendfile_max_chunk" directive.
+
+ *) Feature: the "$http_...", "$sent_http_...", and "$upstream_http_..."
+ variables may be changed using the "set" directive.
+
+ *) Bugfix: a segmentation fault might occur in worker process if the SSI
+ command 'if expr="$var = /"' was used.
+
+ *) Bugfix: trailing boundary of multipart range response was transferred
+ incorrectly.
+ Thanks to Evan Miller.
+
+ *) Bugfix: nginx did not work on Solaris/sparc64 if it was built by Sun
+ Studio.
+ Thanks to Andrei Nigmatulin.
+
+ *) Bugfix: the ngx_http_perl_module could not be built by Solaris make.
+ Thanks to Andrei Nigmatulin.
+
+
+Changes with nginx 0.5.19 24 Apr 2007
+
+ *) Change: now the $request_time variable has millisecond precision.
+
+ *) Change: the method $r->rflush of ngx_http_perl_module was renamed to
+ the $r->flush.
+
+ *) Feature: the $upstream_addr variable.
+
+ *) Feature: the "proxy_headers_hash_max_size" and
+ "proxy_headers_hash_bucket_size" directives.
+ Thanks to Volodymyr Kostyrko.
+
+ *) Bugfix: the files more than 2G could not be transferred using
+ sendfile and limit_rate on 64-bit platforms.
+
+ *) Bugfix: the files more than 2G could not be transferred using
+ sendfile on 64-bit Linux.
+
+
+Changes with nginx 0.5.18 19 Apr 2007
+
+ *) Feature: the ngx_http_sub_filter_module.
+
+ *) Feature: the "$upstream_http_..." variables.
+
+ *) Feature: now the $upstream_status and $upstream_response_time
+ variables keep data about all upstreams before X-Accel-Redirect.
+
+ *) Bugfix: a segmentation fault occurred in master process after first
+ reconfiguration and receiving any signal if nginx was built with
+ ngx_http_perl_module and perl did not support multiplicity; the bug
+ had appeared in 0.5.9.
+
+ *) Bugfix: if perl did not support multiplicity, then after
+ reconfiguration perl code did not work; the bug had appeared in
+ 0.3.38.
+
+
+Changes with nginx 0.5.17 02 Apr 2007
+
+ *) Change: now nginx always returns the 405 status for the TRACE method.
+
+ *) Feature: now nginx supports the "include" directive inside the
+ "types" block.
+
+ *) Bugfix: the $document_root variable usage in the "root" and "alias"
+ directives is disabled: this caused recursive stack overflow.
+
+ *) Bugfix: in the HTTPS protocol in the "proxy_pass" directive.
+
+ *) Bugfix: in some cases non-cachable variables (such as $uri variable)
+ returned old cached value.
+
+
+Changes with nginx 0.5.16 26 Mar 2007
+
+ *) Bugfix: the C-class network was not used as hash key in the "ip_hash"
+ directive.
+ Thanks to Pavel Yarkovoy.
+
+ *) Bugfix: a segmentation fault might occur in worker process if a
+ charset was set in the "Content-Type" header line and the line has
+ trailing ";"; the bug had appeared in 0.3.50.
+
+ *) Bugfix: the "[alert] zero size buf" error when FastCGI server was
+ used and a request body written in a temporary file was multiple of
+ 32K.
+
+ *) Bugfix: nginx could not be built on Solaris without the --with-debug
+ option; the bug had appeared in 0.5.15.
+
+
+Changes with nginx 0.5.15 19 Mar 2007
+
+ *) Feature: the mail proxy supports authenticated SMTP proxying and the
+ "smtp_auth", "smtp_capablities", and "xclient" directives.
+ Thanks to Anton Yuzhaninov and Maxim Dounin.
+
+ *) Feature: now the keep-alive connections are closed just after
+ receiving the reconfiguration signal.
+
+ *) Change: the "imap" and "auth" directives were renamed to the "mail"
+ and "pop3_auth" directives.
+
+ *) Bugfix: a segmentation fault occurred in worker process if the
+ CRAM-MD5 authentication method was used and the APOP method was
+ disabled.
+
+ *) Bugfix: if the "starttls only" directive was used in POP3 protocol,
+ then nginx allowed authentication without switching to the SSL mode.
+
+ *) Bugfix: worker processes did not exit after reconfiguration and did
+ not rotate logs if the eventport method was used.
+
+ *) Bugfix: a worker process may got caught in an endless loop, if the
+ "ip_hash" directive was used.
+
+ *) Bugfix: now nginx does not log some alerts if eventport or /dev/poll
+ methods are used.
+
+
+Changes with nginx 0.5.14 23 Feb 2007
+
+ *) Bugfix: nginx ignored superfluous closing "}" in the end of
+ configuration file.
+
+
+Changes with nginx 0.5.13 19 Feb 2007
+
+ *) Feature: the COPY and MOVE methods.
+
+ *) Bugfix: the ngx_http_realip_module set garbage for requests passed
+ via keep-alive connection.
+
+ *) Bugfix: nginx did not work on big-endian 64-bit Linux.
+ Thanks to Andrei Nigmatulin.
+
+ *) Bugfix: now when IMAP/POP3 proxy receives too long command it closes
+ the connection right away, but not after timeout.
+
+ *) Bugfix: if the "epoll" method was used and a client closed a
+ connection prematurely, then nginx closed the connection after a send
+ timeout only.
+
+ *) Bugfix: nginx could not be built on platforms different from i386,
+ amd64, sparc, and ppc; the bug had appeared in 0.5.8.
+
+
+Changes with nginx 0.5.12 12 Feb 2007
+
+ *) Bugfix: nginx could not be built on platforms different from i386,
+ amd64, sparc, and ppc; the bug had appeared in 0.5.8.
+
+ *) Bugfix: a segmentation fault might occur in worker process if the
+ temporary files were used while working with FastCGI server; the bug
+ had appeared in 0.5.8.
+
+ *) Bugfix: a segmentation fault might occur in worker process if the
+ $fastcgi_script_name variable was logged.
+
+ *) Bugfix: ngx_http_perl_module could not be built on Solaris.
+
+
+Changes with nginx 0.5.11 05 Feb 2007
+
+ *) Feature: now configure detects system PCRE library in MacPorts.
+ Thanks to Chris McGrath.
+
+ *) Bugfix: the response was incorrect if several ranges were requested;
+ the bug had appeared in 0.5.6.
+
+ *) Bugfix: the "create_full_put_path" directive could not create the
+ intermediate directories if no "dav_access" directive was set.
+ Thanks to Evan Miller.
+
+ *) Bugfix: the "0" response code might be logged in the access_log
+ instead of the "400" and "408" error codes.
+
+ *) Bugfix: a segmentation fault might occur in worker process if nginx
+ was built with -O2 optimization.
+
+
+Changes with nginx 0.5.10 26 Jan 2007
+
+ *) Bugfix: while online executable file upgrade the new master process
+ did not inherit the listening sockets; the bug had appeared in 0.5.9.
+
+ *) Bugfix: a segmentation fault might occur in worker process if nginx
+ was built with -O2 optimization; the bug had appeared in 0.5.1.
+
+
+Changes with nginx 0.5.9 25 Jan 2007
+
+ *) Change: now the ngx_http_memcached_module uses the $memcached_key
+ variable value as a key.
+
+ *) Feature: the $memcached_key variable.
+
+ *) Feature: the "clean" parameter in the "client_body_in_file_only"
+ directive.
+
+ *) Feature: the "env" directive.
+
+ *) Feature: the "sendfile" directive is available inside the "if" block.
+
+ *) Feature: now on failure of the writing to access nginx logs a message
+ to error_log, but not more often than once a minute.
+
+ *) Bugfix: the "access_log off" directive did not always turn off the
+ logging.
+
+
+Changes with nginx 0.5.8 19 Jan 2007
+
+ *) Bugfix: a segmentation fault might occur if
+ "client_body_in_file_only on" was used and a request body was small.
+
+ *) Bugfix: a segmentation fault occurred if
+ "client_body_in_file_only on" and "proxy_pass_request_body off" or
+ "fastcgi_pass_request_body off" directives were used, and nginx
+ switched to a next upstream.
+
+ *) Bugfix: if the "proxy_buffering off" directive was used and a client
+ connection was non-active, then the connection was closed after send
+ timeout; the bug had appeared in 0.4.7.
+
+ *) Bugfix: if the "epoll" method was used and a client closed a
+ connection prematurely, then nginx closed the connection after a send
+ timeout only.
+
+ *) Bugfix: the "[alert] zero size buf" error when FastCGI server was
+ used.
+
+ *) Bugfixes in the "limit_zone" directive.
+
+
+Changes with nginx 0.5.7 15 Jan 2007
+
+ *) Feature: the ssl_session_cache storage optimization.
+
+ *) Bugfixes in the "ssl_session_cache" and "limit_zone" directives.
+
+ *) Bugfix: the segmentation fault was occurred on start or while
+ reconfiguration if the "ssl_session_cache" or "limit_zone" directives
+ were used on 64-bit platforms.
+
+ *) Bugfix: a segmentation fault occurred if the "add_before_body" or
+ "add_after_body" directives were used and there was no "Content-Type"
+ header line in response.
+
+ *) Bugfix: the OpenSSL library was always built with the threads
+ support.
+ Thanks to Den Ivanov.
+
+ *) Bugfix: the PCRE-6.5+ library and the icc compiler compatibility.
+
+
+Changes with nginx 0.5.6 09 Jan 2007
+
+ *) Change: now the ngx_http_index_module ignores all methods except the
+ GET, HEAD, and POST methods.
+
+ *) Feature: the ngx_http_limit_zone_module.
+
+ *) Feature: the $binary_remote_addr variable.
+
+ *) Feature: the "ssl_session_cache" directives of the
+ ngx_http_ssl_module and ngx_imap_ssl_module.
+
+ *) Feature: the DELETE method supports recursive removal.
+
+ *) Bugfix: the byte-ranges were transferred incorrectly if the
+ $r->sendfile() was used.
+
+
+Changes with nginx 0.5.5 24 Dec 2006
+
+ *) Change: the -v switch does not show compiler information any more.
+
+ *) Feature: the -V switch.
+
+ *) Feature: the "worker_rlimit_core" directive supports size in K, M,
+ and G.
+
+ *) Bugfix: the nginx.pm module now could be installed by an unprivileged
+ user.
+
+ *) Bugfix: a segmentation fault might occur if the $r->request_body or
+ $r->request_body_file methods were used.
+
+ *) Bugfix: the ppc platform specific bugs.
+
+
+Changes with nginx 0.5.4 15 Dec 2006
+
+ *) Feature: the "perl" directive may be used inside the "limit_except"
+ block.
+
+ *) Bugfix: the ngx_http_dav_module required the "Date" request header
+ line for the DELETE method.
+
+ *) Bugfix: if one only parameter was used in the "dav_access" directive,
+ then nginx might report about configuration error.
+
+ *) Bugfix: a segmentation fault might occur if the $host variable was
+ used; the bug had appeared in 0.4.14.
+
+
+Changes with nginx 0.5.3 13 Dec 2006
+
+ *) Feature: the ngx_http_perl_module supports the $r->status,
+ $r->log_error, and $r->sleep methods.
+
+ *) Feature: the $r->variable method supports variables that do not exist
+ in nginx configuration.
+
+ *) Bugfix: the $r->has_request_body method did not work.
+
+
+Changes with nginx 0.5.2 11 Dec 2006
+
+ *) Bugfix: if the "proxy_pass" directive used the name of the "upstream"
+ block, then nginx tried to resolve the name; the bug had appeared in
+ 0.5.1.
+
+
+Changes with nginx 0.5.1 11 Dec 2006
+
+ *) Bugfix: the "post_action" directive might not run after a
+ unsuccessful completion of a request.
+
+ *) Workaround: for Eudora for Mac; the bug had appeared in 0.4.11.
+ Thanks to Bron Gondwana.
+
+ *) Bugfix: if the "upstream" name was used in the "fastcgi_pass", then
+ the message "no port in upstream" was issued; the bug had appeared in
+ 0.5.0.
+
+ *) Bugfix: if the "proxy_pass" and "fastcgi_pass" directives used the
+ same servers but different ports, then these directives uses the
+ first described port; the bug had appeared in 0.5.0.
+
+ *) Bugfix: if the "proxy_pass" and "fastcgi_pass" directives used the
+ unix domain sockets, then these directives used first described
+ socket; the bug had appeared in 0.5.0.
+
+ *) Bugfix: ngx_http_auth_basic_module ignored the user if it was in the
+ last line in the password file and there was no the carriage return,
+ the line feed, or the ":" symbol after the password.
+
+ *) Bugfix: the $upstream_response_time variable might be equal to
+ "0.000", although response time was more than 1 millisecond.
+
+
+Changes with nginx 0.5.0 04 Dec 2006
+
+ *) Change: the parameters in the "%name" form in the "log_format"
+ directive are not supported anymore.
+
+ *) Change: the "proxy_upstream_max_fails",
+ "proxy_upstream_fail_timeout", "fastcgi_upstream_max_fails",
+ "fastcgi_upstream_fail_timeout", "memcached_upstream_max_fails", and
+ "memcached_upstream_fail_timeout" directives are not supported
+ anymore.
+
+ *) Feature: the "server" directive in the "upstream" context supports
+ the "max_fails", "fail_timeout", and "down" parameters.
+
+ *) Feature: the "ip_hash" directive inside the "upstream" block.
+
+ *) Feature: the WAIT status in the "Auth-Status" header line of the
+ IMAP/POP3 proxy authentication server response.
+
+ *) Bugfix: nginx could not be built on 64-bit platforms; the bug had
+ appeared in 0.4.14.
+
+
+Changes with nginx 0.4.14 27 Nov 2006
+
+ *) Feature: the "proxy_pass_error_message" directive in IMAP/POP3 proxy.
+
+ *) Feature: now configure detects system PCRE library on FreeBSD, Linux,
+ and NetBSD.
+
+ *) Bugfix: ngx_http_perl_module did not work with perl built with the
+ threads support; the bug had appeared in 0.3.38.
+
+ *) Bugfix: ngx_http_perl_module did not work if perl was called
+ recursively.
+
+ *) Bugfix: nginx ignored a host name in a request line.
+
+ *) Bugfix: a worker process may got caught in an endless loop, if a
+ FastCGI server sent too many data to the stderr.
+
+ *) Bugfix: the $upstream_response_time variable may be negative if the
+ system time was changed backward.
+
+ *) Bugfix: the "Auth-Login-Attempt" parameter was not sent to IMAP/POP3
+ proxy authentication server when POP3 was used.
+
+ *) Bugfix: a segmentation fault might occur if connect to IMAP/POP3
+ proxy authentication server failed.
+
+
+Changes with nginx 0.4.13 15 Nov 2006
+
+ *) Feature: the "proxy_pass" directive may be used inside the
+ "limit_except" block.
+
+ *) Feature: the "limit_except" directive supports all WebDAV methods.
+
+ *) Bugfix: if the "add_before_body" directive was used without the
+ "add_after_body" directive, then a response did not transferred
+ complete.
+
+ *) Bugfix: a large request body did not receive if the epoll method and
+ the deferred accept() were used.
+
+ *) Bugfix: a charset could not be set for ngx_http_autoindex_module
+ responses; the bug had appeared in 0.3.50.
+
+ *) Bugfix: the "[alert] zero size buf" error when FastCGI server was
+ used;
+
+ *) Bugfix: the --group= configuration parameter was ignored.
+ Thanks to Thomas Moschny.
+
+ *) Bugfix: the 50th subrequest in SSI response did not work; the bug had
+ appeared in 0.3.50.
+
+
+Changes with nginx 0.4.12 31 Oct 2006
+
+ *) Feature: the ngx_http_perl_module supports the $r->variable method.
+
+ *) Bugfix: if a big static file was included using SSI in a response,
+ then the response may be transferred incomplete.
+
+ *) Bugfix: nginx did not omit the "#fragment" part in URI.
+
+
+Changes with nginx 0.4.11 25 Oct 2006
+
+ *) Feature: the POP3 proxy supports the AUTH LOGIN PLAIN and CRAM-MD5.
+
+ *) Feature: the ngx_http_perl_module supports the $r->allow_ranges
+ method.
+
+ *) Bugfix: if the APOP was enabled in the POP3 proxy, then the USER/PASS
+ commands might not work; the bug had appeared in 0.4.10.
+
+
+Changes with nginx 0.4.10 23 Oct 2006
+
+ *) Feature: the POP3 proxy supports the APOP command.
+
+ *) Bugfix: if the select, poll or /dev/poll methods were used, then
+ while waiting authentication server response the IMAP/POP3 proxy
+ hogged CPU.
+
+ *) Bugfix: a segmentation fault might occur if the $server_addr variable
+ was used in the "map" directive.
+
+ *) Bugfix: the ngx_http_flv_module did not support the byte ranges for
+ full responses; the bug had appeared in 0.4.7.
+
+ *) Bugfix: nginx could not be built on Debian amd64; the bug had
+ appeared in 0.4.9.
+
+
+Changes with nginx 0.4.9 13 Oct 2006
+
+ *) Feature: the "set" parameter in the "include" SSI command.
+
+ *) Feature: the ngx_http_perl_module now tests the nginx.pm module
+ version.
+
+
+Changes with nginx 0.4.8 11 Oct 2006
+
+ *) Bugfix: if an "include" SSI command were before another "include" SSI
+ command with a "wait" parameter, then the "wait" parameter might not
+ work.
+
+ *) Bugfix: the ngx_http_flv_module added the FLV header to the full
+ responses.
+ Thanks to Alexey Kovyrin.
+
+
+Changes with nginx 0.4.7 10 Oct 2006
+
+ *) Feature: the ngx_http_flv_module.
+
+ *) Feature: the $request_body_file variable.
+
+ *) Feature: the "charset" and "source_charset" directives support the
+ variables.
+
+ *) Bugfix: if an "include" SSI command were before another "include" SSI
+ command with a "wait" parameter, then the "wait" parameter might not
+ work.
+
+ *) Bugfix: if the "proxy_buffering off" directive was used or while
+ working with memcached the connections might not be closed on
+ timeout.
+
+ *) Bugfix: nginx did not run on 64-bit platforms except amd64, sparc64,
+ and ppc64.
+
+
+Changes with nginx 0.4.6 06 Oct 2006
+
+ *) Bugfix: nginx did not run on 64-bit platforms except amd64, sparc64,
+ and ppc64.
+
+ *) Bugfix: nginx sent the chunked response for HTTP/1.1 request,
+ if its length was set by text string in the
+ $r->headers_out("Content-Length", ...) method.
+
+ *) Bugfix: after redirecting error by an "error_page" directive any
+ ngx_http_rewrite_module directive returned this error code; the bug
+ had appeared in 0.4.4.
+
+
+Changes with nginx 0.4.5 02 Oct 2006
+
+ *) Bugfix: nginx could not be built on Linux and Solaris; the bug had
+ appeared in 0.4.4.
+
+
+Changes with nginx 0.4.4 02 Oct 2006
+
+ *) Feature: the $scheme variable.
+
+ *) Feature: the "expires" directive supports the "max" parameter.
+
+ *) Feature: the "include" directive supports the "*" mask.
+ Thanks to Jonathan Dance.
+
+ *) Bugfix: the "return" directive always overrode the "error_page"
+ response code redirected by the "error_page" directive.
+
+ *) Bugfix: a segmentation fault occurred if zero-length body was in PUT
+ method.
+
+ *) Bugfix: the redirect was changed incorrectly if the variables were
+ used in the "proxy_redirect" directive.
+
+
+Changes with nginx 0.4.3 26 Sep 2006
+
+ *) Change: now the 499 error could not be redirected using an
+ "error_page" directive.
+
+ *) Feature: the Solaris 10 event ports support.
+
+ *) Feature: the ngx_http_browser_module.
+
+ *) Bugfix: a segmentation fault may occur while redirecting the 400
+ error to the proxied server using a "proxy_pass" directive.
+
+ *) Bugfix: a segmentation fault occurred if an unix domain socket was
+ used in a "proxy_pass" directive; the bug had appeared in 0.3.47.
+
+ *) Bugfix: SSI did work with memcached and nonbuffered responses.
+
+ *) Workaround: of the Sun Studio PAUSE hardware capability bug.
+
+
+Changes with nginx 0.4.2 14 Sep 2006
+
+ *) Bugfix: the O_NOATIME flag support on Linux was canceled; the bug had
+ appeared in 0.4.1.
+
+
+Changes with nginx 0.4.1 14 Sep 2006
+
+ *) Bugfix: the DragonFlyBSD compatibility.
+ Thanks to Pavel Nazarov.
+
+ *) Workaround: of bug in 64-bit Linux sendfile(), when file is more than
+ 2G.
+
+ *) Feature: now on Linux nginx uses O_NOATIME flag for static requests.
+ Thanks to Yusuf Goolamabbas.
+
+
+Changes with nginx 0.4.0 30 Aug 2006
+
+ *) Change in internal API: the HTTP modules initialization was moved
+ from the init module phase to the HTTP postconfiguration phase.
+
+ *) Change: now the request body is not read beforehand for the
+ ngx_http_perl_module: it's required to start the reading using the
+ $r->has_request_body method.
+
+ *) Feature: the ngx_http_perl_module supports the DECLINED return code.
+
+ *) Feature: the ngx_http_dav_module supports the incoming "Date" header
+ line for the PUT method.
+
+ *) Feature: the "ssi" directive is available inside the "if" block.
+
+ *) Bugfix: a segmentation fault occurred if there was an "index"
+ directive with variables and the first index name was without
+ variables; the bug had appeared in 0.1.29.
+
+
+Changes with nginx 0.3.61 28 Aug 2006
+
+ *) Change: now the "tcp_nodelay" directive is turned on by default.
+
+ *) Feature: the "msie_refresh" directive.
+
+ *) Feature: the "recursive_error_pages" directive.
+
+ *) Bugfix: the "rewrite" directive returned incorrect redirect, if the
+ redirect had the captured escaped symbols from original URI.
+
+
+Changes with nginx 0.3.60 18 Aug 2006
+
+ *) Bugfix: a worker process may got caught in an endless loop while an
+ error redirection; the bug had appeared in 0.3.59.
+
+
+Changes with nginx 0.3.59 16 Aug 2006
+
+ *) Feature: now is possible to do several redirection using the
+ "error_page" directive.
+
+ *) Bugfix: the "dav_access" directive did not support three parameters.
+
+ *) Bugfix: the "error_page" directive did not changes the "Content-Type"
+ header line after the "X-Accel-Redirect" was used; the bug had
+ appeared in 0.3.58.
+
+
+Changes with nginx 0.3.58 14 Aug 2006
+
+ *) Feature: the "error_page" directive supports the variables.
+
+ *) Change: now the procfs interface instead of sysctl is used on Linux.
+
+ *) Change: now the "Content-Type" header line is inherited from first
+ response when the "X-Accel-Redirect" was used.
+
+ *) Bugfix: the "error_page" directive did not redirect the 413 error.
+
+ *) Bugfix: the trailing "?" did not remove old arguments if no new
+ arguments were added to a rewritten URI.
+
+ *) Bugfix: nginx could not run on 64-bit FreeBSD 7.0-CURRENT.
+
+
+Changes with nginx 0.3.57 09 Aug 2006
+
+ *) Feature: the $ssl_client_serial variable.
+
+ *) Bugfix: in the "!-e" operator of the "if" directive.
+ Thanks to Andrian Budanstov.
+
+ *) Bugfix: while a client certificate verification nginx did not send to
+ a client the required certificates information.
+
+ *) Bugfix: the $document_root variable did not support the variables in
+ the "root" directive.
+
+
+Changes with nginx 0.3.56 04 Aug 2006
+
+ *) Feature: the "dav_access" directive.
+
+ *) Feature: the "if" directive supports the "-d", "!-d", "-e", "!-e",
+ "-x", and "!-x" operators.
+
+ *) Bugfix: a segmentation fault occurred if a request returned a
+ redirect and some sent to client header lines were logged in the
+ access log.
+
+
+Changes with nginx 0.3.55 28 Jul 2006
+
+ *) Feature: the "stub" parameter in the "include" SSI command.
+
+ *) Feature: the "block" SSI command.
+
+ *) Feature: the unicode2nginx script was added to contrib.
+
+ *) Bugfix: if a "root" was specified by variable only, then the root was
+ relative to a server prefix.
+
+ *) Bugfix: if the request contained "//" or "/./" and escaped symbols
+ after them, then the proxied request was sent unescaped.
+
+ *) Bugfix: the $r->header_in("Cookie") of the ngx_http_perl_module now
+ returns all "Cookie" header lines.
+
+ *) Bugfix: a segmentation fault occurred if
+ "client_body_in_file_only on" was used and nginx switched to a next
+ upstream.
+
+ *) Bugfix: on some condition while reconfiguration character codes
+ inside the "charset_map" may be treated invalid; the bug had appeared
+ in 0.3.50.
+
+
+Changes with nginx 0.3.54 11 Jul 2006
+
+ *) Feature: nginx now logs the subrequest information to the error log.
+
+ *) Feature: the "proxy_next_upstream", "fastcgi_next_upstream", and
+ "memcached_next_upstream" directives support the "off" parameter.
+
+ *) Feature: the "debug_connection" directive supports the CIDR address
+ form.
+
+ *) Bugfix: if a response of proxied server or FastCGI server was
+ converted from UTF-8 or back, then it may be transferred incomplete.
+
+ *) Bugfix: the $upstream_response_time variable had the time of the
+ first request to a backend only.
+
+ *) Bugfix: nginx could not be built on amd64 platform; the bug had
+ appeared in 0.3.53.
+
+
+Changes with nginx 0.3.53 07 Jul 2006
+
+ *) Change: the "add_header" directive adds the string to 204, 301, and
+ 302 responses.
+
+ *) Feature: the "server" directive in the "upstream" context supports
+ the "weight" parameter.
+
+ *) Feature: the "server_name" directive supports the "*" wildcard.
+
+ *) Feature: nginx supports the request body size more than 2G.
+
+ *) Bugfix: if a client was successfully authorized using "satisfy_any
+ on", then anyway the message "access forbidden by rule" was written
+ in the log.
+
+ *) Bugfix: the "PUT" method may erroneously not create a file and return
+ the 409 code.
+
+ *) Bugfix: if the IMAP/POP3 backend returned an error, then nginx
+ continued proxying anyway.
+
+
+Changes with nginx 0.3.52 03 Jul 2006
+
+ *) Change: the ngx_http_index_module behavior for the "POST /" requests
+ is reverted to the 0.3.40 version state: the module now does not
+ return the 405 error.
+
+ *) Bugfix: the worker process may got caught in an endless loop if the
+ limit rate was used; the bug had appeared in 0.3.37.
+
+ *) Bugfix: ngx_http_charset_module logged "unknown charset" alert, even
+ if the recoding was not needed; the bug had appeared in 0.3.50.
+
+ *) Bugfix: if a code response of the PUT request was 409, then a
+ temporary file was not removed.
+
+
+Changes with nginx 0.3.51 30 Jun 2006
+
+ *) Bugfix: the "<" symbols might disappeared some conditions in the SSI;
+ the bug had appeared in 0.3.50.
+
+
+Changes with nginx 0.3.50 28 Jun 2006
+
+ *) Change: the "proxy_redirect_errors" and "fastcgi_redirect_errors"
+ directives was renamed to the "proxy_intercept_errors" and
+ "fastcgi_intercept_errors" directives.
+
+ *) Feature: the ngx_http_charset_module supports the recoding from the
+ single byte encodings to the UTF-8 encoding and back.
+
+ *) Feature: the "X-Accel-Charset" response header line is supported in
+ proxy and FastCGI mode.
+
+ *) Bugfix: the "\" escape symbol in the "\"" and "\'" pairs in the SSI
+ command was removed only if the command also has the "$" symbol.
+
+ *) Bugfix: the "" CRLF
+"" CRLF
+"" CRLF
+"" CRLF
+"" CRLF
+"" CRLF
+;
+
+
+static u_char ngx_http_msie_refresh_head[] =
+"
" CRLF;
+
+
+static char ngx_http_error_301_page[] =
+"" CRLF
+"301 Moved Permanently" CRLF
+"" CRLF
+"301 Moved Permanently
" CRLF
+;
+
+
+static char ngx_http_error_302_page[] =
+"" CRLF
+"302 Found" CRLF
+"" CRLF
+"302 Found
" CRLF
+;
+
+
+static char ngx_http_error_303_page[] =
+"" CRLF
+"303 See Other" CRLF
+"" CRLF
+"303 See Other
" CRLF
+;
+
+
+static char ngx_http_error_307_page[] =
+"" CRLF
+"307 Temporary Redirect" CRLF
+"" CRLF
+"307 Temporary Redirect
" CRLF
+;
+
+
+static char ngx_http_error_400_page[] =
+"" CRLF
+"400 Bad Request" CRLF
+"" CRLF
+"400 Bad Request
" CRLF
+;
+
+
+static char ngx_http_error_401_page[] =
+"" CRLF
+"401 Authorization Required" CRLF
+"" CRLF
+"401 Authorization Required
" CRLF
+;
+
+
+static char ngx_http_error_402_page[] =
+"" CRLF
+"402 Payment Required" CRLF
+"" CRLF
+"402 Payment Required
" CRLF
+;
+
+
+static char ngx_http_error_403_page[] =
+"" CRLF
+"403 Forbidden" CRLF
+"" CRLF
+"403 Forbidden
" CRLF
+;
+
+
+static char ngx_http_error_404_page[] =
+"" CRLF
+"404 Not Found" CRLF
+"" CRLF
+"404 Not Found
" CRLF
+;
+
+
+static char ngx_http_error_405_page[] =
+"" CRLF
+"405 Not Allowed" CRLF
+"" CRLF
+"405 Not Allowed
" CRLF
+;
+
+
+static char ngx_http_error_406_page[] =
+"" CRLF
+"406 Not Acceptable" CRLF
+"" CRLF
+"406 Not Acceptable
" CRLF
+;
+
+
+static char ngx_http_error_408_page[] =
+"" CRLF
+"408 Request Time-out" CRLF
+"" CRLF
+"408 Request Time-out
" CRLF
+;
+
+
+static char ngx_http_error_409_page[] =
+"" CRLF
+"409 Conflict" CRLF
+"" CRLF
+"409 Conflict
" CRLF
+;
+
+
+static char ngx_http_error_410_page[] =
+"" CRLF
+"410 Gone" CRLF
+"" CRLF
+"410 Gone
" CRLF
+;
+
+
+static char ngx_http_error_411_page[] =
+"" CRLF
+"411 Length Required" CRLF
+"" CRLF
+"411 Length Required
" CRLF
+;
+
+
+static char ngx_http_error_412_page[] =
+"" CRLF
+"412 Precondition Failed" CRLF
+"" CRLF
+"412 Precondition Failed
" CRLF
+;
+
+
+static char ngx_http_error_413_page[] =
+"" CRLF
+"413 Request Entity Too Large" CRLF
+"" CRLF
+"413 Request Entity Too Large
" CRLF
+;
+
+
+static char ngx_http_error_414_page[] =
+"" CRLF
+"414 Request-URI Too Large" CRLF
+"" CRLF
+"414 Request-URI Too Large
" CRLF
+;
+
+
+static char ngx_http_error_415_page[] =
+"" CRLF
+"415 Unsupported Media Type" CRLF
+"" CRLF
+"415 Unsupported Media Type
" CRLF
+;
+
+
+static char ngx_http_error_416_page[] =
+"" CRLF
+"416 Requested Range Not Satisfiable" CRLF
+"" CRLF
+"416 Requested Range Not Satisfiable
" CRLF
+;
+
+
+static char ngx_http_error_494_page[] =
+"" CRLF
+"400 Request Header Or Cookie Too Large"
+CRLF
+"" CRLF
+"400 Bad Request
" CRLF
+"Request Header Or Cookie Too Large" CRLF
+;
+
+
+static char ngx_http_error_495_page[] =
+"" CRLF
+"400 The SSL certificate error"
+CRLF
+"" CRLF
+"400 Bad Request
" CRLF
+"The SSL certificate error" CRLF
+;
+
+
+static char ngx_http_error_496_page[] =
+"" CRLF
+"400 No required SSL certificate was sent"
+CRLF
+"" CRLF
+"400 Bad Request
" CRLF
+"No required SSL certificate was sent" CRLF
+;
+
+
+static char ngx_http_error_497_page[] =
+"" CRLF
+"400 The plain HTTP request was sent to HTTPS port"
+CRLF
+"" CRLF
+"400 Bad Request
" CRLF
+"The plain HTTP request was sent to HTTPS port" CRLF
+;
+
+
+static char ngx_http_error_500_page[] =
+"" CRLF
+"500 Internal Server Error" CRLF
+"" CRLF
+"500 Internal Server Error
" CRLF
+;
+
+
+static char ngx_http_error_501_page[] =
+"" CRLF
+"501 Method Not Implemented" CRLF
+"" CRLF
+"501 Method Not Implemented
" CRLF
+;
+
+
+static char ngx_http_error_502_page[] =
+"" CRLF
+"502 Bad Gateway" CRLF
+"" CRLF
+"502 Bad Gateway
" CRLF
+;
+
+
+static char ngx_http_error_503_page[] =
+"" CRLF
+"503 Service Temporarily Unavailable" CRLF
+"" CRLF
+"503 Service Temporarily Unavailable
" CRLF
+;
+
+
+static char ngx_http_error_504_page[] =
+"" CRLF
+"504 Gateway Time-out" CRLF
+"" CRLF
+"504 Gateway Time-out
" CRLF
+;
+
+
+static char ngx_http_error_507_page[] =
+"" CRLF
+"507 Insufficient Storage" CRLF
+"" CRLF
+"507 Insufficient Storage
" CRLF
+;
+
+
+static ngx_str_t ngx_http_error_pages[] = {
+
+ ngx_null_string, /* 201, 204 */
+
+#define NGX_HTTP_LAST_2XX 202
+#define NGX_HTTP_OFF_3XX (NGX_HTTP_LAST_2XX - 201)
+
+ /* ngx_null_string, */ /* 300 */
+ ngx_string(ngx_http_error_301_page),
+ ngx_string(ngx_http_error_302_page),
+ ngx_string(ngx_http_error_303_page),
+ ngx_null_string, /* 304 */
+ ngx_null_string, /* 305 */
+ ngx_null_string, /* 306 */
+ ngx_string(ngx_http_error_307_page),
+
+#define NGX_HTTP_LAST_3XX 308
+#define NGX_HTTP_OFF_4XX (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX)
+
+ ngx_string(ngx_http_error_400_page),
+ ngx_string(ngx_http_error_401_page),
+ ngx_string(ngx_http_error_402_page),
+ ngx_string(ngx_http_error_403_page),
+ ngx_string(ngx_http_error_404_page),
+ ngx_string(ngx_http_error_405_page),
+ ngx_string(ngx_http_error_406_page),
+ ngx_null_string, /* 407 */
+ ngx_string(ngx_http_error_408_page),
+ ngx_string(ngx_http_error_409_page),
+ ngx_string(ngx_http_error_410_page),
+ ngx_string(ngx_http_error_411_page),
+ ngx_string(ngx_http_error_412_page),
+ ngx_string(ngx_http_error_413_page),
+ ngx_string(ngx_http_error_414_page),
+ ngx_string(ngx_http_error_415_page),
+ ngx_string(ngx_http_error_416_page),
+
+#define NGX_HTTP_LAST_4XX 417
+#define NGX_HTTP_OFF_5XX (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX)
+
+ ngx_string(ngx_http_error_494_page), /* 494, request header too large */
+ ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
+ ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
+ ngx_string(ngx_http_error_497_page), /* 497, http to https */
+ ngx_string(ngx_http_error_404_page), /* 498, canceled */
+ ngx_null_string, /* 499, client has closed connection */
+
+ ngx_string(ngx_http_error_500_page),
+ ngx_string(ngx_http_error_501_page),
+ ngx_string(ngx_http_error_502_page),
+ ngx_string(ngx_http_error_503_page),
+ ngx_string(ngx_http_error_504_page),
+ ngx_null_string, /* 505 */
+ ngx_null_string, /* 506 */
+ ngx_string(ngx_http_error_507_page)
+
+#define NGX_HTTP_LAST_5XX 508
+
+};
+
+
+static ngx_str_t ngx_http_get_name = { 3, (u_char *) "GET " };
+
+
+ngx_int_t
+ngx_http_special_response_handler(ngx_http_request_t *r, ngx_int_t error)
+{
+ ngx_uint_t i, err;
+ ngx_http_err_page_t *err_page;
+ ngx_http_core_loc_conf_t *clcf;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http special response: %d, \"%V?%V\"",
+ error, &r->uri, &r->args);
+
+ r->err_status = error;
+
+ if (r->keepalive) {
+ switch (error) {
+ case NGX_HTTP_BAD_REQUEST:
+ case NGX_HTTP_REQUEST_ENTITY_TOO_LARGE:
+ case NGX_HTTP_REQUEST_URI_TOO_LARGE:
+ case NGX_HTTP_TO_HTTPS:
+ case NGX_HTTPS_CERT_ERROR:
+ case NGX_HTTPS_NO_CERT:
+ case NGX_HTTP_INTERNAL_SERVER_ERROR:
+ r->keepalive = 0;
+ }
+ }
+
+ if (r->lingering_close) {
+ switch (error) {
+ case NGX_HTTP_BAD_REQUEST:
+ case NGX_HTTP_TO_HTTPS:
+ case NGX_HTTPS_CERT_ERROR:
+ case NGX_HTTPS_NO_CERT:
+ r->lingering_close = 0;
+ }
+ }
+
+ r->headers_out.content_type.len = 0;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!r->error_page && clcf->error_pages && r->uri_changes != 0) {
+
+ if (clcf->recursive_error_pages == 0) {
+ r->error_page = 1;
+ }
+
+ err_page = clcf->error_pages->elts;
+
+ for (i = 0; i < clcf->error_pages->nelts; i++) {
+ if (err_page[i].status == error) {
+ return ngx_http_send_error_page(r, &err_page[i]);
+ }
+ }
+ }
+
+ r->expect_tested = 1;
+
+ if (ngx_http_discard_request_body(r) != NGX_OK) {
+ error = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (clcf->msie_refresh
+ && r->headers_in.msie
+ && (error == NGX_HTTP_MOVED_PERMANENTLY
+ || error == NGX_HTTP_MOVED_TEMPORARILY))
+ {
+ return ngx_http_send_refresh(r);
+ }
+
+ if (error == NGX_HTTP_CREATED) {
+ /* 201 */
+ err = 0;
+
+ } else if (error == NGX_HTTP_NO_CONTENT) {
+ /* 204 */
+ err = 0;
+
+ } else if (error >= NGX_HTTP_MOVED_PERMANENTLY
+ && error < NGX_HTTP_LAST_3XX)
+ {
+ /* 3XX */
+ err = error - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX;
+
+ } else if (error >= NGX_HTTP_BAD_REQUEST
+ && error < NGX_HTTP_LAST_4XX)
+ {
+ /* 4XX */
+ err = error - NGX_HTTP_BAD_REQUEST + NGX_HTTP_OFF_4XX;
+
+ } else if (error >= NGX_HTTP_NGINX_CODES
+ && error < NGX_HTTP_LAST_5XX)
+ {
+ /* 49X, 5XX */
+ err = error - NGX_HTTP_NGINX_CODES + NGX_HTTP_OFF_5XX;
+ switch (error) {
+ case NGX_HTTP_TO_HTTPS:
+ case NGX_HTTPS_CERT_ERROR:
+ case NGX_HTTPS_NO_CERT:
+ case NGX_HTTP_REQUEST_HEADER_TOO_LARGE:
+ r->err_status = NGX_HTTP_BAD_REQUEST;
+ break;
+ }
+
+ } else {
+ /* unknown code, zero body */
+ err = 0;
+ }
+
+ return ngx_http_send_special_response(r, clcf, err);
+}
+
+
+ngx_int_t
+ngx_http_filter_finalize_request(ngx_http_request_t *r, ngx_module_t *m,
+ ngx_int_t error)
+{
+ void *ctx;
+ ngx_int_t rc;
+
+ ngx_http_clean_header(r);
+
+ ctx = NULL;
+
+ if (m) {
+ ctx = r->ctx[m->ctx_index];
+ }
+
+ /* clear the modules contexts */
+ ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module);
+
+ if (m) {
+ r->ctx[m->ctx_index] = ctx;
+ }
+
+ r->filter_finalize = 1;
+
+ rc = ngx_http_special_response_handler(r, error);
+
+ /* NGX_ERROR resets any pending data */
+
+ switch (rc) {
+
+ case NGX_OK:
+ case NGX_DONE:
+ return NGX_ERROR;
+
+ default:
+ return rc;
+ }
+}
+
+
+void
+ngx_http_clean_header(ngx_http_request_t *r)
+{
+ ngx_memzero(&r->headers_out.status,
+ sizeof(ngx_http_headers_out_t)
+ - offsetof(ngx_http_headers_out_t, status));
+
+ r->headers_out.headers.part.nelts = 0;
+ r->headers_out.headers.part.next = NULL;
+ r->headers_out.headers.last = &r->headers_out.headers.part;
+
+ r->headers_out.content_length_n = -1;
+ r->headers_out.last_modified_time = -1;
+}
+
+
+static ngx_int_t
+ngx_http_send_error_page(ngx_http_request_t *r, ngx_http_err_page_t *err_page)
+{
+ ngx_int_t overwrite;
+ ngx_str_t uri, args;
+ ngx_table_elt_t *location;
+ ngx_http_core_loc_conf_t *clcf;
+
+ overwrite = err_page->overwrite;
+
+ if (overwrite && overwrite != NGX_HTTP_OK) {
+ r->expect_tested = 1;
+ }
+
+ if (overwrite >= 0) {
+ r->err_status = overwrite;
+ }
+
+ if (ngx_http_complex_value(r, &err_page->value, &uri) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (uri.data[0] == '/') {
+
+ if (err_page->value.lengths) {
+ ngx_http_split_args(r, &uri, &args);
+
+ } else {
+ args = err_page->args;
+ }
+
+ if (r->method != NGX_HTTP_HEAD) {
+ r->method = NGX_HTTP_GET;
+ r->method_name = ngx_http_get_name;
+ }
+
+ return ngx_http_internal_redirect(r, &uri, &args);
+ }
+
+ if (uri.data[0] == '@') {
+ return ngx_http_named_location(r, &uri);
+ }
+
+ location = ngx_list_push(&r->headers_out.headers);
+
+ if (location == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (overwrite != NGX_HTTP_MOVED_PERMANENTLY
+ && overwrite != NGX_HTTP_MOVED_TEMPORARILY
+ && overwrite != NGX_HTTP_SEE_OTHER
+ && overwrite != NGX_HTTP_TEMPORARY_REDIRECT)
+ {
+ r->err_status = NGX_HTTP_MOVED_TEMPORARILY;
+ }
+
+ location->hash = 1;
+ ngx_str_set(&location->key, "Location");
+ location->value = uri;
+
+ ngx_http_clear_location(r);
+
+ r->headers_out.location = location;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->msie_refresh && r->headers_in.msie) {
+ return ngx_http_send_refresh(r);
+ }
+
+ return ngx_http_send_special_response(r, clcf, r->err_status
+ - NGX_HTTP_MOVED_PERMANENTLY
+ + NGX_HTTP_OFF_3XX);
+}
+
+
+static ngx_int_t
+ngx_http_send_special_response(ngx_http_request_t *r,
+ ngx_http_core_loc_conf_t *clcf, ngx_uint_t err)
+{
+ u_char *tail;
+ size_t len;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_uint_t msie_padding;
+ ngx_chain_t out[3];
+
+ if (clcf->server_tokens) {
+ len = sizeof(ngx_http_error_full_tail) - 1;
+ tail = ngx_http_error_full_tail;
+
+ } else {
+ len = sizeof(ngx_http_error_tail) - 1;
+ tail = ngx_http_error_tail;
+ }
+
+ msie_padding = 0;
+
+ if (ngx_http_error_pages[err].len) {
+ r->headers_out.content_length_n = ngx_http_error_pages[err].len + len;
+ if (clcf->msie_padding
+ && (r->headers_in.msie || r->headers_in.chrome)
+ && r->http_version >= NGX_HTTP_VERSION_10
+ && err >= NGX_HTTP_OFF_4XX)
+ {
+ r->headers_out.content_length_n +=
+ sizeof(ngx_http_msie_padding) - 1;
+ msie_padding = 1;
+ }
+
+ r->headers_out.content_type_len = sizeof("text/html") - 1;
+ ngx_str_set(&r->headers_out.content_type, "text/html");
+ r->headers_out.content_type_lowcase = NULL;
+
+ } else {
+ r->headers_out.content_length_n = 0;
+ }
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ ngx_http_clear_accept_ranges(r);
+ ngx_http_clear_last_modified(r);
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || r->header_only) {
+ return rc;
+ }
+
+ if (ngx_http_error_pages[err].len == 0) {
+ return ngx_http_send_special(r, NGX_HTTP_LAST);
+ }
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = ngx_http_error_pages[err].data;
+ b->last = ngx_http_error_pages[err].data + ngx_http_error_pages[err].len;
+
+ out[0].buf = b;
+ out[0].next = &out[1];
+
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+
+ b->pos = tail;
+ b->last = tail + len;
+
+ out[1].buf = b;
+ out[1].next = NULL;
+
+ if (msie_padding) {
+ b = ngx_calloc_buf(r->pool);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->memory = 1;
+ b->pos = ngx_http_msie_padding;
+ b->last = ngx_http_msie_padding + sizeof(ngx_http_msie_padding) - 1;
+
+ out[1].next = &out[2];
+ out[2].buf = b;
+ out[2].next = NULL;
+ }
+
+ if (r == r->main) {
+ b->last_buf = 1;
+ }
+
+ b->last_in_chain = 1;
+
+ return ngx_http_output_filter(r, &out[0]);
+}
+
+
+static ngx_int_t
+ngx_http_send_refresh(ngx_http_request_t *r)
+{
+ u_char *p, *location;
+ size_t len, size;
+ uintptr_t escape;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+ ngx_chain_t out;
+
+ len = r->headers_out.location->value.len;
+ location = r->headers_out.location->value.data;
+
+ escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH);
+
+ size = sizeof(ngx_http_msie_refresh_head) - 1
+ + escape + len
+ + sizeof(ngx_http_msie_refresh_tail) - 1;
+
+ r->err_status = NGX_HTTP_OK;
+
+ r->headers_out.content_type_len = sizeof("text/html") - 1;
+ ngx_str_set(&r->headers_out.content_type, "text/html");
+ r->headers_out.content_type_lowcase = NULL;
+
+ r->headers_out.location->hash = 0;
+ r->headers_out.location = NULL;
+
+ r->headers_out.content_length_n = size;
+
+ if (r->headers_out.content_length) {
+ r->headers_out.content_length->hash = 0;
+ r->headers_out.content_length = NULL;
+ }
+
+ ngx_http_clear_accept_ranges(r);
+ ngx_http_clear_last_modified(r);
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || r->header_only) {
+ return rc;
+ }
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
+
+ p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head,
+ sizeof(ngx_http_msie_refresh_head) - 1);
+
+ if (escape == 0) {
+ p = ngx_cpymem(p, location, len);
+
+ } else {
+ p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH);
+ }
+
+ b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail,
+ sizeof(ngx_http_msie_refresh_tail) - 1);
+
+ b->last_buf = 1;
+ b->last_in_chain = 1;
+
+ out.buf = b;
+ out.next = NULL;
+
+ return ngx_http_output_filter(r, &out);
+}
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
new file mode 100644
index 0000000..ffbbc11
--- /dev/null
+++ b/src/http/ngx_http_upstream.c
@@ -0,0 +1,4555 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+#if (NGX_HTTP_CACHE)
+static ngx_int_t ngx_http_upstream_cache(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_cache_send(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_cache_status(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+#endif
+
+static void ngx_http_upstream_init_request(ngx_http_request_t *r);
+static void ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r);
+static void ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r);
+static void ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
+ ngx_event_t *ev);
+static void ngx_http_upstream_connect(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_reinit(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_send_request(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_process_header(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static ngx_int_t ngx_http_upstream_test_connect(ngx_connection_t *c);
+static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_send_response(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void
+ ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r);
+static void
+ ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void
+ ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r,
+ ngx_uint_t do_write);
+static ngx_int_t ngx_http_upstream_non_buffered_filter_init(void *data);
+static ngx_int_t ngx_http_upstream_non_buffered_filter(void *data,
+ ssize_t bytes);
+static void ngx_http_upstream_process_downstream(ngx_http_request_t *r);
+static void ngx_http_upstream_process_upstream(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_process_request(ngx_http_request_t *r);
+static void ngx_http_upstream_store(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_dummy_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+static void ngx_http_upstream_next(ngx_http_request_t *r,
+ ngx_http_upstream_t *u, ngx_uint_t ft_type);
+static void ngx_http_upstream_cleanup(void *data);
+static void ngx_http_upstream_finalize_request(ngx_http_request_t *r,
+ ngx_http_upstream_t *u, ngx_int_t rc);
+
+static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t
+ ngx_http_upstream_process_cache_control(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_ignore_header_line(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_expires(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_accel_expires(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_limit_rate(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_buffering(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_charset(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_header_line(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t
+ ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_content_type(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_content_length(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_last_modified(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_rewrite_location(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+
+#if (NGX_HTTP_GZIP)
+static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset);
+#endif
+
+static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf);
+static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_status_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_response_time_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_upstream_response_length_variable(
+ ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);
+
+static char *ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy);
+static char *ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+static void *ngx_http_upstream_create_main_conf(ngx_conf_t *cf);
+static char *ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf);
+
+#if (NGX_HTTP_SSL)
+static void ngx_http_upstream_ssl_init_connection(ngx_http_request_t *,
+ ngx_http_upstream_t *u, ngx_connection_t *c);
+static void ngx_http_upstream_ssl_handshake(ngx_connection_t *c);
+#endif
+
+
+ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = {
+
+ { ngx_string("Status"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, status),
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+ { ngx_string("Content-Type"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, content_type),
+ ngx_http_upstream_copy_content_type, 0, 1 },
+
+ { ngx_string("Content-Length"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, content_length),
+ ngx_http_upstream_copy_content_length, 0, 0 },
+
+ { ngx_string("Date"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, date),
+ ngx_http_upstream_copy_header_line,
+ offsetof(ngx_http_headers_out_t, date), 0 },
+
+ { ngx_string("Last-Modified"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, last_modified),
+ ngx_http_upstream_copy_last_modified, 0, 0 },
+
+ { ngx_string("ETag"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, etag),
+ ngx_http_upstream_copy_header_line,
+ offsetof(ngx_http_headers_out_t, etag), 0 },
+
+ { ngx_string("Server"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, server),
+ ngx_http_upstream_copy_header_line,
+ offsetof(ngx_http_headers_out_t, server), 0 },
+
+ { ngx_string("WWW-Authenticate"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, www_authenticate),
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+ { ngx_string("Location"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, location),
+ ngx_http_upstream_rewrite_location, 0, 0 },
+
+ { ngx_string("Refresh"),
+ ngx_http_upstream_ignore_header_line, 0,
+ ngx_http_upstream_rewrite_refresh, 0, 0 },
+
+ { ngx_string("Set-Cookie"),
+ ngx_http_upstream_process_set_cookie, 0,
+ ngx_http_upstream_copy_header_line, 0, 1 },
+
+ { ngx_string("Content-Disposition"),
+ ngx_http_upstream_ignore_header_line, 0,
+ ngx_http_upstream_copy_header_line, 0, 1 },
+
+ { ngx_string("Cache-Control"),
+ ngx_http_upstream_process_cache_control, 0,
+ ngx_http_upstream_copy_multi_header_lines,
+ offsetof(ngx_http_headers_out_t, cache_control), 1 },
+
+ { ngx_string("Expires"),
+ ngx_http_upstream_process_expires, 0,
+ ngx_http_upstream_copy_header_line,
+ offsetof(ngx_http_headers_out_t, expires), 1 },
+
+ { ngx_string("Accept-Ranges"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, accept_ranges),
+ ngx_http_upstream_copy_allow_ranges,
+ offsetof(ngx_http_headers_out_t, accept_ranges), 1 },
+
+ { ngx_string("Connection"),
+ ngx_http_upstream_ignore_header_line, 0,
+ ngx_http_upstream_ignore_header_line, 0, 0 },
+
+ { ngx_string("Keep-Alive"),
+ ngx_http_upstream_ignore_header_line, 0,
+ ngx_http_upstream_ignore_header_line, 0, 0 },
+
+ { ngx_string("X-Powered-By"),
+ ngx_http_upstream_ignore_header_line, 0,
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+ { ngx_string("X-Accel-Expires"),
+ ngx_http_upstream_process_accel_expires, 0,
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+ { ngx_string("X-Accel-Redirect"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, x_accel_redirect),
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+ { ngx_string("X-Accel-Limit-Rate"),
+ ngx_http_upstream_process_limit_rate, 0,
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+ { ngx_string("X-Accel-Buffering"),
+ ngx_http_upstream_process_buffering, 0,
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+ { ngx_string("X-Accel-Charset"),
+ ngx_http_upstream_process_charset, 0,
+ ngx_http_upstream_copy_header_line, 0, 0 },
+
+#if (NGX_HTTP_GZIP)
+ { ngx_string("Content-Encoding"),
+ ngx_http_upstream_process_header_line,
+ offsetof(ngx_http_upstream_headers_in_t, content_encoding),
+ ngx_http_upstream_copy_content_encoding, 0, 0 },
+#endif
+
+ { ngx_null_string, NULL, 0, NULL, 0, 0 }
+};
+
+
+static ngx_command_t ngx_http_upstream_commands[] = {
+
+ { ngx_string("upstream"),
+ NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,
+ ngx_http_upstream,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("server"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
+ ngx_http_upstream_server,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_http_module_t ngx_http_upstream_module_ctx = {
+ ngx_http_upstream_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ ngx_http_upstream_create_main_conf, /* create main configuration */
+ ngx_http_upstream_init_main_conf, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_upstream_module = {
+ NGX_MODULE_V1,
+ &ngx_http_upstream_module_ctx, /* module context */
+ ngx_http_upstream_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_http_variable_t ngx_http_upstream_vars[] = {
+
+ { ngx_string("upstream_addr"), NULL,
+ ngx_http_upstream_addr_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("upstream_status"), NULL,
+ ngx_http_upstream_status_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("upstream_response_time"), NULL,
+ ngx_http_upstream_response_time_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("upstream_response_length"), NULL,
+ ngx_http_upstream_response_length_variable, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+#if (NGX_HTTP_CACHE)
+
+ { ngx_string("upstream_cache_status"), NULL,
+ ngx_http_upstream_cache_status, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+#endif
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+static ngx_http_upstream_next_t ngx_http_upstream_next_errors[] = {
+ { 500, NGX_HTTP_UPSTREAM_FT_HTTP_500 },
+ { 502, NGX_HTTP_UPSTREAM_FT_HTTP_502 },
+ { 503, NGX_HTTP_UPSTREAM_FT_HTTP_503 },
+ { 504, NGX_HTTP_UPSTREAM_FT_HTTP_504 },
+ { 404, NGX_HTTP_UPSTREAM_FT_HTTP_404 },
+ { 0, 0 }
+};
+
+
+ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[] = {
+ { ngx_string("GET"), NGX_HTTP_GET},
+ { ngx_string("HEAD"), NGX_HTTP_HEAD },
+ { ngx_string("POST"), NGX_HTTP_POST },
+ { ngx_null_string, 0 }
+};
+
+
+ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[] = {
+ { ngx_string("X-Accel-Redirect"), NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT },
+ { ngx_string("X-Accel-Expires"), NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES },
+ { ngx_string("X-Accel-Limit-Rate"), NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE },
+ { ngx_string("X-Accel-Buffering"), NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING },
+ { ngx_string("X-Accel-Charset"), NGX_HTTP_UPSTREAM_IGN_XA_CHARSET },
+ { ngx_string("Expires"), NGX_HTTP_UPSTREAM_IGN_EXPIRES },
+ { ngx_string("Cache-Control"), NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL },
+ { ngx_string("Set-Cookie"), NGX_HTTP_UPSTREAM_IGN_SET_COOKIE },
+ { ngx_null_string, 0 }
+};
+
+
+ngx_int_t
+ngx_http_upstream_create(ngx_http_request_t *r)
+{
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+
+ if (u && u->cleanup) {
+ r->main->count++;
+ ngx_http_upstream_cleanup(r);
+ }
+
+ u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
+ if (u == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->upstream = u;
+
+ u->peer.log = r->connection->log;
+ u->peer.log_error = NGX_ERROR_ERR;
+#if (NGX_THREADS)
+ u->peer.lock = &r->connection->lock;
+#endif
+
+#if (NGX_HTTP_CACHE)
+ r->cache = NULL;
+#endif
+
+ return NGX_OK;
+}
+
+
+void
+ngx_http_upstream_init(ngx_http_request_t *r)
+{
+ ngx_connection_t *c;
+
+ c = r->connection;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http init upstream, client timer: %d", c->read->timer_set);
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+
+ if (!c->write->active) {
+ if (ngx_add_event(c->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT)
+ == NGX_ERROR)
+ {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+ }
+
+ ngx_http_upstream_init_request(r);
+}
+
+
+static void
+ngx_http_upstream_init_request(ngx_http_request_t *r)
+{
+ ngx_str_t *host;
+ ngx_uint_t i;
+ ngx_resolver_ctx_t *ctx, temp;
+ ngx_http_cleanup_t *cln;
+ ngx_http_upstream_t *u;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_upstream_srv_conf_t *uscf, **uscfp;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ if (r->aio) {
+ return;
+ }
+
+ u = r->upstream;
+
+#if (NGX_HTTP_CACHE)
+
+ if (u->conf->cache) {
+ ngx_int_t rc;
+
+ rc = ngx_http_upstream_cache(r, u);
+
+ if (rc == NGX_BUSY) {
+ r->write_event_handler = ngx_http_upstream_init_request;
+ return;
+ }
+
+ r->write_event_handler = ngx_http_request_empty_handler;
+
+ if (rc == NGX_DONE) {
+ return;
+ }
+
+ if (rc != NGX_DECLINED) {
+ ngx_http_finalize_request(r, rc);
+ return;
+ }
+ }
+
+#endif
+
+ u->store = (u->conf->store || u->conf->store_lengths);
+
+ if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {
+ r->read_event_handler = ngx_http_upstream_rd_check_broken_connection;
+ r->write_event_handler = ngx_http_upstream_wr_check_broken_connection;
+ }
+
+ if (r->request_body) {
+ u->request_bufs = r->request_body->bufs;
+ }
+
+ if (u->create_request(r) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ u->peer.local = u->conf->local;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ u->output.alignment = clcf->directio_alignment;
+ u->output.pool = r->pool;
+ u->output.bufs.num = 1;
+ u->output.bufs.size = clcf->client_body_buffer_size;
+ u->output.output_filter = ngx_chain_writer;
+ u->output.filter_ctx = &u->writer;
+
+ u->writer.pool = r->pool;
+
+ if (r->upstream_states == NULL) {
+
+ r->upstream_states = ngx_array_create(r->pool, 1,
+ sizeof(ngx_http_upstream_state_t));
+ if (r->upstream_states == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ } else {
+
+ u->state = ngx_array_push(r->upstream_states);
+ if (u->state == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));
+ }
+
+ cln = ngx_http_cleanup_add(r, 0);
+ if (cln == NULL) {
+ ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ cln->handler = ngx_http_upstream_cleanup;
+ cln->data = r;
+ u->cleanup = &cln->handler;
+
+ if (u->resolved == NULL) {
+
+ uscf = u->conf->upstream;
+
+ } else {
+
+ if (u->resolved->sockaddr) {
+
+ if (ngx_http_upstream_create_round_robin_peer(r, u->resolved)
+ != NGX_OK)
+ {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_http_upstream_connect(r, u);
+
+ return;
+ }
+
+ host = &u->resolved->host;
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ uscfp = umcf->upstreams.elts;
+
+ for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+ uscf = uscfp[i];
+
+ if (uscf->host.len == host->len
+ && ((uscf->port == 0 && u->resolved->no_port)
+ || uscf->port == u->resolved->port)
+ && ngx_memcmp(uscf->host.data, host->data, host->len) == 0)
+ {
+ goto found;
+ }
+ }
+
+ if (u->resolved->port == 0) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "no port in upstream \"%V\"", host);
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ temp.name = *host;
+
+ ctx = ngx_resolve_start(clcf->resolver, &temp);
+ if (ctx == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ if (ctx == NGX_NO_RESOLVER) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "no resolver defined to resolve %V", host);
+
+ ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
+ return;
+ }
+
+ ctx->name = *host;
+ ctx->type = NGX_RESOLVE_A;
+ ctx->handler = ngx_http_upstream_resolve_handler;
+ ctx->data = r;
+ ctx->timeout = clcf->resolver_timeout;
+
+ u->resolved->ctx = ctx;
+
+ if (ngx_resolve_name(ctx) != NGX_OK) {
+ u->resolved->ctx = NULL;
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ return;
+ }
+
+found:
+
+ if (uscf->peer.init(r, uscf) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_http_upstream_connect(r, u);
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+static ngx_int_t
+ngx_http_upstream_cache(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_int_t rc;
+ ngx_http_cache_t *c;
+
+ c = r->cache;
+
+ if (c == NULL) {
+
+ if (!(r->method & u->conf->cache_methods)) {
+ return NGX_DECLINED;
+ }
+
+ if (r->method & NGX_HTTP_HEAD) {
+ u->method = ngx_http_core_get_method;
+ }
+
+ if (ngx_http_file_cache_new(r) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ if (u->create_key(r) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ /* TODO: add keys */
+
+ ngx_http_file_cache_create_key(r);
+
+ if (r->cache->header_start + 256 >= u->conf->buffer_size) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "%V_buffer_size %uz is not enough for cache key, "
+ "it should increased at least to %uz",
+ &u->conf->module, u->conf->buffer_size,
+ ngx_align(r->cache->header_start + 256, 1024));
+
+ r->cache = NULL;
+ return NGX_DECLINED;
+ }
+
+ u->cacheable = 1;
+
+ switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) {
+
+ case NGX_ERROR:
+ return NGX_ERROR;
+
+ case NGX_DECLINED:
+ u->cache_status = NGX_HTTP_CACHE_BYPASS;
+ return NGX_DECLINED;
+
+ default: /* NGX_OK */
+ break;
+ }
+
+ c = r->cache;
+
+ c->min_uses = u->conf->cache_min_uses;
+ c->body_start = u->conf->buffer_size;
+ c->file_cache = u->conf->cache->data;
+
+ u->cache_status = NGX_HTTP_CACHE_MISS;
+ }
+
+ rc = ngx_http_file_cache_open(r);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream cache: %i", rc);
+
+ switch (rc) {
+
+ case NGX_HTTP_CACHE_UPDATING:
+
+ if (u->conf->cache_use_stale & NGX_HTTP_UPSTREAM_FT_UPDATING) {
+ u->cache_status = rc;
+ rc = NGX_OK;
+
+ } else {
+ rc = NGX_HTTP_CACHE_STALE;
+ }
+
+ break;
+
+ case NGX_OK:
+ u->cache_status = NGX_HTTP_CACHE_HIT;
+ }
+
+ switch (rc) {
+
+ case NGX_OK:
+
+ rc = ngx_http_upstream_cache_send(r, u);
+
+ if (rc != NGX_HTTP_UPSTREAM_INVALID_HEADER) {
+ return rc;
+ }
+
+ break;
+
+ case NGX_HTTP_CACHE_STALE:
+
+ c->valid_sec = 0;
+ u->buffer.start = NULL;
+ u->cache_status = NGX_HTTP_CACHE_EXPIRED;
+
+ break;
+
+ case NGX_DECLINED:
+
+ if ((size_t) (u->buffer.end - u->buffer.start) < u->conf->buffer_size) {
+ u->buffer.start = NULL;
+
+ } else {
+ u->buffer.pos = u->buffer.start + c->header_start;
+ u->buffer.last = u->buffer.pos;
+ }
+
+ break;
+
+ case NGX_HTTP_CACHE_SCARCE:
+
+ u->cacheable = 0;
+
+ break;
+
+ case NGX_AGAIN:
+
+ return NGX_BUSY;
+
+ case NGX_ERROR:
+
+ return NGX_ERROR;
+
+ default:
+
+ /* cached NGX_HTTP_BAD_GATEWAY, NGX_HTTP_GATEWAY_TIME_OUT, etc. */
+
+ u->cache_status = NGX_HTTP_CACHE_HIT;
+
+ return rc;
+ }
+
+ r->cached = 0;
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_int_t rc;
+ ngx_http_cache_t *c;
+
+ r->cached = 1;
+ c = r->cache;
+
+ if (c->header_start == c->body_start) {
+ r->http_version = NGX_HTTP_VERSION_9;
+ return ngx_http_cache_send(r);
+ }
+
+ /* TODO: cache stack */
+
+ u->buffer = *c->buf;
+ u->buffer.pos += c->header_start;
+
+ ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t));
+
+ if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ rc = u->process_header(r);
+
+ if (rc == NGX_OK) {
+
+ if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
+ return NGX_DONE;
+ }
+
+ return ngx_http_cache_send(r);
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ /* rc == NGX_HTTP_UPSTREAM_INVALID_HEADER */
+
+ /* TODO: delete file */
+
+ return rc;
+}
+
+#endif
+
+
+static void
+ngx_http_upstream_resolve_handler(ngx_resolver_ctx_t *ctx)
+{
+ ngx_http_request_t *r;
+ ngx_http_upstream_t *u;
+ ngx_http_upstream_resolved_t *ur;
+
+ r = ctx->data;
+
+ u = r->upstream;
+ ur = u->resolved;
+
+ if (ctx->state) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "%V could not be resolved (%i: %s)",
+ &ctx->name, ctx->state,
+ ngx_resolver_strerror(ctx->state));
+
+ ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
+ return;
+ }
+
+ ur->naddrs = ctx->naddrs;
+ ur->addrs = ctx->addrs;
+
+#if (NGX_DEBUG)
+ {
+ in_addr_t addr;
+ ngx_uint_t i;
+
+ for (i = 0; i < ctx->naddrs; i++) {
+ addr = ntohl(ur->addrs[i]);
+
+ ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "name was resolved to %ud.%ud.%ud.%ud",
+ (addr >> 24) & 0xff, (addr >> 16) & 0xff,
+ (addr >> 8) & 0xff, addr & 0xff);
+ }
+ }
+#endif
+
+ if (ngx_http_upstream_create_round_robin_peer(r, ur) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_resolve_name_done(ctx);
+ ur->ctx = NULL;
+
+ ngx_http_upstream_connect(r, u);
+}
+
+
+static void
+ngx_http_upstream_handler(ngx_event_t *ev)
+{
+ ngx_connection_t *c;
+ ngx_http_request_t *r;
+ ngx_http_log_ctx_t *ctx;
+ ngx_http_upstream_t *u;
+
+ c = ev->data;
+ r = c->data;
+
+ u = r->upstream;
+ c = r->connection;
+
+ ctx = c->log->data;
+ ctx->current_request = r;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream request: \"%V?%V\"", &r->uri, &r->args);
+
+ if (ev->write) {
+ u->write_event_handler(r, u);
+
+ } else {
+ u->read_event_handler(r, u);
+ }
+
+ ngx_http_run_posted_requests(c);
+}
+
+
+static void
+ngx_http_upstream_rd_check_broken_connection(ngx_http_request_t *r)
+{
+ ngx_http_upstream_check_broken_connection(r, r->connection->read);
+}
+
+
+static void
+ngx_http_upstream_wr_check_broken_connection(ngx_http_request_t *r)
+{
+ ngx_http_upstream_check_broken_connection(r, r->connection->write);
+}
+
+
+static void
+ngx_http_upstream_check_broken_connection(ngx_http_request_t *r,
+ ngx_event_t *ev)
+{
+ int n;
+ char buf[1];
+ ngx_err_t err;
+ ngx_int_t event;
+ ngx_connection_t *c;
+ ngx_http_upstream_t *u;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+ "http upstream check client, write event:%d, \"%V\"",
+ ev->write, &r->uri);
+
+ c = r->connection;
+ u = r->upstream;
+
+ if (c->error) {
+ if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) {
+
+ event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT;
+
+ if (ngx_del_event(ev, event, 0) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+ if (!u->cacheable) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ }
+
+ return;
+ }
+
+#if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+
+ if (!ev->pending_eof) {
+ return;
+ }
+
+ ev->eof = 1;
+ c->error = 1;
+
+ if (ev->kq_errno) {
+ ev->error = 1;
+ }
+
+ if (!u->cacheable && u->peer.connection) {
+ ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno,
+ "kevent() reported that client prematurely closed "
+ "connection, so upstream connection is closed too");
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno,
+ "kevent() reported that client prematurely closed "
+ "connection");
+
+ if (u->peer.connection == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ }
+
+ return;
+ }
+
+#endif
+
+ n = recv(c->fd, buf, 1, MSG_PEEK);
+
+ err = ngx_socket_errno;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, err,
+ "http upstream recv(): %d", n);
+
+ if (ev->write && (n >= 0 || err == NGX_EAGAIN)) {
+ return;
+ }
+
+ if ((ngx_event_flags & NGX_USE_LEVEL_EVENT) && ev->active) {
+
+ event = ev->write ? NGX_WRITE_EVENT : NGX_READ_EVENT;
+
+ if (ngx_del_event(ev, event, 0) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+ if (n > 0) {
+ return;
+ }
+
+ if (n == -1) {
+ if (err == NGX_EAGAIN) {
+ return;
+ }
+
+ ev->error = 1;
+
+ } else { /* n == 0 */
+ err = 0;
+ }
+
+ ev->eof = 1;
+ c->error = 1;
+
+ if (!u->cacheable && u->peer.connection) {
+ ngx_log_error(NGX_LOG_INFO, ev->log, err,
+ "client prematurely closed connection, "
+ "so upstream connection is closed too");
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ ngx_log_error(NGX_LOG_INFO, ev->log, err,
+ "client prematurely closed connection");
+
+ if (u->peer.connection == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ }
+}
+
+
+static void
+ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_int_t rc;
+ ngx_time_t *tp;
+ ngx_connection_t *c;
+
+ r->connection->log->action = "connecting to upstream";
+
+ r->connection->single_connection = 0;
+
+ if (u->state && u->state->response_sec) {
+ tp = ngx_timeofday();
+ u->state->response_sec = tp->sec - u->state->response_sec;
+ u->state->response_msec = tp->msec - u->state->response_msec;
+ }
+
+ u->state = ngx_array_push(r->upstream_states);
+ if (u->state == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ ngx_memzero(u->state, sizeof(ngx_http_upstream_state_t));
+
+ tp = ngx_timeofday();
+ u->state->response_sec = tp->sec;
+ u->state->response_msec = tp->msec;
+
+ rc = ngx_event_connect_peer(&u->peer);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream connect: %i", rc);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ u->state->peer = u->peer.name;
+
+ if (rc == NGX_BUSY) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no live upstreams");
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_NOLIVE);
+ return;
+ }
+
+ if (rc == NGX_DECLINED) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+ return;
+ }
+
+ /* rc == NGX_OK || rc == NGX_AGAIN */
+
+ c = u->peer.connection;
+
+ c->data = r;
+
+ c->write->handler = ngx_http_upstream_handler;
+ c->read->handler = ngx_http_upstream_handler;
+
+ u->write_event_handler = ngx_http_upstream_send_request_handler;
+ u->read_event_handler = ngx_http_upstream_process_header;
+
+ c->sendfile &= r->connection->sendfile;
+ u->output.sendfile = c->sendfile;
+
+ c->pool = r->pool;
+ c->log = r->connection->log;
+ c->read->log = c->log;
+ c->write->log = c->log;
+
+ /* init or reinit the ngx_output_chain() and ngx_chain_writer() contexts */
+
+ u->writer.out = NULL;
+ u->writer.last = &u->writer.out;
+ u->writer.connection = c;
+ u->writer.limit = 0;
+
+ if (u->request_sent) {
+ if (ngx_http_upstream_reinit(r, u) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+ if (r->request_body
+ && r->request_body->buf
+ && r->request_body->temp_file
+ && r == r->main)
+ {
+ /*
+ * the r->request_body->buf can be reused for one request only,
+ * the subrequests should allocate their own temporay bufs
+ */
+
+ u->output.free = ngx_alloc_chain_link(r->pool);
+ if (u->output.free == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ u->output.free->buf = r->request_body->buf;
+ u->output.free->next = NULL;
+ u->output.allocated = 1;
+
+ r->request_body->buf->pos = r->request_body->buf->start;
+ r->request_body->buf->last = r->request_body->buf->start;
+ r->request_body->buf->tag = u->output.tag;
+ }
+
+ u->request_sent = 0;
+
+ if (rc == NGX_AGAIN) {
+ ngx_add_timer(c->write, u->conf->connect_timeout);
+ return;
+ }
+
+#if (NGX_HTTP_SSL)
+
+ if (u->ssl && c->ssl == NULL) {
+ ngx_http_upstream_ssl_init_connection(r, u, c);
+ return;
+ }
+
+#endif
+
+ ngx_http_upstream_send_request(r, u);
+}
+
+
+#if (NGX_HTTP_SSL)
+
+static void
+ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r,
+ ngx_http_upstream_t *u, ngx_connection_t *c)
+{
+ ngx_int_t rc;
+
+ if (ngx_ssl_create_connection(u->conf->ssl, c,
+ NGX_SSL_BUFFER|NGX_SSL_CLIENT)
+ != NGX_OK)
+ {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ c->sendfile = 0;
+ u->output.sendfile = 0;
+
+ if (u->conf->ssl_session_reuse) {
+ if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+ }
+
+ r->connection->log->action = "SSL handshaking to upstream";
+
+ rc = ngx_ssl_handshake(c);
+
+ if (rc == NGX_AGAIN) {
+ c->ssl->handler = ngx_http_upstream_ssl_handshake;
+ return;
+ }
+
+ ngx_http_upstream_ssl_handshake(c);
+}
+
+
+static void
+ngx_http_upstream_ssl_handshake(ngx_connection_t *c)
+{
+ ngx_http_request_t *r;
+ ngx_http_upstream_t *u;
+
+ r = c->data;
+ u = r->upstream;
+
+ if (c->ssl->handshaked) {
+
+ if (u->conf->ssl_session_reuse) {
+ u->peer.save_session(&u->peer, u->peer.data);
+ }
+
+ c->write->handler = ngx_http_upstream_handler;
+ c->read->handler = ngx_http_upstream_handler;
+
+ ngx_http_upstream_send_request(r, u);
+
+ return;
+ }
+
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_upstream_reinit(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_chain_t *cl;
+
+ if (u->reinit_request(r) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t));
+
+ if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ /* reinit the request chain */
+
+ for (cl = u->request_bufs; cl; cl = cl->next) {
+ cl->buf->pos = cl->buf->start;
+ cl->buf->file_pos = 0;
+ }
+
+ /* reinit the subrequest's ngx_output_chain() context */
+
+ if (r->request_body && r->request_body->temp_file
+ && r != r->main && u->output.buf)
+ {
+ u->output.free = ngx_alloc_chain_link(r->pool);
+ if (u->output.free == NULL) {
+ return NGX_ERROR;
+ }
+
+ u->output.free->buf = u->output.buf;
+ u->output.free->next = NULL;
+
+ u->output.buf->pos = u->output.buf->start;
+ u->output.buf->last = u->output.buf->start;
+ }
+
+ u->output.buf = NULL;
+ u->output.in = NULL;
+ u->output.busy = NULL;
+
+ /* reinit u->buffer */
+
+ u->buffer.pos = u->buffer.start;
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->cache) {
+ u->buffer.pos += r->cache->header_start;
+ }
+
+#endif
+
+ u->buffer.last = u->buffer.pos;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream send request");
+
+ if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+ return;
+ }
+
+ c->log->action = "sending request to upstream";
+
+ rc = ngx_output_chain(&u->output, u->request_sent ? NULL : u->request_bufs);
+
+ u->request_sent = 1;
+
+ if (rc == NGX_ERROR) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+ return;
+ }
+
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+
+ if (rc == NGX_AGAIN) {
+ ngx_add_timer(c->write, u->conf->send_timeout);
+
+ if (ngx_handle_write_event(c->write, u->conf->send_lowat) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ return;
+ }
+
+ /* rc == NGX_OK */
+
+ if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) {
+ if (ngx_tcp_push(c->fd) == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno,
+ ngx_tcp_push_n " failed");
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ c->tcp_nopush = NGX_TCP_NOPUSH_UNSET;
+ }
+
+ ngx_add_timer(c->read, u->conf->read_timeout);
+
+#if 1
+ if (c->read->ready) {
+
+ /* post aio operation */
+
+ /*
+ * TODO comment
+ * although we can post aio operation just in the end
+ * of ngx_http_upstream_connect() CHECK IT !!!
+ * it's better to do here because we postpone header buffer allocation
+ */
+
+ ngx_http_upstream_process_header(r, u);
+ return;
+ }
+#endif
+
+ u->write_event_handler = ngx_http_upstream_dummy_handler;
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+}
+
+
+static void
+ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream send request handler");
+
+ if (c->write->timedout) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
+ return;
+ }
+
+#if (NGX_HTTP_SSL)
+
+ if (u->ssl && c->ssl == NULL) {
+ ngx_http_upstream_ssl_init_connection(r, u, c);
+ return;
+ }
+
+#endif
+
+ if (u->header_sent) {
+ u->write_event_handler = ngx_http_upstream_dummy_handler;
+
+ (void) ngx_handle_write_event(c->write, 0);
+
+ return;
+ }
+
+ ngx_http_upstream_send_request(r, u);
+}
+
+
+static void
+ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ssize_t n;
+ ngx_int_t rc;
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream process header");
+
+ c->log->action = "reading response header from upstream";
+
+ if (c->read->timedout) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
+ return;
+ }
+
+ if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+ return;
+ }
+
+ if (u->buffer.start == NULL) {
+ u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size);
+ if (u->buffer.start == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ u->buffer.pos = u->buffer.start;
+ u->buffer.last = u->buffer.start;
+ u->buffer.end = u->buffer.start + u->conf->buffer_size;
+ u->buffer.temporary = 1;
+
+ u->buffer.tag = u->output.tag;
+
+ if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
+ sizeof(ngx_table_elt_t))
+ != NGX_OK)
+ {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->cache) {
+ u->buffer.pos += r->cache->header_start;
+ u->buffer.last = u->buffer.pos;
+ }
+#endif
+ }
+
+ for ( ;; ) {
+
+ n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);
+
+ if (n == NGX_AGAIN) {
+#if 0
+ ngx_add_timer(rev, u->read_timeout);
+#endif
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ return;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "upstream prematurely closed connection");
+ }
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+ return;
+ }
+
+ u->buffer.last += n;
+
+#if 0
+ u->valid_header_in = 0;
+
+ u->peer.cached = 0;
+#endif
+
+ rc = u->process_header(r);
+
+ if (rc == NGX_AGAIN) {
+
+ if (u->buffer.last == u->buffer.end) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "upstream sent too big header");
+
+ ngx_http_upstream_next(r, u,
+ NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
+ return;
+ }
+
+ continue;
+ }
+
+ break;
+ }
+
+ if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
+ ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ /* rc == NGX_OK */
+
+ if (u->headers_in.status_n > NGX_HTTP_SPECIAL_RESPONSE) {
+
+ if (r->subrequest_in_memory) {
+ u->buffer.last = u->buffer.pos;
+ }
+
+ if (ngx_http_upstream_test_next(r, u) == NGX_OK) {
+ return;
+ }
+
+ if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) {
+ return;
+ }
+ }
+
+ if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
+ return;
+ }
+
+ if (!r->subrequest_in_memory) {
+ ngx_http_upstream_send_response(r, u);
+ return;
+ }
+
+ /* subrequest content in memory */
+
+ if (u->input_filter == NULL) {
+ u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;
+ u->input_filter = ngx_http_upstream_non_buffered_filter;
+ u->input_filter_ctx = r;
+ }
+
+ if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return;
+ }
+
+ n = u->buffer.last - u->buffer.pos;
+
+ if (n) {
+ u->buffer.last -= n;
+
+ u->state->response_length += n;
+
+ if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+ return;
+ }
+
+ if (u->length == 0) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+ }
+
+ u->read_event_handler = ngx_http_upstream_process_body_in_memory;
+
+ ngx_http_upstream_process_body_in_memory(r, u);
+}
+
+
+static ngx_int_t
+ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_uint_t status;
+ ngx_http_upstream_next_t *un;
+
+ status = u->headers_in.status_n;
+
+ for (un = ngx_http_upstream_next_errors; un->status; un++) {
+
+ if (status != un->status) {
+ continue;
+ }
+
+ if (u->peer.tries > 1 && (u->conf->next_upstream & un->mask)) {
+ ngx_http_upstream_next(r, u, un->mask);
+ return NGX_OK;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (u->cache_status == NGX_HTTP_CACHE_EXPIRED
+ && (u->conf->cache_use_stale & un->mask))
+ {
+ ngx_int_t rc;
+
+ rc = u->reinit_request(r);
+
+ if (rc == NGX_OK) {
+ u->cache_status = NGX_HTTP_CACHE_STALE;
+ rc = ngx_http_upstream_cache_send(r, u);
+ }
+
+ ngx_http_upstream_finalize_request(r, u, rc);
+ return NGX_OK;
+ }
+
+#endif
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_intercept_errors(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_int_t status;
+ ngx_uint_t i;
+ ngx_table_elt_t *h;
+ ngx_http_err_page_t *err_page;
+ ngx_http_core_loc_conf_t *clcf;
+
+ status = u->headers_in.status_n;
+
+ if (status == NGX_HTTP_NOT_FOUND && u->conf->intercept_404) {
+ ngx_http_upstream_finalize_request(r, u, NGX_HTTP_NOT_FOUND);
+ return NGX_OK;
+ }
+
+ if (!u->conf->intercept_errors) {
+ return NGX_DECLINED;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->error_pages == NULL) {
+ return NGX_DECLINED;
+ }
+
+ err_page = clcf->error_pages->elts;
+ for (i = 0; i < clcf->error_pages->nelts; i++) {
+
+ if (err_page[i].status == status) {
+
+ if (status == NGX_HTTP_UNAUTHORIZED
+ && u->headers_in.www_authenticate)
+ {
+ h = ngx_list_push(&r->headers_out.headers);
+
+ if (h == NULL) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_OK;
+ }
+
+ *h = *u->headers_in.www_authenticate;
+
+ r->headers_out.www_authenticate = h;
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->cache) {
+ time_t valid;
+
+ valid = ngx_http_file_cache_valid(u->conf->cache_valid, status);
+
+ if (valid) {
+ r->cache->valid_sec = ngx_time() + valid;
+ r->cache->error = status;
+ }
+
+ ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+ }
+#endif
+ ngx_http_upstream_finalize_request(r, u, status);
+
+ return NGX_OK;
+ }
+ }
+
+ return NGX_DECLINED;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_test_connect(ngx_connection_t *c)
+{
+ int err;
+ socklen_t len;
+
+#if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ if (c->write->pending_eof) {
+ c->log->action = "connecting to upstream";
+ (void) ngx_connection_error(c, c->write->kq_errno,
+ "kevent() reported that connect() failed");
+ return NGX_ERROR;
+ }
+
+ } else
+#endif
+ {
+ err = 0;
+ len = sizeof(int);
+
+ /*
+ * BSDs and Linux return 0 and set a pending error in err
+ * Solaris returns -1 and sets errno
+ */
+
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len)
+ == -1)
+ {
+ err = ngx_errno;
+ }
+
+ if (err) {
+ c->log->action = "connecting to upstream";
+ (void) ngx_connection_error(c, err, "connect() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_str_t *uri, args;
+ ngx_uint_t i, flags;
+ ngx_list_part_t *part;
+ ngx_table_elt_t *h;
+ ngx_http_upstream_header_t *hh;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ if (u->headers_in.x_accel_redirect
+ && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT))
+ {
+ ngx_http_upstream_finalize_request(r, u, NGX_DECLINED);
+
+ part = &u->headers_in.headers.part;
+ h = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ h = part->elts;
+ i = 0;
+ }
+
+ hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash,
+ h[i].lowcase_key, h[i].key.len);
+
+ if (hh && hh->redirect) {
+ if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) {
+ ngx_http_finalize_request(r,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_DONE;
+ }
+ }
+ }
+
+ uri = &u->headers_in.x_accel_redirect->value;
+ ngx_str_null(&args);
+ flags = NGX_HTTP_LOG_UNSAFE;
+
+ if (ngx_http_parse_unsafe_uri(r, uri, &args, &flags) != NGX_OK) {
+ ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
+ return NGX_DONE;
+ }
+
+ if (r->method != NGX_HTTP_HEAD) {
+ r->method = NGX_HTTP_GET;
+ }
+
+ r->valid_unparsed_uri = 0;
+
+ ngx_http_internal_redirect(r, uri, &args);
+ ngx_http_finalize_request(r, NGX_DONE);
+ return NGX_DONE;
+ }
+
+ part = &u->headers_in.headers.part;
+ h = part->elts;
+
+ for (i = 0; /* void */; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ h = part->elts;
+ i = 0;
+ }
+
+ if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash,
+ h[i].lowcase_key, h[i].key.len))
+ {
+ continue;
+ }
+
+ hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash,
+ h[i].lowcase_key, h[i].key.len);
+
+ if (hh) {
+ if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_DONE;
+ }
+
+ continue;
+ }
+
+ if (ngx_http_upstream_copy_header_line(r, &h[i], 0) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_INTERNAL_SERVER_ERROR);
+ return NGX_DONE;
+ }
+ }
+
+ if (r->headers_out.server && r->headers_out.server->value.data == NULL) {
+ r->headers_out.server->hash = 0;
+ }
+
+ if (r->headers_out.date && r->headers_out.date->value.data == NULL) {
+ r->headers_out.date->hash = 0;
+ }
+
+ r->headers_out.status = u->headers_in.status_n;
+ r->headers_out.status_line = u->headers_in.status_line;
+
+ u->headers_in.content_length_n = r->headers_out.content_length_n;
+
+ if (r->headers_out.content_length_n != -1) {
+ u->length = (size_t) r->headers_out.content_length_n;
+
+ } else {
+ u->length = NGX_MAX_SIZE_T_VALUE;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_process_body_in_memory(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_event_t *rev;
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+ rev = c->read;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream process body on memory");
+
+ if (rev->timedout) {
+ ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
+ ngx_http_upstream_finalize_request(r, u, NGX_ETIMEDOUT);
+ return;
+ }
+
+ b = &u->buffer;
+
+ for ( ;; ) {
+
+ size = b->end - b->last;
+
+ if (size == 0) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "upstream buffer is too small to read response");
+ ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+ return;
+ }
+
+ n = c->recv(c, b->last, size);
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (n == 0 || n == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, n);
+ return;
+ }
+
+ u->state->response_length += n;
+
+ if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+ return;
+ }
+
+ if (!rev->ready) {
+ break;
+ }
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+ return;
+ }
+
+ if (rev->active) {
+ ngx_add_timer(rev, u->conf->read_timeout);
+
+ } else if (rev->timer_set) {
+ ngx_del_timer(rev);
+ }
+}
+
+
+static void
+ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ int tcp_nodelay;
+ ssize_t n;
+ ngx_int_t rc;
+ ngx_event_pipe_t *p;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ rc = ngx_http_send_header(r);
+
+ if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) {
+ ngx_http_upstream_finalize_request(r, u, rc);
+ return;
+ }
+
+ c = r->connection;
+
+ if (r->header_only) {
+
+ if (u->cacheable || u->store) {
+
+ if (ngx_shutdown_socket(c->fd, NGX_WRITE_SHUTDOWN) == -1) {
+ ngx_connection_error(c, ngx_socket_errno,
+ ngx_shutdown_socket_n " failed");
+ }
+
+ r->read_event_handler = ngx_http_request_empty_handler;
+ r->write_event_handler = ngx_http_request_empty_handler;
+ c->error = 1;
+
+ } else {
+ ngx_http_upstream_finalize_request(r, u, rc);
+ return;
+ }
+ }
+
+ u->header_sent = 1;
+
+ if (r->request_body && r->request_body->temp_file) {
+ ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd);
+ r->request_body->temp_file->file.fd = NGX_INVALID_FILE;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (!u->buffering) {
+
+ if (u->input_filter == NULL) {
+ u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;
+ u->input_filter = ngx_http_upstream_non_buffered_filter;
+ u->input_filter_ctx = r;
+ }
+
+ u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream;
+ r->write_event_handler =
+ ngx_http_upstream_process_non_buffered_downstream;
+
+ r->limit_rate = 0;
+
+ if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ if (clcf->tcp_nodelay && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay");
+
+ tcp_nodelay = 1;
+
+ if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
+ (const void *) &tcp_nodelay, sizeof(int)) == -1)
+ {
+ ngx_connection_error(c, ngx_socket_errno,
+ "setsockopt(TCP_NODELAY) failed");
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ c->tcp_nodelay = NGX_TCP_NODELAY_SET;
+ }
+
+ n = u->buffer.last - u->buffer.pos;
+
+ if (n) {
+ u->buffer.last = u->buffer.pos;
+
+ u->state->response_length += n;
+
+ if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ ngx_http_upstream_process_non_buffered_downstream(r);
+
+ } else {
+ u->buffer.pos = u->buffer.start;
+ u->buffer.last = u->buffer.start;
+
+ if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ if (u->peer.connection->read->ready) {
+ ngx_http_upstream_process_non_buffered_upstream(r, u);
+ }
+ }
+
+ return;
+ }
+
+ /* TODO: preallocate event_pipe bufs, look "Content-Length" */
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->cache && r->cache->file.fd != NGX_INVALID_FILE) {
+ ngx_pool_run_cleanup_file(r->pool, r->cache->file.fd);
+ r->cache->file.fd = NGX_INVALID_FILE;
+ }
+
+ switch (ngx_http_test_predicates(r, u->conf->no_cache)) {
+
+ case NGX_ERROR:
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+
+ case NGX_DECLINED:
+ u->cacheable = 0;
+ break;
+
+ default: /* NGX_OK */
+
+ if (u->cache_status == NGX_HTTP_CACHE_BYPASS) {
+
+ r->cache->min_uses = u->conf->cache_min_uses;
+ r->cache->body_start = u->conf->buffer_size;
+ r->cache->file_cache = u->conf->cache->data;
+
+ if (ngx_http_file_cache_create(r) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+ }
+
+ break;
+ }
+
+ if (u->cacheable) {
+ time_t now, valid;
+
+ now = ngx_time();
+
+ valid = r->cache->valid_sec;
+
+ if (valid == 0) {
+ valid = ngx_http_file_cache_valid(u->conf->cache_valid,
+ u->headers_in.status_n);
+ if (valid) {
+ r->cache->valid_sec = now + valid;
+ }
+ }
+
+ if (valid) {
+ r->cache->last_modified = r->headers_out.last_modified_time;
+ r->cache->date = now;
+ r->cache->body_start = (u_short) (u->buffer.pos - u->buffer.start);
+
+ ngx_http_file_cache_set_header(r, u->buffer.start);
+
+ } else {
+ u->cacheable = 0;
+ r->headers_out.last_modified_time = -1;
+ }
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http cacheable: %d", u->cacheable);
+
+ if (u->cacheable == 0 && r->cache) {
+ ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+ }
+
+#endif
+
+ p = u->pipe;
+
+ p->output_filter = (ngx_event_pipe_output_filter_pt) ngx_http_output_filter;
+ p->output_ctx = r;
+ p->tag = u->output.tag;
+ p->bufs = u->conf->bufs;
+ p->busy_size = u->conf->busy_buffers_size;
+ p->upstream = u->peer.connection;
+ p->downstream = c;
+ p->pool = r->pool;
+ p->log = c->log;
+
+ p->cacheable = u->cacheable || u->store;
+
+ p->temp_file = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+ if (p->temp_file == NULL) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ p->temp_file->file.fd = NGX_INVALID_FILE;
+ p->temp_file->file.log = c->log;
+ p->temp_file->path = u->conf->temp_path;
+ p->temp_file->pool = r->pool;
+
+ if (p->cacheable) {
+ p->temp_file->persistent = 1;
+
+ } else {
+ p->temp_file->log_level = NGX_LOG_WARN;
+ p->temp_file->warn = "an upstream response is buffered "
+ "to a temporary file";
+ }
+
+ p->max_temp_file_size = u->conf->max_temp_file_size;
+ p->temp_file_write_size = u->conf->temp_file_write_size;
+
+ p->preread_bufs = ngx_alloc_chain_link(r->pool);
+ if (p->preread_bufs == NULL) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ p->preread_bufs->buf = &u->buffer;
+ p->preread_bufs->next = NULL;
+ u->buffer.recycled = 1;
+
+ p->preread_size = u->buffer.last - u->buffer.pos;
+
+ if (u->cacheable) {
+
+ p->buf_to_file = ngx_calloc_buf(r->pool);
+ if (p->buf_to_file == NULL) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ p->buf_to_file->pos = u->buffer.start;
+ p->buf_to_file->last = u->buffer.pos;
+ p->buf_to_file->temporary = 1;
+ }
+
+ if (ngx_event_flags & NGX_USE_AIO_EVENT) {
+ /* the posted aio operation may corrupt a shadow buffer */
+ p->single_buf = 1;
+ }
+
+ /* TODO: p->free_bufs = 0 if use ngx_create_chain_of_bufs() */
+ p->free_bufs = 1;
+
+ /*
+ * event_pipe would do u->buffer.last += p->preread_size
+ * as though these bytes were read
+ */
+ u->buffer.last = u->buffer.pos;
+
+ if (u->conf->cyclic_temp_file) {
+
+ /*
+ * we need to disable the use of sendfile() if we use cyclic temp file
+ * because the writing a new data may interfere with sendfile()
+ * that uses the same kernel file pages (at least on FreeBSD)
+ */
+
+ p->cyclic_temp_file = 1;
+ c->sendfile = 0;
+
+ } else {
+ p->cyclic_temp_file = 0;
+ }
+
+ p->read_timeout = u->conf->read_timeout;
+ p->send_timeout = clcf->send_timeout;
+ p->send_lowat = clcf->send_lowat;
+
+ u->read_event_handler = ngx_http_upstream_process_upstream;
+ r->write_event_handler = ngx_http_upstream_process_downstream;
+
+ ngx_http_upstream_process_upstream(r, u);
+}
+
+
+static void
+ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r)
+{
+ ngx_event_t *wev;
+ ngx_connection_t *c;
+ ngx_http_upstream_t *u;
+
+ c = r->connection;
+ u = r->upstream;
+ wev = c->write;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream process non buffered downstream");
+
+ c->log->action = "sending to client";
+
+ if (wev->timedout) {
+ c->timedout = 1;
+ ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
+ ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT);
+ return;
+ }
+
+ ngx_http_upstream_process_non_buffered_request(r, 1);
+}
+
+
+static void
+ngx_http_upstream_process_non_buffered_upstream(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream process non buffered upstream");
+
+ c->log->action = "reading upstream";
+
+ if (c->read->timedout) {
+ ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ ngx_http_upstream_process_non_buffered_request(r, 0);
+}
+
+
+static void
+ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r,
+ ngx_uint_t do_write)
+{
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_int_t rc;
+ ngx_connection_t *downstream, *upstream;
+ ngx_http_upstream_t *u;
+ ngx_http_core_loc_conf_t *clcf;
+
+ u = r->upstream;
+ downstream = r->connection;
+ upstream = u->peer.connection;
+
+ b = &u->buffer;
+
+ do_write = do_write || u->length == 0;
+
+ for ( ;; ) {
+
+ if (do_write) {
+
+ if (u->out_bufs || u->busy_bufs) {
+ rc = ngx_http_output_filter(r, u->out_bufs);
+
+ if (rc == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ ngx_chain_update_chains(&u->free_bufs, &u->busy_bufs,
+ &u->out_bufs, u->output.tag);
+ }
+
+ if (u->busy_bufs == NULL) {
+
+ if (u->length == 0
+ || upstream->read->eof
+ || upstream->read->error)
+ {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ b->pos = b->start;
+ b->last = b->start;
+ }
+ }
+
+ size = b->end - b->last;
+
+ if (size > u->length) {
+ size = u->length;
+ }
+
+ if (size && upstream->read->ready) {
+
+ n = upstream->recv(upstream, b->last, size);
+
+ if (n == NGX_AGAIN) {
+ break;
+ }
+
+ if (n > 0) {
+ u->state->response_length += n;
+
+ if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+ }
+
+ do_write = 1;
+
+ continue;
+ }
+
+ break;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (downstream->data == r) {
+ if (ngx_handle_write_event(downstream->write, clcf->send_lowat)
+ != NGX_OK)
+ {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+ }
+
+ if (downstream->write->active && !downstream->write->ready) {
+ ngx_add_timer(downstream->write, clcf->send_timeout);
+
+ } else if (downstream->write->timer_set) {
+ ngx_del_timer(downstream->write);
+ }
+
+ if (ngx_handle_read_event(upstream->read, 0) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ if (upstream->read->active && !upstream->read->ready) {
+ ngx_add_timer(upstream->read, u->conf->read_timeout);
+
+ } else if (upstream->read->timer_set) {
+ ngx_del_timer(upstream->read);
+ }
+}
+
+
+static ngx_int_t
+ngx_http_upstream_non_buffered_filter_init(void *data)
+{
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_non_buffered_filter(void *data, ssize_t bytes)
+{
+ ngx_http_request_t *r = data;
+
+ ngx_buf_t *b;
+ ngx_chain_t *cl, **ll;
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+
+ for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) {
+ ll = &cl->next;
+ }
+
+ cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ll = cl;
+
+ cl->buf->flush = 1;
+ cl->buf->memory = 1;
+
+ b = &u->buffer;
+
+ cl->buf->pos = b->last;
+ b->last += bytes;
+ cl->buf->last = b->last;
+ cl->buf->tag = u->output.tag;
+
+ if (u->length == NGX_MAX_SIZE_T_VALUE) {
+ return NGX_OK;
+ }
+
+ u->length -= bytes;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_process_downstream(ngx_http_request_t *r)
+{
+ ngx_event_t *wev;
+ ngx_connection_t *c;
+ ngx_event_pipe_t *p;
+ ngx_http_upstream_t *u;
+
+ c = r->connection;
+ u = r->upstream;
+ p = u->pipe;
+ wev = c->write;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream process downstream");
+
+ c->log->action = "sending to client";
+
+ if (wev->timedout) {
+
+ if (wev->delayed) {
+
+ wev->timedout = 0;
+ wev->delayed = 0;
+
+ if (!wev->ready) {
+ ngx_add_timer(wev, p->send_timeout);
+
+ if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ }
+
+ return;
+ }
+
+ if (ngx_event_pipe(p, wev->write) == NGX_ABORT) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+
+ } else {
+ p->downstream_error = 1;
+ c->timedout = 1;
+ ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
+ }
+
+ } else {
+
+ if (wev->delayed) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http downstream delayed");
+
+ if (ngx_handle_write_event(wev, p->send_lowat) != NGX_OK) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ }
+
+ return;
+ }
+
+ if (ngx_event_pipe(p, 1) == NGX_ABORT) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+ }
+
+ ngx_http_upstream_process_request(r);
+}
+
+
+static void
+ngx_http_upstream_process_upstream(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_connection_t *c;
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http upstream process upstream");
+
+ c->log->action = "reading upstream";
+
+ if (c->read->timedout) {
+ u->pipe->upstream_error = 1;
+ ngx_connection_error(c, NGX_ETIMEDOUT, "upstream timed out");
+
+ } else {
+ if (ngx_event_pipe(u->pipe, 0) == NGX_ABORT) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+ }
+
+ ngx_http_upstream_process_request(r);
+}
+
+
+static void
+ngx_http_upstream_process_request(ngx_http_request_t *r)
+{
+ ngx_temp_file_t *tf;
+ ngx_event_pipe_t *p;
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+ p = u->pipe;
+
+ if (u->peer.connection) {
+
+ if (u->store) {
+
+ if (p->upstream_eof || p->upstream_done) {
+
+ tf = u->pipe->temp_file;
+
+ if (u->headers_in.status_n == NGX_HTTP_OK
+ && (u->headers_in.content_length_n == -1
+ || (u->headers_in.content_length_n == tf->offset)))
+ {
+ ngx_http_upstream_store(r, u);
+ u->store = 0;
+ }
+ }
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (u->cacheable) {
+
+ if (p->upstream_done) {
+ ngx_http_file_cache_update(r, u->pipe->temp_file);
+
+ } else if (p->upstream_eof) {
+
+ /* TODO: check length & update cache */
+
+ ngx_http_file_cache_update(r, u->pipe->temp_file);
+
+ } else if (p->upstream_error) {
+ ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+ }
+ }
+
+#endif
+
+ if (p->upstream_done || p->upstream_eof || p->upstream_error) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream exit: %p", p->out);
+#if 0
+ ngx_http_busy_unlock(u->conf->busy_lock, &u->busy_lock);
+#endif
+ ngx_http_upstream_finalize_request(r, u, 0);
+ return;
+ }
+ }
+
+ if (p->downstream_error) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream downstream error");
+
+ if (!u->cacheable && !u->store && u->peer.connection) {
+ ngx_http_upstream_finalize_request(r, u, 0);
+ }
+ }
+}
+
+
+static void
+ngx_http_upstream_store(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ size_t root;
+ time_t lm;
+ ngx_str_t path;
+ ngx_temp_file_t *tf;
+ ngx_ext_rename_file_t ext;
+
+ tf = u->pipe->temp_file;
+
+ if (tf->file.fd == NGX_INVALID_FILE) {
+
+ /* create file for empty 200 response */
+
+ tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t));
+ if (tf == NULL) {
+ return;
+ }
+
+ tf->file.fd = NGX_INVALID_FILE;
+ tf->file.log = r->connection->log;
+ tf->path = u->conf->temp_path;
+ tf->pool = r->pool;
+ tf->persistent = 1;
+
+ if (ngx_create_temp_file(&tf->file, tf->path, tf->pool,
+ tf->persistent, tf->clean, tf->access)
+ != NGX_OK)
+ {
+ return;
+ }
+
+ u->pipe->temp_file = tf;
+ }
+
+ ext.access = u->conf->store_access;
+ ext.path_access = u->conf->store_access;
+ ext.time = -1;
+ ext.create_path = 1;
+ ext.delete_file = 1;
+ ext.log = r->connection->log;
+
+ if (u->headers_in.last_modified) {
+
+ lm = ngx_http_parse_time(u->headers_in.last_modified->value.data,
+ u->headers_in.last_modified->value.len);
+
+ if (lm != NGX_ERROR) {
+ ext.time = lm;
+ ext.fd = tf->file.fd;
+ }
+ }
+
+ if (u->conf->store_lengths == NULL) {
+
+ ngx_http_map_uri_to_path(r, &path, &root, 0);
+
+ } else {
+ if (ngx_http_script_run(r, &path, u->conf->store_lengths->elts, 0,
+ u->conf->store_values->elts)
+ == NULL)
+ {
+ return;
+ }
+ }
+
+ path.len--;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "upstream stores \"%s\" to \"%s\"",
+ tf->file.name.data, path.data);
+
+ (void) ngx_ext_rename_file(&tf->file.name, &path, &ext);
+}
+
+
+static void
+ngx_http_upstream_dummy_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream dummy handler");
+}
+
+
+static void
+ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u,
+ ngx_uint_t ft_type)
+{
+ ngx_uint_t status, state;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http next upstream, %xi", ft_type);
+
+#if 0
+ ngx_http_busy_unlock(u->conf->busy_lock, &u->busy_lock);
+#endif
+
+ if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) {
+ state = NGX_PEER_NEXT;
+ } else {
+ state = NGX_PEER_FAILED;
+ }
+
+ if (ft_type != NGX_HTTP_UPSTREAM_FT_NOLIVE) {
+ u->peer.free(&u->peer, u->peer.data, state);
+ }
+
+ if (ft_type == NGX_HTTP_UPSTREAM_FT_TIMEOUT) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ }
+
+ if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) {
+ status = 0;
+
+ } else {
+ switch(ft_type) {
+
+ case NGX_HTTP_UPSTREAM_FT_TIMEOUT:
+ status = NGX_HTTP_GATEWAY_TIME_OUT;
+ break;
+
+ case NGX_HTTP_UPSTREAM_FT_HTTP_500:
+ status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+
+ case NGX_HTTP_UPSTREAM_FT_HTTP_404:
+ status = NGX_HTTP_NOT_FOUND;
+ break;
+
+ /*
+ * NGX_HTTP_UPSTREAM_FT_BUSY_LOCK and NGX_HTTP_UPSTREAM_FT_MAX_WAITING
+ * never reach here
+ */
+
+ default:
+ status = NGX_HTTP_BAD_GATEWAY;
+ }
+ }
+
+ if (r->connection->error) {
+ ngx_http_upstream_finalize_request(r, u,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+ return;
+ }
+
+ if (status) {
+ u->state->status = status;
+
+ if (u->peer.tries == 0 || !(u->conf->next_upstream & ft_type)) {
+
+#if (NGX_HTTP_CACHE)
+
+ if (u->cache_status == NGX_HTTP_CACHE_EXPIRED
+ && (u->conf->cache_use_stale & ft_type))
+ {
+ ngx_int_t rc;
+
+ rc = u->reinit_request(r);
+
+ if (rc == NGX_OK) {
+ u->cache_status = NGX_HTTP_CACHE_STALE;
+ rc = ngx_http_upstream_cache_send(r, u);
+ }
+
+ ngx_http_upstream_finalize_request(r, u, rc);
+ return;
+ }
+#endif
+
+ ngx_http_upstream_finalize_request(r, u, status);
+ return;
+ }
+ }
+
+ if (u->peer.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "close http upstream connection: %d",
+ u->peer.connection->fd);
+#if (NGX_HTTP_SSL)
+
+ if (u->peer.connection->ssl) {
+ u->peer.connection->ssl->no_wait_shutdown = 1;
+ u->peer.connection->ssl->no_send_shutdown = 1;
+
+ (void) ngx_ssl_shutdown(u->peer.connection);
+ }
+#endif
+
+ ngx_close_connection(u->peer.connection);
+ u->peer.connection = NULL;
+ }
+
+#if 0
+ if (u->conf->busy_lock && !u->busy_locked) {
+ ngx_http_upstream_busy_lock(p);
+ return;
+ }
+#endif
+
+ ngx_http_upstream_connect(r, u);
+}
+
+
+static void
+ngx_http_upstream_cleanup(void *data)
+{
+ ngx_http_request_t *r = data;
+
+ ngx_http_upstream_t *u;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "cleanup http upstream request: \"%V\"", &r->uri);
+
+ u = r->upstream;
+
+ if (u->resolved && u->resolved->ctx) {
+ ngx_resolve_name_done(u->resolved->ctx);
+ u->resolved->ctx = NULL;
+ }
+
+ ngx_http_upstream_finalize_request(r, u, NGX_DONE);
+}
+
+
+static void
+ngx_http_upstream_finalize_request(ngx_http_request_t *r,
+ ngx_http_upstream_t *u, ngx_int_t rc)
+{
+ ngx_time_t *tp;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http upstream request: %i", rc);
+
+ if (u->cleanup) {
+ *u->cleanup = NULL;
+ u->cleanup = NULL;
+ }
+
+ if (u->resolved && u->resolved->ctx) {
+ ngx_resolve_name_done(u->resolved->ctx);
+ u->resolved->ctx = NULL;
+ }
+
+ if (u->state && u->state->response_sec) {
+ tp = ngx_timeofday();
+ u->state->response_sec = tp->sec - u->state->response_sec;
+ u->state->response_msec = tp->msec - u->state->response_msec;
+
+ if (u->pipe) {
+ u->state->response_length = u->pipe->read_length;
+ }
+ }
+
+ u->finalize_request(r, rc);
+
+ if (u->peer.free) {
+ u->peer.free(&u->peer, u->peer.data, 0);
+ }
+
+ if (u->peer.connection) {
+
+#if (NGX_HTTP_SSL)
+
+ /* TODO: do not shutdown persistent connection */
+
+ if (u->peer.connection->ssl) {
+
+ /*
+ * We send the "close notify" shutdown alert to the upstream only
+ * and do not wait its "close notify" shutdown alert.
+ * It is acceptable according to the TLS standard.
+ */
+
+ u->peer.connection->ssl->no_wait_shutdown = 1;
+
+ (void) ngx_ssl_shutdown(u->peer.connection);
+ }
+#endif
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "close http upstream connection: %d",
+ u->peer.connection->fd);
+
+ ngx_close_connection(u->peer.connection);
+ }
+
+ u->peer.connection = NULL;
+
+ if (u->pipe && u->pipe->temp_file) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream temp fd: %d",
+ u->pipe->temp_file->file.fd);
+ }
+
+ if (u->store && u->pipe && u->pipe->temp_file
+ && u->pipe->temp_file->file.fd != NGX_INVALID_FILE)
+ {
+ if (ngx_delete_file(u->pipe->temp_file->file.name.data)
+ == NGX_FILE_ERROR)
+ {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ ngx_delete_file_n " \"%s\" failed",
+ u->pipe->temp_file->file.name.data);
+ }
+ }
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->cache) {
+
+ if (u->cacheable) {
+
+ if (rc == NGX_HTTP_BAD_GATEWAY || rc == NGX_HTTP_GATEWAY_TIME_OUT) {
+ time_t valid;
+
+ valid = ngx_http_file_cache_valid(u->conf->cache_valid, rc);
+
+ if (valid) {
+ r->cache->valid_sec = ngx_time() + valid;
+ r->cache->error = rc;
+ }
+ }
+ }
+
+ ngx_http_file_cache_free(r->cache, u->pipe->temp_file);
+ }
+
+#endif
+
+ if (u->header_sent
+ && rc != NGX_HTTP_REQUEST_TIME_OUT
+ && (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE))
+ {
+ rc = 0;
+ }
+
+ if (rc == NGX_DECLINED) {
+ return;
+ }
+
+ r->connection->log->action = "sending to client";
+
+ if (rc == 0
+#if (NGX_HTTP_CACHE)
+ && !r->cached
+#endif
+ )
+ {
+ rc = ngx_http_send_special(r, NGX_HTTP_LAST);
+ }
+
+ ngx_http_finalize_request(r, rc);
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_table_elt_t **ph;
+
+ ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset);
+
+ if (*ph == NULL) {
+ *ph = h;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_ignore_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+#if (NGX_HTTP_CACHE)
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+
+ if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) {
+ u->cacheable = 0;
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_cache_control(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset)
+{
+ ngx_array_t *pa;
+ ngx_table_elt_t **ph;
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+ pa = &u->headers_in.cache_control;
+
+ if (pa->elts == NULL) {
+ if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ ph = ngx_array_push(pa);
+ if (ph == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ph = h;
+
+#if (NGX_HTTP_CACHE)
+ {
+ u_char *p, *last;
+ ngx_int_t n;
+
+ if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL) {
+ return NGX_OK;
+ }
+
+ if (r->cache == NULL) {
+ return NGX_OK;
+ }
+
+ if (r->cache->valid_sec != 0) {
+ return NGX_OK;
+ }
+
+ p = h->value.data;
+ last = p + h->value.len;
+
+ if (ngx_strlcasestrn(p, last, (u_char *) "no-cache", 8 - 1) != NULL
+ || ngx_strlcasestrn(p, last, (u_char *) "no-store", 8 - 1) != NULL
+ || ngx_strlcasestrn(p, last, (u_char *) "private", 7 - 1) != NULL)
+ {
+ u->cacheable = 0;
+ return NGX_OK;
+ }
+
+ p = ngx_strlcasestrn(p, last, (u_char *) "max-age=", 8 - 1);
+
+ if (p == NULL) {
+ return NGX_OK;
+ }
+
+ n = 0;
+
+ for (p += 8; p < last; p++) {
+ if (*p == ',' || *p == ';' || *p == ' ') {
+ break;
+ }
+
+ if (*p >= '0' && *p <= '9') {
+ n = n * 10 + *p - '0';
+ continue;
+ }
+
+ u->cacheable = 0;
+ return NGX_OK;
+ }
+
+ if (n == 0) {
+ u->cacheable = 0;
+ return NGX_OK;
+ }
+
+ r->cache->valid_sec = ngx_time() + n;
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+ u->headers_in.expires = h;
+
+#if (NGX_HTTP_CACHE)
+ {
+ time_t expires;
+
+ if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_EXPIRES) {
+ return NGX_OK;
+ }
+
+ if (r->cache == NULL) {
+ return NGX_OK;
+ }
+
+ if (r->cache->valid_sec != 0) {
+ return NGX_OK;
+ }
+
+ expires = ngx_http_parse_time(h->value.data, h->value.len);
+
+ if (expires == NGX_ERROR || expires < ngx_time()) {
+ u->cacheable = 0;
+ return NGX_OK;
+ }
+
+ r->cache->valid_sec = expires;
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_accel_expires(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset)
+{
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+ u->headers_in.x_accel_expires = h;
+
+#if (NGX_HTTP_CACHE)
+ {
+ u_char *p;
+ size_t len;
+ ngx_int_t n;
+
+ if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES) {
+ return NGX_OK;
+ }
+
+ if (r->cache == NULL) {
+ return NGX_OK;
+ }
+
+ len = h->value.len;
+ p = h->value.data;
+
+ if (p[0] != '@') {
+ n = ngx_atoi(p, len);
+
+ switch (n) {
+ case 0:
+ u->cacheable = 0;
+ case NGX_ERROR:
+ return NGX_OK;
+
+ default:
+ r->cache->valid_sec = ngx_time() + n;
+ return NGX_OK;
+ }
+ }
+
+ p++;
+ len--;
+
+ n = ngx_atoi(p, len);
+
+ if (n != NGX_ERROR) {
+ r->cache->valid_sec = n;
+ }
+ }
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_limit_rate(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_int_t n;
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+ u->headers_in.x_accel_limit_rate = h;
+
+ if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE) {
+ return NGX_OK;
+ }
+
+ n = ngx_atoi(h->value.data, h->value.len);
+
+ if (n != NGX_ERROR) {
+ r->limit_rate = (size_t) n;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_buffering(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ u_char c0, c1, c2;
+ ngx_http_upstream_t *u;
+
+ u = r->upstream;
+
+ if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING) {
+ return NGX_OK;
+ }
+
+ if (u->conf->change_buffering) {
+
+ if (h->value.len == 2) {
+ c0 = ngx_tolower(h->value.data[0]);
+ c1 = ngx_tolower(h->value.data[1]);
+
+ if (c0 == 'n' && c1 == 'o') {
+ u->buffering = 0;
+ }
+
+ } else if (h->value.len == 3) {
+ c0 = ngx_tolower(h->value.data[0]);
+ c1 = ngx_tolower(h->value.data[1]);
+ c2 = ngx_tolower(h->value.data[2]);
+
+ if (c0 == 'y' && c1 == 'e' && c2 == 's') {
+ u->buffering = 1;
+ }
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_process_charset(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ if (r->upstream->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) {
+ return NGX_OK;
+ }
+
+ r->headers_out.override_charset = &h->value;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_table_elt_t *ho, **ph;
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ if (offset) {
+ ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset);
+ *ph = ho;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset)
+{
+ ngx_array_t *pa;
+ ngx_table_elt_t *ho, **ph;
+
+ pa = (ngx_array_t *) ((char *) &r->headers_out + offset);
+
+ if (pa->elts == NULL) {
+ if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+ }
+
+ ph = ngx_array_push(pa);
+ if (ph == NULL) {
+ return NGX_ERROR;
+ }
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+ *ph = ho;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_content_type(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ u_char *p, *last;
+
+ r->headers_out.content_type_len = h->value.len;
+ r->headers_out.content_type = h->value;
+ r->headers_out.content_type_lowcase = NULL;
+
+ for (p = h->value.data; *p; p++) {
+
+ if (*p != ';') {
+ continue;
+ }
+
+ last = p;
+
+ while (*++p == ' ') { /* void */ }
+
+ if (*p == '\0') {
+ return NGX_OK;
+ }
+
+ if (ngx_strncasecmp(p, (u_char *) "charset=", 8) != 0) {
+ continue;
+ }
+
+ p += 8;
+
+ r->headers_out.content_type_len = last - h->value.data;
+
+ if (*p == '"') {
+ p++;
+ }
+
+ last = h->value.data + h->value.len;
+
+ if (*(last - 1) == '"') {
+ last--;
+ }
+
+ r->headers_out.charset.len = last - p;
+ r->headers_out.charset.data = p;
+
+ return NGX_OK;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_content_length(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_table_elt_t *ho;
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ r->headers_out.content_length = ho;
+ r->headers_out.content_length_n = ngx_atoof(h->value.data, h->value.len);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_table_elt_t *ho;
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ r->headers_out.last_modified = ho;
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->upstream->cacheable) {
+ r->headers_out.last_modified_time = ngx_http_parse_time(h->value.data,
+ h->value.len);
+ }
+
+#endif
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_rewrite_location(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ ngx_int_t rc;
+ ngx_table_elt_t *ho;
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ if (r->upstream->rewrite_redirect) {
+ rc = r->upstream->rewrite_redirect(r, ho, 0);
+
+ if (rc == NGX_DECLINED) {
+ return NGX_OK;
+ }
+
+ if (rc == NGX_OK) {
+ r->headers_out.location = ho;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "rewritten location: \"%V\"", &ho->value);
+ }
+
+ return rc;
+ }
+
+ if (ho->value.data[0] != '/') {
+ r->headers_out.location = ho;
+ }
+
+ /*
+ * we do not set r->headers_out.location here to avoid the handling
+ * the local redirects without a host name by ngx_http_header_filter()
+ */
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, ngx_table_elt_t *h,
+ ngx_uint_t offset)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_table_elt_t *ho;
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ if (r->upstream->rewrite_redirect) {
+
+ p = ngx_strcasestrn(ho->value.data, "url=", 4 - 1);
+
+ if (p) {
+ rc = r->upstream->rewrite_redirect(r, ho, p + 4 - ho->value.data);
+
+ } else {
+ return NGX_OK;
+ }
+
+ if (rc == NGX_DECLINED) {
+ return NGX_OK;
+ }
+
+ if (rc == NGX_OK) {
+ r->headers_out.refresh = ho;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "rewritten refresh: \"%V\"", &ho->value);
+ }
+
+ return rc;
+ }
+
+ r->headers_out.refresh = ho;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset)
+{
+ ngx_table_elt_t *ho;
+
+#if (NGX_HTTP_CACHE)
+
+ if (r->cached) {
+ r->allow_ranges = 1;
+ return NGX_OK;
+
+ }
+
+#endif
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ r->headers_out.accept_ranges = ho;
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HTTP_GZIP)
+
+static ngx_int_t
+ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r,
+ ngx_table_elt_t *h, ngx_uint_t offset)
+{
+ ngx_table_elt_t *ho;
+
+ ho = ngx_list_push(&r->headers_out.headers);
+ if (ho == NULL) {
+ return NGX_ERROR;
+ }
+
+ *ho = *h;
+
+ r->headers_out.content_encoding = ho;
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_http_upstream_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ for (v = ngx_http_upstream_vars; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_addr_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_http_upstream_state_t *state;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ len = 0;
+ state = r->upstream_states->elts;
+
+ for (i = 0; i < r->upstream_states->nelts; i++) {
+ if (state[i].peer) {
+ len += state[i].peer->len + 2;
+
+ } else {
+ len += 3;
+ }
+ }
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->data = p;
+
+ i = 0;
+
+ for ( ;; ) {
+ if (state[i].peer) {
+ p = ngx_cpymem(p, state[i].peer->data, state[i].peer->len);
+ }
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ if (state[i].peer) {
+ *p++ = ',';
+ *p++ = ' ';
+
+ } else {
+ *p++ = ' ';
+ *p++ = ':';
+ *p++ = ' ';
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ continue;
+ }
+ }
+
+ v->len = p - v->data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_status_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_http_upstream_state_t *state;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ len = r->upstream_states->nelts * (3 + 2);
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->data = p;
+
+ i = 0;
+ state = r->upstream_states->elts;
+
+ for ( ;; ) {
+ if (state[i].status) {
+ p = ngx_sprintf(p, "%ui", state[i].status);
+
+ } else {
+ *p++ = '-';
+ }
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ if (state[i].peer) {
+ *p++ = ',';
+ *p++ = ' ';
+
+ } else {
+ *p++ = ' ';
+ *p++ = ':';
+ *p++ = ' ';
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ continue;
+ }
+ }
+
+ v->len = p - v->data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_response_time_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_msec_int_t ms;
+ ngx_http_upstream_state_t *state;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ len = r->upstream_states->nelts * (NGX_TIME_T_LEN + 4 + 2);
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->data = p;
+
+ i = 0;
+ state = r->upstream_states->elts;
+
+ for ( ;; ) {
+ if (state[i].status) {
+ ms = (ngx_msec_int_t)
+ (state[i].response_sec * 1000 + state[i].response_msec);
+ ms = ngx_max(ms, 0);
+ p = ngx_sprintf(p, "%d.%03d", ms / 1000, ms % 1000);
+
+ } else {
+ *p++ = '-';
+ }
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ if (state[i].peer) {
+ *p++ = ',';
+ *p++ = ' ';
+
+ } else {
+ *p++ = ' ';
+ *p++ = ':';
+ *p++ = ' ';
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ continue;
+ }
+ }
+
+ v->len = p - v->data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_response_length_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_http_upstream_state_t *state;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (r->upstream_states == NULL || r->upstream_states->nelts == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ len = r->upstream_states->nelts * (NGX_OFF_T_LEN + 2);
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->data = p;
+
+ i = 0;
+ state = r->upstream_states->elts;
+
+ for ( ;; ) {
+ p = ngx_sprintf(p, "%O", state[i].response_length);
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ if (state[i].peer) {
+ *p++ = ',';
+ *p++ = ' ';
+
+ } else {
+ *p++ = ' ';
+ *p++ = ':';
+ *p++ = ' ';
+
+ if (++i == r->upstream_states->nelts) {
+ break;
+ }
+
+ continue;
+ }
+ }
+
+ v->len = p - v->data;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_upstream_header_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->upstream == NULL) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+ &r->upstream->headers_in.headers.part,
+ sizeof("upstream_http_") - 1);
+}
+
+
+#if (NGX_HTTP_CACHE)
+
+ngx_int_t
+ngx_http_upstream_cache_status(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_uint_t n;
+
+ if (r->upstream == NULL || r->upstream->cache_status == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ n = r->upstream->cache_status - 1;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->len = ngx_http_cache_status[n].len;
+ v->data = ngx_http_cache_status[n].data;
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static char *
+ngx_http_upstream(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy)
+{
+ char *rv;
+ void *mconf;
+ ngx_str_t *value;
+ ngx_url_t u;
+ ngx_uint_t m;
+ ngx_conf_t pcf;
+ ngx_http_module_t *module;
+ ngx_http_conf_ctx_t *ctx, *http_ctx;
+ ngx_http_upstream_srv_conf_t *uscf;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ value = cf->args->elts;
+ u.host = value[1];
+ u.no_resolve = 1;
+
+ uscf = ngx_http_upstream_add(cf, &u, NGX_HTTP_UPSTREAM_CREATE
+ |NGX_HTTP_UPSTREAM_WEIGHT
+ |NGX_HTTP_UPSTREAM_MAX_FAILS
+ |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
+ |NGX_HTTP_UPSTREAM_DOWN
+ |NGX_HTTP_UPSTREAM_BACKUP);
+ if (uscf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ http_ctx = cf->ctx;
+ ctx->main_conf = http_ctx->main_conf;
+
+ /* the upstream{}'s srv_conf */
+
+ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->srv_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->srv_conf[ngx_http_upstream_module.ctx_index] = uscf;
+
+ uscf->srv_conf = ctx->srv_conf;
+
+
+ /* the upstream{}'s loc_conf */
+
+ ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
+ if (ctx->loc_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->create_srv_conf) {
+ mconf = module->create_srv_conf(cf);
+ if (mconf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf;
+ }
+
+ if (module->create_loc_conf) {
+ mconf = module->create_loc_conf(cf);
+ if (mconf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->loc_conf[ngx_modules[m]->ctx_index] = mconf;
+ }
+ }
+
+
+ /* parse inside upstream{} */
+
+ pcf = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_HTTP_UPS_CONF;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = pcf;
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ if (uscf->servers == NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "no servers are inside upstream");
+ return NGX_CONF_ERROR;
+ }
+
+ return rv;
+}
+
+
+static char *
+ngx_http_upstream_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_http_upstream_srv_conf_t *uscf = conf;
+
+ time_t fail_timeout;
+ ngx_str_t *value, s;
+ ngx_url_t u;
+ ngx_int_t weight, max_fails;
+ ngx_uint_t i;
+ ngx_http_upstream_server_t *us;
+
+ if (uscf->servers == NULL) {
+ uscf->servers = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_http_upstream_server_t));
+ if (uscf->servers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ us = ngx_array_push(uscf->servers);
+ if (us == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.default_port = 80;
+
+ if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%s in upstream \"%V\"", u.err, &u.url);
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ weight = 1;
+ max_fails = 1;
+ fail_timeout = 10;
+
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "weight=", 7) == 0) {
+
+ if (!(uscf->flags & NGX_HTTP_UPSTREAM_WEIGHT)) {
+ goto invalid;
+ }
+
+ weight = ngx_atoi(&value[i].data[7], value[i].len - 7);
+
+ if (weight == NGX_ERROR || weight == 0) {
+ goto invalid;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "max_fails=", 10) == 0) {
+
+ if (!(uscf->flags & NGX_HTTP_UPSTREAM_MAX_FAILS)) {
+ goto invalid;
+ }
+
+ max_fails = ngx_atoi(&value[i].data[10], value[i].len - 10);
+
+ if (max_fails == NGX_ERROR) {
+ goto invalid;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "fail_timeout=", 13) == 0) {
+
+ if (!(uscf->flags & NGX_HTTP_UPSTREAM_FAIL_TIMEOUT)) {
+ goto invalid;
+ }
+
+ s.len = value[i].len - 13;
+ s.data = &value[i].data[13];
+
+ fail_timeout = ngx_parse_time(&s, 1);
+
+ if (fail_timeout == NGX_ERROR) {
+ goto invalid;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "backup", 6) == 0) {
+
+ if (!(uscf->flags & NGX_HTTP_UPSTREAM_BACKUP)) {
+ goto invalid;
+ }
+
+ us->backup = 1;
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "down", 4) == 0) {
+
+ if (!(uscf->flags & NGX_HTTP_UPSTREAM_DOWN)) {
+ goto invalid;
+ }
+
+ us->down = 1;
+
+ continue;
+ }
+
+ goto invalid;
+ }
+
+ us->addrs = u.addrs;
+ us->naddrs = u.naddrs;
+ us->weight = weight;
+ us->max_fails = max_fails;
+ us->fail_timeout = fail_timeout;
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid parameter \"%V\"", &value[i]);
+
+ return NGX_CONF_ERROR;
+}
+
+
+ngx_http_upstream_srv_conf_t *
+ngx_http_upstream_add(ngx_conf_t *cf, ngx_url_t *u, ngx_uint_t flags)
+{
+ ngx_uint_t i;
+ ngx_http_upstream_server_t *us;
+ ngx_http_upstream_srv_conf_t *uscf, **uscfp;
+ ngx_http_upstream_main_conf_t *umcf;
+
+ if (!(flags & NGX_HTTP_UPSTREAM_CREATE)) {
+
+ if (ngx_parse_url(cf->pool, u) != NGX_OK) {
+ if (u->err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%s in upstream \"%V\"", u->err, &u->url);
+ }
+
+ return NULL;
+ }
+ }
+
+ umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
+
+ uscfp = umcf->upstreams.elts;
+
+ for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+ if (uscfp[i]->host.len != u->host.len
+ || ngx_strncasecmp(uscfp[i]->host.data, u->host.data, u->host.len)
+ != 0)
+ {
+ continue;
+ }
+
+ if ((flags & NGX_HTTP_UPSTREAM_CREATE)
+ && (uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE))
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate upstream \"%V\"", &u->host);
+ return NULL;
+ }
+
+ if ((uscfp[i]->flags & NGX_HTTP_UPSTREAM_CREATE) && u->port) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "upstream \"%V\" may not have port %d",
+ &u->host, u->port);
+ return NULL;
+ }
+
+ if ((flags & NGX_HTTP_UPSTREAM_CREATE) && uscfp[i]->port) {
+ ngx_log_error(NGX_LOG_WARN, cf->log, 0,
+ "upstream \"%V\" may not have port %d in %s:%ui",
+ &u->host, uscfp[i]->port,
+ uscfp[i]->file_name, uscfp[i]->line);
+ return NULL;
+ }
+
+ if (uscfp[i]->port != u->port) {
+ continue;
+ }
+
+ if (uscfp[i]->default_port && u->default_port
+ && uscfp[i]->default_port != u->default_port)
+ {
+ continue;
+ }
+
+ if (flags & NGX_HTTP_UPSTREAM_CREATE) {
+ uscfp[i]->flags = flags;
+ }
+
+ return uscfp[i];
+ }
+
+ uscf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_srv_conf_t));
+ if (uscf == NULL) {
+ return NULL;
+ }
+
+ uscf->flags = flags;
+ uscf->host = u->host;
+ uscf->file_name = cf->conf_file->file.name.data;
+ uscf->line = cf->conf_file->line;
+ uscf->port = u->port;
+ uscf->default_port = u->default_port;
+
+ if (u->naddrs == 1) {
+ uscf->servers = ngx_array_create(cf->pool, 1,
+ sizeof(ngx_http_upstream_server_t));
+ if (uscf->servers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ us = ngx_array_push(uscf->servers);
+ if (us == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(us, sizeof(ngx_http_upstream_server_t));
+
+ us->addrs = u->addrs;
+ us->naddrs = u->naddrs;
+ }
+
+ uscfp = ngx_array_push(&umcf->upstreams);
+ if (uscfp == NULL) {
+ return NULL;
+ }
+
+ *uscfp = uscf;
+
+ return uscf;
+}
+
+
+char *
+ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf)
+{
+ char *p = conf;
+
+ ngx_int_t rc;
+ ngx_str_t *value;
+ ngx_addr_t **paddr;
+
+ paddr = (ngx_addr_t **) (p + cmd->offset);
+
+ *paddr = ngx_palloc(cf->pool, sizeof(ngx_addr_t));
+ if (*paddr == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ rc = ngx_parse_addr(cf->pool, *paddr, value[1].data, value[1].len);
+
+ switch (rc) {
+ case NGX_OK:
+ (*paddr)->name = value[1];
+ return NGX_CONF_OK;
+
+ case NGX_DECLINED:
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid address \"%V\"", &value[1]);
+ default:
+ return NGX_CONF_ERROR;
+ }
+}
+
+
+ngx_int_t
+ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf,
+ ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev,
+ ngx_str_t *default_hide_headers, ngx_hash_init_t *hash)
+{
+ ngx_str_t *h;
+ ngx_uint_t i, j;
+ ngx_array_t hide_headers;
+ ngx_hash_key_t *hk;
+
+ if (conf->hide_headers == NGX_CONF_UNSET_PTR
+ && conf->pass_headers == NGX_CONF_UNSET_PTR)
+ {
+ conf->hide_headers_hash = prev->hide_headers_hash;
+
+ if (conf->hide_headers_hash.buckets
+#if (NGX_HTTP_CACHE)
+ && ((conf->cache == NULL) == (prev->cache == NULL))
+#endif
+ )
+ {
+ return NGX_OK;
+ }
+
+ conf->hide_headers = prev->hide_headers;
+ conf->pass_headers = prev->pass_headers;
+
+ } else {
+ if (conf->hide_headers == NGX_CONF_UNSET_PTR) {
+ conf->hide_headers = prev->hide_headers;
+ }
+
+ if (conf->pass_headers == NGX_CONF_UNSET_PTR) {
+ conf->pass_headers = prev->pass_headers;
+ }
+ }
+
+ if (ngx_array_init(&hide_headers, cf->temp_pool, 4, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ for (h = default_hide_headers; h->len; h++) {
+ hk = ngx_array_push(&hide_headers);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key = *h;
+ hk->key_hash = ngx_hash_key_lc(h->data, h->len);
+ hk->value = (void *) 1;
+ }
+
+ if (conf->hide_headers != NGX_CONF_UNSET_PTR) {
+
+ h = conf->hide_headers->elts;
+
+ for (i = 0; i < conf->hide_headers->nelts; i++) {
+
+ hk = hide_headers.elts;
+
+ for (j = 0; j < hide_headers.nelts; j++) {
+ if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) {
+ goto exist;
+ }
+ }
+
+ hk = ngx_array_push(&hide_headers);
+ if (hk == NULL) {
+ return NGX_ERROR;
+ }
+
+ hk->key = h[i];
+ hk->key_hash = ngx_hash_key_lc(h[i].data, h[i].len);
+ hk->value = (void *) 1;
+
+ exist:
+
+ continue;
+ }
+ }
+
+ if (conf->pass_headers != NGX_CONF_UNSET_PTR) {
+
+ h = conf->pass_headers->elts;
+ hk = hide_headers.elts;
+
+ for (i = 0; i < conf->pass_headers->nelts; i++) {
+ for (j = 0; j < hide_headers.nelts; j++) {
+
+ if (hk[j].key.data == NULL) {
+ continue;
+ }
+
+ if (ngx_strcasecmp(h[i].data, hk[j].key.data) == 0) {
+ hk[j].key.data = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ hash->hash = &conf->hide_headers_hash;
+ hash->key = ngx_hash_key_lc;
+ hash->pool = cf->pool;
+ hash->temp_pool = NULL;
+
+ return ngx_hash_init(hash, hide_headers.elts, hide_headers.nelts);
+}
+
+
+static void *
+ngx_http_upstream_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_http_upstream_main_conf_t *umcf;
+
+ umcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_main_conf_t));
+ if (umcf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&umcf->upstreams, cf->pool, 4,
+ sizeof(ngx_http_upstream_srv_conf_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return umcf;
+}
+
+
+static char *
+ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
+{
+ ngx_http_upstream_main_conf_t *umcf = conf;
+
+ ngx_uint_t i;
+ ngx_array_t headers_in;
+ ngx_hash_key_t *hk;
+ ngx_hash_init_t hash;
+ ngx_http_upstream_init_pt init;
+ ngx_http_upstream_header_t *header;
+ ngx_http_upstream_srv_conf_t **uscfp;
+
+ uscfp = umcf->upstreams.elts;
+
+ for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+ init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:
+ ngx_http_upstream_init_round_robin;
+
+ if (init(cf, uscfp[i]) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+
+ /* upstream_headers_in_hash */
+
+ if (ngx_array_init(&headers_in, cf->temp_pool, 32, sizeof(ngx_hash_key_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ for (header = ngx_http_upstream_headers_in; header->name.len; header++) {
+ hk = ngx_array_push(&headers_in);
+ if (hk == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ hk->key = header->name;
+ hk->key_hash = ngx_hash_key_lc(header->name.data, header->name.len);
+ hk->value = header;
+ }
+
+ hash.hash = &umcf->headers_in_hash;
+ hash.key = ngx_hash_key_lc;
+ hash.max_size = 512;
+ hash.bucket_size = ngx_align(64, ngx_cacheline_size);
+ hash.name = "upstream_headers_in_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, headers_in.elts, headers_in.nelts) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h
new file mode 100644
index 0000000..17fe538
--- /dev/null
+++ b/src/http/ngx_http_upstream.h
@@ -0,0 +1,350 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_UPSTREAM_H_INCLUDED_
+#define _NGX_HTTP_UPSTREAM_H_INCLUDED_
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+#define NGX_HTTP_UPSTREAM_FT_ERROR 0x00000002
+#define NGX_HTTP_UPSTREAM_FT_TIMEOUT 0x00000004
+#define NGX_HTTP_UPSTREAM_FT_INVALID_HEADER 0x00000008
+#define NGX_HTTP_UPSTREAM_FT_HTTP_500 0x00000010
+#define NGX_HTTP_UPSTREAM_FT_HTTP_502 0x00000020
+#define NGX_HTTP_UPSTREAM_FT_HTTP_503 0x00000040
+#define NGX_HTTP_UPSTREAM_FT_HTTP_504 0x00000080
+#define NGX_HTTP_UPSTREAM_FT_HTTP_404 0x00000100
+#define NGX_HTTP_UPSTREAM_FT_UPDATING 0x00000200
+#define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00000400
+#define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00000800
+#define NGX_HTTP_UPSTREAM_FT_NOLIVE 0x40000000
+#define NGX_HTTP_UPSTREAM_FT_OFF 0x80000000
+
+#define NGX_HTTP_UPSTREAM_FT_STATUS (NGX_HTTP_UPSTREAM_FT_HTTP_500 \
+ |NGX_HTTP_UPSTREAM_FT_HTTP_502 \
+ |NGX_HTTP_UPSTREAM_FT_HTTP_503 \
+ |NGX_HTTP_UPSTREAM_FT_HTTP_504 \
+ |NGX_HTTP_UPSTREAM_FT_HTTP_404)
+
+#define NGX_HTTP_UPSTREAM_INVALID_HEADER 40
+
+
+#define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002
+#define NGX_HTTP_UPSTREAM_IGN_XA_EXPIRES 0x00000004
+#define NGX_HTTP_UPSTREAM_IGN_EXPIRES 0x00000008
+#define NGX_HTTP_UPSTREAM_IGN_CACHE_CONTROL 0x00000010
+#define NGX_HTTP_UPSTREAM_IGN_SET_COOKIE 0x00000020
+#define NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE 0x00000040
+#define NGX_HTTP_UPSTREAM_IGN_XA_BUFFERING 0x00000080
+#define NGX_HTTP_UPSTREAM_IGN_XA_CHARSET 0x00000100
+
+
+typedef struct {
+ ngx_msec_t bl_time;
+ ngx_uint_t bl_state;
+
+ ngx_uint_t status;
+ time_t response_sec;
+ ngx_uint_t response_msec;
+ off_t response_length;
+
+ ngx_str_t *peer;
+} ngx_http_upstream_state_t;
+
+
+typedef struct {
+ ngx_hash_t headers_in_hash;
+ ngx_array_t upstreams;
+ /* ngx_http_upstream_srv_conf_t */
+} ngx_http_upstream_main_conf_t;
+
+typedef struct ngx_http_upstream_srv_conf_s ngx_http_upstream_srv_conf_t;
+
+typedef ngx_int_t (*ngx_http_upstream_init_pt)(ngx_conf_t *cf,
+ ngx_http_upstream_srv_conf_t *us);
+typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us);
+
+
+typedef struct {
+ ngx_http_upstream_init_pt init_upstream;
+ ngx_http_upstream_init_peer_pt init;
+ void *data;
+} ngx_http_upstream_peer_t;
+
+
+typedef struct {
+ ngx_addr_t *addrs;
+ ngx_uint_t naddrs;
+ ngx_uint_t weight;
+ ngx_uint_t max_fails;
+ time_t fail_timeout;
+
+ unsigned down:1;
+ unsigned backup:1;
+} ngx_http_upstream_server_t;
+
+
+#define NGX_HTTP_UPSTREAM_CREATE 0x0001
+#define NGX_HTTP_UPSTREAM_WEIGHT 0x0002
+#define NGX_HTTP_UPSTREAM_MAX_FAILS 0x0004
+#define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 0x0008
+#define NGX_HTTP_UPSTREAM_DOWN 0x0010
+#define NGX_HTTP_UPSTREAM_BACKUP 0x0020
+
+
+struct ngx_http_upstream_srv_conf_s {
+ ngx_http_upstream_peer_t peer;
+ void **srv_conf;
+
+ ngx_array_t *servers; /* ngx_http_upstream_server_t */
+
+ ngx_uint_t flags;
+ ngx_str_t host;
+ u_char *file_name;
+ ngx_uint_t line;
+ in_port_t port;
+ in_port_t default_port;
+};
+
+
+typedef struct {
+ ngx_http_upstream_srv_conf_t *upstream;
+
+ ngx_msec_t connect_timeout;
+ ngx_msec_t send_timeout;
+ ngx_msec_t read_timeout;
+ ngx_msec_t timeout;
+
+ size_t send_lowat;
+ size_t buffer_size;
+
+ size_t busy_buffers_size;
+ size_t max_temp_file_size;
+ size_t temp_file_write_size;
+
+ size_t busy_buffers_size_conf;
+ size_t max_temp_file_size_conf;
+ size_t temp_file_write_size_conf;
+
+ ngx_bufs_t bufs;
+
+ ngx_uint_t ignore_headers;
+ ngx_uint_t next_upstream;
+ ngx_uint_t store_access;
+ ngx_flag_t buffering;
+ ngx_flag_t pass_request_headers;
+ ngx_flag_t pass_request_body;
+
+ ngx_flag_t ignore_client_abort;
+ ngx_flag_t intercept_errors;
+ ngx_flag_t cyclic_temp_file;
+
+ ngx_path_t *temp_path;
+
+ ngx_hash_t hide_headers_hash;
+ ngx_array_t *hide_headers;
+ ngx_array_t *pass_headers;
+
+ ngx_addr_t *local;
+
+#if (NGX_HTTP_CACHE)
+ ngx_shm_zone_t *cache;
+
+ ngx_uint_t cache_min_uses;
+ ngx_uint_t cache_use_stale;
+ ngx_uint_t cache_methods;
+
+ ngx_array_t *cache_valid;
+ ngx_array_t *cache_bypass;
+ ngx_array_t *no_cache;
+#endif
+
+ ngx_array_t *store_lengths;
+ ngx_array_t *store_values;
+
+ signed store:2;
+ unsigned intercept_404:1;
+ unsigned change_buffering:1;
+
+#if (NGX_HTTP_SSL)
+ ngx_ssl_t *ssl;
+ ngx_flag_t ssl_session_reuse;
+#endif
+
+ ngx_str_t module;
+} ngx_http_upstream_conf_t;
+
+
+typedef struct {
+ ngx_str_t name;
+ ngx_http_header_handler_pt handler;
+ ngx_uint_t offset;
+ ngx_http_header_handler_pt copy_handler;
+ ngx_uint_t conf;
+ ngx_uint_t redirect; /* unsigned redirect:1; */
+} ngx_http_upstream_header_t;
+
+
+typedef struct {
+ ngx_list_t headers;
+
+ ngx_uint_t status_n;
+ ngx_str_t status_line;
+
+ ngx_table_elt_t *status;
+ ngx_table_elt_t *date;
+ ngx_table_elt_t *server;
+ ngx_table_elt_t *connection;
+
+ ngx_table_elt_t *expires;
+ ngx_table_elt_t *etag;
+ ngx_table_elt_t *x_accel_expires;
+ ngx_table_elt_t *x_accel_redirect;
+ ngx_table_elt_t *x_accel_limit_rate;
+
+ ngx_table_elt_t *content_type;
+ ngx_table_elt_t *content_length;
+
+ ngx_table_elt_t *last_modified;
+ ngx_table_elt_t *location;
+ ngx_table_elt_t *accept_ranges;
+ ngx_table_elt_t *www_authenticate;
+
+#if (NGX_HTTP_GZIP)
+ ngx_table_elt_t *content_encoding;
+#endif
+
+ off_t content_length_n;
+
+ ngx_array_t cache_control;
+} ngx_http_upstream_headers_in_t;
+
+
+typedef struct {
+ ngx_str_t host;
+ in_port_t port;
+ ngx_uint_t no_port; /* unsigned no_port:1 */
+
+ ngx_uint_t naddrs;
+ in_addr_t *addrs;
+
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+
+ ngx_resolver_ctx_t *ctx;
+} ngx_http_upstream_resolved_t;
+
+
+typedef void (*ngx_http_upstream_handler_pt)(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+
+
+struct ngx_http_upstream_s {
+ ngx_http_upstream_handler_pt read_event_handler;
+ ngx_http_upstream_handler_pt write_event_handler;
+
+ ngx_peer_connection_t peer;
+
+ ngx_event_pipe_t *pipe;
+
+ ngx_chain_t *request_bufs;
+
+ ngx_output_chain_ctx_t output;
+ ngx_chain_writer_ctx_t writer;
+
+ ngx_http_upstream_conf_t *conf;
+
+ ngx_http_upstream_headers_in_t headers_in;
+
+ ngx_http_upstream_resolved_t *resolved;
+
+ ngx_buf_t buffer;
+ size_t length;
+
+ ngx_chain_t *out_bufs;
+ ngx_chain_t *busy_bufs;
+ ngx_chain_t *free_bufs;
+
+ ngx_int_t (*input_filter_init)(void *data);
+ ngx_int_t (*input_filter)(void *data, ssize_t bytes);
+ void *input_filter_ctx;
+
+#if (NGX_HTTP_CACHE)
+ ngx_int_t (*create_key)(ngx_http_request_t *r);
+#endif
+ ngx_int_t (*create_request)(ngx_http_request_t *r);
+ ngx_int_t (*reinit_request)(ngx_http_request_t *r);
+ ngx_int_t (*process_header)(ngx_http_request_t *r);
+ void (*abort_request)(ngx_http_request_t *r);
+ void (*finalize_request)(ngx_http_request_t *r,
+ ngx_int_t rc);
+ ngx_int_t (*rewrite_redirect)(ngx_http_request_t *r,
+ ngx_table_elt_t *h, size_t prefix);
+
+ ngx_msec_t timeout;
+
+ ngx_http_upstream_state_t *state;
+
+ ngx_str_t method;
+ ngx_str_t schema;
+ ngx_str_t uri;
+
+ ngx_http_cleanup_pt *cleanup;
+
+ unsigned store:1;
+ unsigned cacheable:1;
+ unsigned accel:1;
+ unsigned ssl:1;
+#if (NGX_HTTP_CACHE)
+ unsigned cache_status:3;
+#endif
+
+ unsigned buffering:1;
+
+ unsigned request_sent:1;
+ unsigned header_sent:1;
+};
+
+
+typedef struct {
+ ngx_uint_t status;
+ ngx_uint_t mask;
+} ngx_http_upstream_next_t;
+
+
+ngx_int_t ngx_http_upstream_header_variable(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+ngx_int_t ngx_http_upstream_create(ngx_http_request_t *r);
+void ngx_http_upstream_init(ngx_http_request_t *r);
+ngx_http_upstream_srv_conf_t *ngx_http_upstream_add(ngx_conf_t *cf,
+ ngx_url_t *u, ngx_uint_t flags);
+char *ngx_http_upstream_bind_set_slot(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+ngx_int_t ngx_http_upstream_hide_headers_hash(ngx_conf_t *cf,
+ ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev,
+ ngx_str_t *default_hide_headers, ngx_hash_init_t *hash);
+
+
+#define ngx_http_conf_upstream_srv_conf(uscf, module) \
+ uscf->srv_conf[module.ctx_index]
+
+
+extern ngx_module_t ngx_http_upstream_module;
+extern ngx_conf_bitmask_t ngx_http_upstream_cache_method_mask[];
+extern ngx_conf_bitmask_t ngx_http_upstream_ignore_headers_masks[];
+
+
+#endif /* _NGX_HTTP_UPSTREAM_H_INCLUDED_ */
diff --git a/src/http/ngx_http_upstream_round_robin.c b/src/http/ngx_http_upstream_round_robin.c
new file mode 100644
index 0000000..afc9b2e
--- /dev/null
+++ b/src/http/ngx_http_upstream_round_robin.c
@@ -0,0 +1,791 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+static ngx_int_t ngx_http_upstream_cmp_servers(const void *one,
+ const void *two);
+static ngx_uint_t
+ngx_http_upstream_get_peer(ngx_http_upstream_rr_peers_t *peers);
+
+#if (NGX_HTTP_SSL)
+
+static ngx_int_t ngx_http_upstream_empty_set_session(ngx_peer_connection_t *pc,
+ void *data);
+static void ngx_http_upstream_empty_save_session(ngx_peer_connection_t *pc,
+ void *data);
+
+#endif
+
+
+ngx_int_t
+ngx_http_upstream_init_round_robin(ngx_conf_t *cf,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ ngx_url_t u;
+ ngx_uint_t i, j, n;
+ ngx_http_upstream_server_t *server;
+ ngx_http_upstream_rr_peers_t *peers, *backup;
+
+ us->peer.init = ngx_http_upstream_init_round_robin_peer;
+
+ if (us->servers) {
+ server = us->servers->elts;
+
+ n = 0;
+
+ for (i = 0; i < us->servers->nelts; i++) {
+ if (server[i].backup) {
+ continue;
+ }
+
+ n += server[i].naddrs;
+ }
+
+ peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
+ + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));
+ if (peers == NULL) {
+ return NGX_ERROR;
+ }
+
+ peers->single = (n == 1);
+ peers->number = n;
+ peers->name = &us->host;
+
+ n = 0;
+
+ for (i = 0; i < us->servers->nelts; i++) {
+ for (j = 0; j < server[i].naddrs; j++) {
+ if (server[i].backup) {
+ continue;
+ }
+
+ peers->peer[n].sockaddr = server[i].addrs[j].sockaddr;
+ peers->peer[n].socklen = server[i].addrs[j].socklen;
+ peers->peer[n].name = server[i].addrs[j].name;
+ peers->peer[n].max_fails = server[i].max_fails;
+ peers->peer[n].fail_timeout = server[i].fail_timeout;
+ peers->peer[n].down = server[i].down;
+ peers->peer[n].weight = server[i].down ? 0 : server[i].weight;
+ peers->peer[n].current_weight = peers->peer[n].weight;
+ n++;
+ }
+ }
+
+ us->peer.data = peers;
+
+ ngx_sort(&peers->peer[0], (size_t) n,
+ sizeof(ngx_http_upstream_rr_peer_t),
+ ngx_http_upstream_cmp_servers);
+
+ /* backup servers */
+
+ n = 0;
+
+ for (i = 0; i < us->servers->nelts; i++) {
+ if (!server[i].backup) {
+ continue;
+ }
+
+ n += server[i].naddrs;
+ }
+
+ if (n == 0) {
+ return NGX_OK;
+ }
+
+ backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
+ + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));
+ if (backup == NULL) {
+ return NGX_ERROR;
+ }
+
+ peers->single = 0;
+ backup->single = 0;
+ backup->number = n;
+ backup->name = &us->host;
+
+ n = 0;
+
+ for (i = 0; i < us->servers->nelts; i++) {
+ for (j = 0; j < server[i].naddrs; j++) {
+ if (!server[i].backup) {
+ continue;
+ }
+
+ backup->peer[n].sockaddr = server[i].addrs[j].sockaddr;
+ backup->peer[n].socklen = server[i].addrs[j].socklen;
+ backup->peer[n].name = server[i].addrs[j].name;
+ backup->peer[n].weight = server[i].weight;
+ backup->peer[n].current_weight = server[i].weight;
+ backup->peer[n].max_fails = server[i].max_fails;
+ backup->peer[n].fail_timeout = server[i].fail_timeout;
+ backup->peer[n].down = server[i].down;
+ n++;
+ }
+ }
+
+ peers->next = backup;
+
+ ngx_sort(&backup->peer[0], (size_t) n,
+ sizeof(ngx_http_upstream_rr_peer_t),
+ ngx_http_upstream_cmp_servers);
+
+ return NGX_OK;
+ }
+
+
+ /* an upstream implicitly defined by proxy_pass, etc. */
+
+ if (us->port == 0 && us->default_port == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no port in upstream \"%V\" in %s:%ui",
+ &us->host, us->file_name, us->line);
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.host = us->host;
+ u.port = (in_port_t) (us->port ? us->port : us->default_port);
+
+ if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "%s in upstream \"%V\" in %s:%ui",
+ u.err, &us->host, us->file_name, us->line);
+ }
+
+ return NGX_ERROR;
+ }
+
+ n = u.naddrs;
+
+ peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
+ + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));
+ if (peers == NULL) {
+ return NGX_ERROR;
+ }
+
+ peers->single = (n == 1);
+ peers->number = n;
+ peers->name = &us->host;
+
+ for (i = 0; i < u.naddrs; i++) {
+ peers->peer[i].sockaddr = u.addrs[i].sockaddr;
+ peers->peer[i].socklen = u.addrs[i].socklen;
+ peers->peer[i].name = u.addrs[i].name;
+ peers->peer[i].weight = 1;
+ peers->peer[i].current_weight = 1;
+ peers->peer[i].max_fails = 1;
+ peers->peer[i].fail_timeout = 10;
+ }
+
+ us->peer.data = peers;
+
+ /* implicitly defined upstream has no backup servers */
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_upstream_cmp_servers(const void *one, const void *two)
+{
+ ngx_http_upstream_rr_peer_t *first, *second;
+
+ first = (ngx_http_upstream_rr_peer_t *) one;
+ second = (ngx_http_upstream_rr_peer_t *) two;
+
+ return (first->weight < second->weight);
+}
+
+
+ngx_int_t
+ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us)
+{
+ ngx_uint_t n;
+ ngx_http_upstream_rr_peer_data_t *rrp;
+
+ rrp = r->upstream->peer.data;
+
+ if (rrp == NULL) {
+ rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t));
+ if (rrp == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.data = rrp;
+ }
+
+ rrp->peers = us->peer.data;
+ rrp->current = 0;
+
+ n = rrp->peers->number;
+
+ if (rrp->peers->next && rrp->peers->next->number > n) {
+ n = rrp->peers->next->number;
+ }
+
+ if (n <= 8 * sizeof(uintptr_t)) {
+ rrp->tried = &rrp->data;
+ rrp->data = 0;
+
+ } else {
+ n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t));
+
+ rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t));
+ if (rrp->tried == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;
+ r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;
+ r->upstream->peer.tries = rrp->peers->number;
+#if (NGX_HTTP_SSL)
+ r->upstream->peer.set_session =
+ ngx_http_upstream_set_round_robin_peer_session;
+ r->upstream->peer.save_session =
+ ngx_http_upstream_save_round_robin_peer_session;
+#endif
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r,
+ ngx_http_upstream_resolved_t *ur)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i, n;
+ struct sockaddr_in *sin;
+ ngx_http_upstream_rr_peers_t *peers;
+ ngx_http_upstream_rr_peer_data_t *rrp;
+
+ rrp = r->upstream->peer.data;
+
+ if (rrp == NULL) {
+ rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t));
+ if (rrp == NULL) {
+ return NGX_ERROR;
+ }
+
+ r->upstream->peer.data = rrp;
+ }
+
+ peers = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_rr_peers_t)
+ + sizeof(ngx_http_upstream_rr_peer_t) * (ur->naddrs - 1));
+ if (peers == NULL) {
+ return NGX_ERROR;
+ }
+
+ peers->single = (ur->naddrs == 1);
+ peers->number = ur->naddrs;
+ peers->name = &ur->host;
+
+ if (ur->sockaddr) {
+ peers->peer[0].sockaddr = ur->sockaddr;
+ peers->peer[0].socklen = ur->socklen;
+ peers->peer[0].name = ur->host;
+ peers->peer[0].weight = 1;
+ peers->peer[0].current_weight = 1;
+ peers->peer[0].max_fails = 1;
+ peers->peer[0].fail_timeout = 10;
+
+ } else {
+
+ for (i = 0; i < ur->naddrs; i++) {
+
+ len = NGX_INET_ADDRSTRLEN + sizeof(":65536") - 1;
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ len = ngx_inet_ntop(AF_INET, &ur->addrs[i], p, NGX_INET_ADDRSTRLEN);
+ len = ngx_sprintf(&p[len], ":%d", ur->port) - p;
+
+ sin = ngx_pcalloc(r->pool, sizeof(struct sockaddr_in));
+ if (sin == NULL) {
+ return NGX_ERROR;
+ }
+
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(ur->port);
+ sin->sin_addr.s_addr = ur->addrs[i];
+
+ peers->peer[i].sockaddr = (struct sockaddr *) sin;
+ peers->peer[i].socklen = sizeof(struct sockaddr_in);
+ peers->peer[i].name.len = len;
+ peers->peer[i].name.data = p;
+ peers->peer[i].weight = 1;
+ peers->peer[i].current_weight = 1;
+ peers->peer[i].max_fails = 1;
+ peers->peer[i].fail_timeout = 10;
+ }
+ }
+
+ rrp->peers = peers;
+ rrp->current = 0;
+
+ if (rrp->peers->number <= 8 * sizeof(uintptr_t)) {
+ rrp->tried = &rrp->data;
+ rrp->data = 0;
+
+ } else {
+ n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
+ / (8 * sizeof(uintptr_t));
+
+ rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t));
+ if (rrp->tried == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;
+ r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;
+ r->upstream->peer.tries = rrp->peers->number;
+#if (NGX_HTTP_SSL)
+ r->upstream->peer.set_session = ngx_http_upstream_empty_set_session;
+ r->upstream->peer.save_session = ngx_http_upstream_empty_save_session;
+#endif
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_http_upstream_rr_peer_data_t *rrp = data;
+
+ time_t now;
+ uintptr_t m;
+ ngx_int_t rc;
+ ngx_uint_t i, n;
+ ngx_connection_t *c;
+ ngx_http_upstream_rr_peer_t *peer;
+ ngx_http_upstream_rr_peers_t *peers;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get rr peer, try: %ui", pc->tries);
+
+ now = ngx_time();
+
+ /* ngx_lock_mutex(rrp->peers->mutex); */
+
+ if (rrp->peers->last_cached) {
+
+ /* cached connection */
+
+ c = rrp->peers->cached[rrp->peers->last_cached];
+ rrp->peers->last_cached--;
+
+ /* ngx_unlock_mutex(ppr->peers->mutex); */
+
+#if (NGX_THREADS)
+ c->read->lock = c->read->own_lock;
+ c->write->lock = c->write->own_lock;
+#endif
+
+ pc->connection = c;
+ pc->cached = 1;
+
+ return NGX_OK;
+ }
+
+ pc->cached = 0;
+ pc->connection = NULL;
+
+ if (rrp->peers->single) {
+ peer = &rrp->peers->peer[0];
+
+ } else {
+
+ /* there are several peers */
+
+ if (pc->tries == rrp->peers->number) {
+
+ /* it's a first try - get a current peer */
+
+ i = pc->tries;
+
+ for ( ;; ) {
+ rrp->current = ngx_http_upstream_get_peer(rrp->peers);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "get rr peer, current: %ui %i",
+ rrp->current,
+ rrp->peers->peer[rrp->current].current_weight);
+
+ n = rrp->current / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << rrp->current % (8 * sizeof(uintptr_t));
+
+ if (!(rrp->tried[n] & m)) {
+ peer = &rrp->peers->peer[rrp->current];
+
+ if (!peer->down) {
+
+ if (peer->max_fails == 0
+ || peer->fails < peer->max_fails)
+ {
+ break;
+ }
+
+ if (now - peer->accessed > peer->fail_timeout) {
+ peer->fails = 0;
+ break;
+ }
+
+ peer->current_weight = 0;
+
+ } else {
+ rrp->tried[n] |= m;
+ }
+
+ pc->tries--;
+ }
+
+ if (pc->tries == 0) {
+ goto failed;
+ }
+
+ if (--i == 0) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
+ "round robin upstream stuck on %ui tries",
+ pc->tries);
+ goto failed;
+ }
+ }
+
+ peer->current_weight--;
+
+ } else {
+
+ i = pc->tries;
+
+ for ( ;; ) {
+ n = rrp->current / (8 * sizeof(uintptr_t));
+ m = (uintptr_t) 1 << rrp->current % (8 * sizeof(uintptr_t));
+
+ if (!(rrp->tried[n] & m)) {
+
+ peer = &rrp->peers->peer[rrp->current];
+
+ if (!peer->down) {
+
+ if (peer->max_fails == 0
+ || peer->fails < peer->max_fails)
+ {
+ break;
+ }
+
+ if (now - peer->accessed > peer->fail_timeout) {
+ peer->fails = 0;
+ break;
+ }
+
+ peer->current_weight = 0;
+
+ } else {
+ rrp->tried[n] |= m;
+ }
+
+ pc->tries--;
+ }
+
+ rrp->current++;
+
+ if (rrp->current >= rrp->peers->number) {
+ rrp->current = 0;
+ }
+
+ if (pc->tries == 0) {
+ goto failed;
+ }
+
+ if (--i == 0) {
+ ngx_log_error(NGX_LOG_ALERT, pc->log, 0,
+ "round robin upstream stuck on %ui tries",
+ pc->tries);
+ goto failed;
+ }
+ }
+
+ peer->current_weight--;
+ }
+
+ rrp->tried[n] |= m;
+ }
+
+ pc->sockaddr = peer->sockaddr;
+ pc->socklen = peer->socklen;
+ pc->name = &peer->name;
+
+ /* ngx_unlock_mutex(rrp->peers->mutex); */
+
+ if (pc->tries == 1 && rrp->peers->next) {
+ pc->tries += rrp->peers->next->number;
+
+ n = rrp->peers->next->number / (8 * sizeof(uintptr_t)) + 1;
+ for (i = 0; i < n; i++) {
+ rrp->tried[i] = 0;
+ }
+ }
+
+ return NGX_OK;
+
+failed:
+
+ peers = rrp->peers;
+
+ if (peers->next) {
+
+ /* ngx_unlock_mutex(peers->mutex); */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers");
+
+ rrp->peers = peers->next;
+ pc->tries = rrp->peers->number;
+
+ n = rrp->peers->number / (8 * sizeof(uintptr_t)) + 1;
+ for (i = 0; i < n; i++) {
+ rrp->tried[i] = 0;
+ }
+
+ rc = ngx_http_upstream_get_round_robin_peer(pc, rrp);
+
+ if (rc != NGX_BUSY) {
+ return rc;
+ }
+
+ /* ngx_lock_mutex(peers->mutex); */
+ }
+
+ /* all peers failed, mark them as live for quick recovery */
+
+ for (i = 0; i < peers->number; i++) {
+ peers->peer[i].fails = 0;
+ }
+
+ /* ngx_unlock_mutex(peers->mutex); */
+
+ pc->name = peers->name;
+
+ return NGX_BUSY;
+}
+
+
+static ngx_uint_t
+ngx_http_upstream_get_peer(ngx_http_upstream_rr_peers_t *peers)
+{
+ ngx_uint_t i, n, reset = 0;
+ ngx_http_upstream_rr_peer_t *peer;
+
+ peer = &peers->peer[0];
+
+ for ( ;; ) {
+
+ for (i = 0; i < peers->number; i++) {
+
+ if (peer[i].current_weight <= 0) {
+ continue;
+ }
+
+ n = i;
+
+ while (i < peers->number - 1) {
+
+ i++;
+
+ if (peer[i].current_weight <= 0) {
+ continue;
+ }
+
+ if (peer[n].current_weight * 1000 / peer[i].current_weight
+ > peer[n].weight * 1000 / peer[i].weight)
+ {
+ return n;
+ }
+
+ n = i;
+ }
+
+ if (peer[i].current_weight > 0) {
+ n = i;
+ }
+
+ return n;
+ }
+
+ if (reset++) {
+ return 0;
+ }
+
+ for (i = 0; i < peers->number; i++) {
+ peer[i].current_weight = peer[i].weight;
+ }
+ }
+}
+
+
+void
+ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data,
+ ngx_uint_t state)
+{
+ ngx_http_upstream_rr_peer_data_t *rrp = data;
+
+ time_t now;
+ ngx_http_upstream_rr_peer_t *peer;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "free rr peer %ui %ui", pc->tries, state);
+
+ if (state == 0 && pc->tries == 0) {
+ return;
+ }
+
+ /* TODO: NGX_PEER_KEEPALIVE */
+
+ if (rrp->peers->single) {
+ pc->tries = 0;
+ return;
+ }
+
+ if (state & NGX_PEER_FAILED) {
+ now = ngx_time();
+
+ peer = &rrp->peers->peer[rrp->current];
+
+ /* ngx_lock_mutex(rrp->peers->mutex); */
+
+ peer->fails++;
+ peer->accessed = now;
+
+ if (peer->max_fails) {
+ peer->current_weight -= peer->weight / peer->max_fails;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "free rr peer failed: %ui %i",
+ rrp->current, peer->current_weight);
+
+ if (peer->current_weight < 0) {
+ peer->current_weight = 0;
+ }
+
+ /* ngx_unlock_mutex(rrp->peers->mutex); */
+ }
+
+ rrp->current++;
+
+ if (rrp->current >= rrp->peers->number) {
+ rrp->current = 0;
+ }
+
+ if (pc->tries) {
+ pc->tries--;
+ }
+
+ /* ngx_unlock_mutex(rrp->peers->mutex); */
+}
+
+
+#if (NGX_HTTP_SSL)
+
+ngx_int_t
+ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc,
+ void *data)
+{
+ ngx_http_upstream_rr_peer_data_t *rrp = data;
+
+ ngx_int_t rc;
+ ngx_ssl_session_t *ssl_session;
+ ngx_http_upstream_rr_peer_t *peer;
+
+ peer = &rrp->peers->peer[rrp->current];
+
+ /* TODO: threads only mutex */
+ /* ngx_lock_mutex(rrp->peers->mutex); */
+
+ ssl_session = peer->ssl_session;
+
+ rc = ngx_ssl_set_session(pc->connection, ssl_session);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "set session: %p:%d",
+ ssl_session, ssl_session ? ssl_session->references : 0);
+
+ /* ngx_unlock_mutex(rrp->peers->mutex); */
+
+ return rc;
+}
+
+
+void
+ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc,
+ void *data)
+{
+ ngx_http_upstream_rr_peer_data_t *rrp = data;
+
+ ngx_ssl_session_t *old_ssl_session, *ssl_session;
+ ngx_http_upstream_rr_peer_t *peer;
+
+ ssl_session = ngx_ssl_get_session(pc->connection);
+
+ if (ssl_session == NULL) {
+ return;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "save session: %p:%d", ssl_session, ssl_session->references);
+
+ peer = &rrp->peers->peer[rrp->current];
+
+ /* TODO: threads only mutex */
+ /* ngx_lock_mutex(rrp->peers->mutex); */
+
+ old_ssl_session = peer->ssl_session;
+ peer->ssl_session = ssl_session;
+
+ /* ngx_unlock_mutex(rrp->peers->mutex); */
+
+ if (old_ssl_session) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "old session: %p:%d",
+ old_ssl_session, old_ssl_session->references);
+
+ /* TODO: may block */
+
+ ngx_ssl_free_session(old_ssl_session);
+ }
+}
+
+
+static ngx_int_t
+ngx_http_upstream_empty_set_session(ngx_peer_connection_t *pc, void *data)
+{
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_upstream_empty_save_session(ngx_peer_connection_t *pc, void *data)
+{
+ return;
+}
+
+#endif
diff --git a/src/http/ngx_http_upstream_round_robin.h b/src/http/ngx_http_upstream_round_robin.h
new file mode 100644
index 0000000..6d285ab
--- /dev/null
+++ b/src/http/ngx_http_upstream_round_robin.h
@@ -0,0 +1,85 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+#define _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_
+
+
+#include
+#include
+#include
+
+
+typedef struct {
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+ ngx_str_t name;
+
+ ngx_int_t current_weight;
+ ngx_int_t weight;
+
+ ngx_uint_t fails;
+ time_t accessed;
+
+ ngx_uint_t max_fails;
+ time_t fail_timeout;
+
+ ngx_uint_t down; /* unsigned down:1; */
+
+#if (NGX_HTTP_SSL)
+ ngx_ssl_session_t *ssl_session; /* local to a process */
+#endif
+} ngx_http_upstream_rr_peer_t;
+
+
+typedef struct ngx_http_upstream_rr_peers_s ngx_http_upstream_rr_peers_t;
+
+struct ngx_http_upstream_rr_peers_s {
+ ngx_uint_t single; /* unsigned single:1; */
+ ngx_uint_t number;
+ ngx_uint_t last_cached;
+
+ /* ngx_mutex_t *mutex; */
+ ngx_connection_t **cached;
+
+ ngx_str_t *name;
+
+ ngx_http_upstream_rr_peers_t *next;
+
+ ngx_http_upstream_rr_peer_t peer[1];
+};
+
+
+typedef struct {
+ ngx_http_upstream_rr_peers_t *peers;
+ ngx_uint_t current;
+ uintptr_t *tried;
+ uintptr_t data;
+} ngx_http_upstream_rr_peer_data_t;
+
+
+ngx_int_t ngx_http_upstream_init_round_robin(ngx_conf_t *cf,
+ ngx_http_upstream_srv_conf_t *us);
+ngx_int_t ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *us);
+ngx_int_t ngx_http_upstream_create_round_robin_peer(ngx_http_request_t *r,
+ ngx_http_upstream_resolved_t *ur);
+ngx_int_t ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc,
+ void *data);
+void ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc,
+ void *data, ngx_uint_t state);
+
+#if (NGX_HTTP_SSL)
+ngx_int_t
+ ngx_http_upstream_set_round_robin_peer_session(ngx_peer_connection_t *pc,
+ void *data);
+void ngx_http_upstream_save_round_robin_peer_session(ngx_peer_connection_t *pc,
+ void *data);
+#endif
+
+
+#endif /* _NGX_HTTP_UPSTREAM_ROUND_ROBIN_H_INCLUDED_ */
diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c
new file mode 100644
index 0000000..a9eb0af
--- /dev/null
+++ b/src/http/ngx_http_variables.c
@@ -0,0 +1,2053 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+
+
+static ngx_int_t ngx_http_variable_request(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static void ngx_http_variable_request_set(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_get_size(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static void ngx_http_variable_request_set_size(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_header(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_headers(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_line(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_cookie(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_argument(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_variable_host(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_binary_remote_addr(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_remote_addr(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_remote_port(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_scheme(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_is_args(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_document_root(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_realpath_root(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_filename(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_server_name(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_method(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_remote_user(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_body_bytes_sent(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_completion(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_body(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_request_body_file(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_variable_sent_content_type(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_content_length(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_location(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_last_modified(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_connection(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_keep_alive(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+static ngx_int_t ngx_http_variable_nginx_version(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_hostname(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t ngx_http_variable_pid(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+/*
+ * TODO:
+ * Apache CGI: AUTH_TYPE, PATH_INFO (null), PATH_TRANSLATED
+ * REMOTE_HOST (null), REMOTE_IDENT (null),
+ * SERVER_SOFTWARE
+ *
+ * Apache SSI: DOCUMENT_NAME, LAST_MODIFIED, USER_NAME (file owner)
+ */
+
+/*
+ * the $http_host, $http_user_agent, $http_referer, $http_via,
+ * and $http_x_forwarded_for variables may be handled by generic
+ * ngx_http_variable_unknown_header_in(), but for perfomance reasons
+ * they are handled using dedicated entries
+ */
+
+static ngx_http_variable_t ngx_http_core_variables[] = {
+
+ { ngx_string("http_host"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.host), 0, 0 },
+
+ { ngx_string("http_user_agent"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.user_agent), 0, 0 },
+
+ { ngx_string("http_referer"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.referer), 0, 0 },
+
+#if (NGX_HTTP_GZIP)
+ { ngx_string("http_via"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.via), 0, 0 },
+#endif
+
+#if (NGX_HTTP_PROXY || NGX_HTTP_REALIP)
+ { ngx_string("http_x_forwarded_for"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.x_forwarded_for), 0, 0 },
+#endif
+
+ { ngx_string("http_cookie"), NULL, ngx_http_variable_headers,
+ offsetof(ngx_http_request_t, headers_in.cookies), 0, 0 },
+
+ { ngx_string("content_length"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.content_length), 0, 0 },
+
+ { ngx_string("content_type"), NULL, ngx_http_variable_header,
+ offsetof(ngx_http_request_t, headers_in.content_type), 0, 0 },
+
+ { ngx_string("host"), NULL, ngx_http_variable_host, 0, 0, 0 },
+
+ { ngx_string("binary_remote_addr"), NULL,
+ ngx_http_variable_binary_remote_addr, 0, 0, 0 },
+
+ { ngx_string("remote_addr"), NULL, ngx_http_variable_remote_addr, 0, 0, 0 },
+
+ { ngx_string("remote_port"), NULL, ngx_http_variable_remote_port, 0, 0, 0 },
+
+ { ngx_string("server_addr"), NULL, ngx_http_variable_server_addr, 0, 0, 0 },
+
+ { ngx_string("server_port"), NULL, ngx_http_variable_server_port, 0, 0, 0 },
+
+ { ngx_string("server_protocol"), NULL, ngx_http_variable_request,
+ offsetof(ngx_http_request_t, http_protocol), 0, 0 },
+
+ { ngx_string("scheme"), NULL, ngx_http_variable_scheme, 0, 0, 0 },
+
+ { ngx_string("request_uri"), NULL, ngx_http_variable_request,
+ offsetof(ngx_http_request_t, unparsed_uri), 0, 0 },
+
+ { ngx_string("uri"), NULL, ngx_http_variable_request,
+ offsetof(ngx_http_request_t, uri),
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("document_uri"), NULL, ngx_http_variable_request,
+ offsetof(ngx_http_request_t, uri),
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("request"), NULL, ngx_http_variable_request_line, 0, 0, 0 },
+
+ { ngx_string("document_root"), NULL,
+ ngx_http_variable_document_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("realpath_root"), NULL,
+ ngx_http_variable_realpath_root, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("query_string"), NULL, ngx_http_variable_request,
+ offsetof(ngx_http_request_t, args),
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("args"),
+ ngx_http_variable_request_set,
+ ngx_http_variable_request,
+ offsetof(ngx_http_request_t, args),
+ NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("is_args"), NULL, ngx_http_variable_is_args,
+ 0, NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("request_filename"), NULL,
+ ngx_http_variable_request_filename, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("server_name"), NULL, ngx_http_variable_server_name, 0, 0, 0 },
+
+ { ngx_string("request_method"), NULL,
+ ngx_http_variable_request_method, 0,
+ NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("remote_user"), NULL, ngx_http_variable_remote_user, 0, 0, 0 },
+
+ { ngx_string("body_bytes_sent"), NULL, ngx_http_variable_body_bytes_sent,
+ 0, 0, 0 },
+
+ { ngx_string("request_completion"), NULL,
+ ngx_http_variable_request_completion,
+ 0, 0, 0 },
+
+ { ngx_string("request_body"), NULL,
+ ngx_http_variable_request_body,
+ 0, 0, 0 },
+
+ { ngx_string("request_body_file"), NULL,
+ ngx_http_variable_request_body_file,
+ 0, 0, 0 },
+
+ { ngx_string("sent_http_content_type"), NULL,
+ ngx_http_variable_sent_content_type, 0, 0, 0 },
+
+ { ngx_string("sent_http_content_length"), NULL,
+ ngx_http_variable_sent_content_length, 0, 0, 0 },
+
+ { ngx_string("sent_http_location"), NULL,
+ ngx_http_variable_sent_location, 0, 0, 0 },
+
+ { ngx_string("sent_http_last_modified"), NULL,
+ ngx_http_variable_sent_last_modified, 0, 0, 0 },
+
+ { ngx_string("sent_http_connection"), NULL,
+ ngx_http_variable_sent_connection, 0, 0, 0 },
+
+ { ngx_string("sent_http_keep_alive"), NULL,
+ ngx_http_variable_sent_keep_alive, 0, 0, 0 },
+
+ { ngx_string("sent_http_transfer_encoding"), NULL,
+ ngx_http_variable_sent_transfer_encoding, 0, 0, 0 },
+
+ { ngx_string("sent_http_cache_control"), NULL, ngx_http_variable_headers,
+ offsetof(ngx_http_request_t, headers_out.cache_control), 0, 0 },
+
+ { ngx_string("limit_rate"), ngx_http_variable_request_set_size,
+ ngx_http_variable_request_get_size,
+ offsetof(ngx_http_request_t, limit_rate),
+ NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_NOCACHEABLE, 0 },
+
+ { ngx_string("nginx_version"), NULL, ngx_http_variable_nginx_version,
+ 0, 0, 0 },
+
+ { ngx_string("hostname"), NULL, ngx_http_variable_hostname,
+ 0, 0, 0 },
+
+ { ngx_string("pid"), NULL, ngx_http_variable_pid,
+ 0, 0, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+
+ngx_http_variable_value_t ngx_http_variable_null_value =
+ ngx_http_variable("");
+ngx_http_variable_value_t ngx_http_variable_true_value =
+ ngx_http_variable("1");
+
+
+ngx_http_variable_t *
+ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags)
+{
+ ngx_int_t rc;
+ ngx_uint_t i;
+ ngx_hash_key_t *key;
+ ngx_http_variable_t *v;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ key = cmcf->variables_keys->keys.elts;
+ for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {
+ if (name->len != key[i].key.len
+ || ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0)
+ {
+ continue;
+ }
+
+ v = key[i].value;
+
+ if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the duplicate \"%V\" variable", name);
+ return NULL;
+ }
+
+ return v;
+ }
+
+ v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));
+ if (v == NULL) {
+ return NULL;
+ }
+
+ v->name.len = name->len;
+ v->name.data = ngx_pnalloc(cf->pool, name->len);
+ if (v->name.data == NULL) {
+ return NULL;
+ }
+
+ ngx_strlow(v->name.data, name->data, name->len);
+
+ v->set_handler = NULL;
+ v->get_handler = NULL;
+ v->data = 0;
+ v->flags = flags;
+ v->index = 0;
+
+ rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);
+
+ if (rc == NGX_ERROR) {
+ return NULL;
+ }
+
+ if (rc == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting variable name \"%V\"", name);
+ return NULL;
+ }
+
+ return v;
+}
+
+
+ngx_int_t
+ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name)
+{
+ ngx_uint_t i;
+ ngx_http_variable_t *v;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ v = cmcf->variables.elts;
+
+ if (v == NULL) {
+ if (ngx_array_init(&cmcf->variables, cf->pool, 4,
+ sizeof(ngx_http_variable_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ } else {
+ for (i = 0; i < cmcf->variables.nelts; i++) {
+ if (name->len != v[i].name.len
+ || ngx_strncasecmp(name->data, v[i].name.data, name->len) != 0)
+ {
+ continue;
+ }
+
+ return i;
+ }
+ }
+
+ v = ngx_array_push(&cmcf->variables);
+ if (v == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->name.len = name->len;
+ v->name.data = ngx_pnalloc(cf->pool, name->len);
+ if (v->name.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_strlow(v->name.data, name->data, name->len);
+
+ v->set_handler = NULL;
+ v->get_handler = NULL;
+ v->data = 0;
+ v->flags = 0;
+ v->index = cmcf->variables.nelts - 1;
+
+ return cmcf->variables.nelts - 1;
+}
+
+
+ngx_http_variable_value_t *
+ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index)
+{
+ ngx_http_variable_t *v;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ if (cmcf->variables.nelts <= index) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ "unknown variable index: %d", index);
+ return NULL;
+ }
+
+ if (r->variables[index].not_found || r->variables[index].valid) {
+ return &r->variables[index];
+ }
+
+ v = cmcf->variables.elts;
+
+ if (v[index].get_handler(r, &r->variables[index], v[index].data)
+ == NGX_OK)
+ {
+ if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) {
+ r->variables[index].no_cacheable = 1;
+ }
+
+ return &r->variables[index];
+ }
+
+ r->variables[index].valid = 0;
+ r->variables[index].not_found = 1;
+
+ return NULL;
+}
+
+
+ngx_http_variable_value_t *
+ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index)
+{
+ ngx_http_variable_value_t *v;
+
+ v = &r->variables[index];
+
+ if (v->valid || v->not_found) {
+ if (!v->no_cacheable) {
+ return v;
+ }
+
+ v->valid = 0;
+ v->not_found = 0;
+ }
+
+ return ngx_http_get_indexed_variable(r, index);
+}
+
+
+ngx_http_variable_value_t *
+ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key)
+{
+ ngx_http_variable_t *v;
+ ngx_http_variable_value_t *vv;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ v = ngx_hash_find(&cmcf->variables_hash, key, name->data, name->len);
+
+ if (v) {
+ if (v->flags & NGX_HTTP_VAR_INDEXED) {
+ return ngx_http_get_flushed_variable(r, v->index);
+
+ } else {
+
+ vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
+
+ if (vv && v->get_handler(r, vv, v->data) == NGX_OK) {
+ return vv;
+ }
+
+ return NULL;
+ }
+ }
+
+ vv = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
+ if (vv == NULL) {
+ return NULL;
+ }
+
+ if (ngx_strncmp(name->data, "http_", 5) == 0) {
+
+ if (ngx_http_variable_unknown_header_in(r, vv, (uintptr_t) name)
+ == NGX_OK)
+ {
+ return vv;
+ }
+
+ return NULL;
+ }
+
+ if (ngx_strncmp(name->data, "sent_http_", 10) == 0) {
+
+ if (ngx_http_variable_unknown_header_out(r, vv, (uintptr_t) name)
+ == NGX_OK)
+ {
+ return vv;
+ }
+
+ return NULL;
+ }
+
+ if (ngx_strncmp(name->data, "upstream_http_", 14) == 0) {
+
+ if (ngx_http_upstream_header_variable(r, vv, (uintptr_t) name)
+ == NGX_OK)
+ {
+ return vv;
+ }
+
+ return NULL;
+ }
+
+ if (ngx_strncmp(name->data, "cookie_", 7) == 0) {
+
+ if (ngx_http_variable_cookie(r, vv, (uintptr_t) name) == NGX_OK) {
+ return vv;
+ }
+
+ return NULL;
+ }
+
+ if (ngx_strncmp(name->data, "arg_", 4) == 0) {
+
+ if (ngx_http_variable_argument(r, vv, (uintptr_t) name) == NGX_OK) {
+ return vv;
+ }
+
+ return NULL;
+ }
+
+ vv->not_found = 1;
+
+ return vv;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_str_t *s;
+
+ s = (ngx_str_t *) ((char *) r + data);
+
+ if (s->data) {
+ v->len = s->len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = s->data;
+
+ } else {
+ v->not_found = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_variable_request_set(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_str_t *s;
+
+ s = (ngx_str_t *) ((char *) r + data);
+
+ s->len = v->len;
+ s->data = v->data;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_get_size(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ size_t *sp;
+
+ sp = (size_t *) ((char *) r + data);
+
+ v->data = ngx_pnalloc(r->pool, NGX_SIZE_T_LEN);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%uz", *sp) - v->data;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_http_variable_request_set_size(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ssize_t s, *sp;
+ ngx_str_t val;
+
+ val.len = v->len;
+ val.data = v->data;
+
+ s = ngx_parse_size(&val);
+
+ if (s == NGX_ERROR) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "invalid size \"%V\"", &val);
+ return;
+ }
+
+ sp = (ssize_t *) ((char *) r + data);
+
+ *sp = s;
+
+ return;
+}
+
+
+static ngx_int_t
+ngx_http_variable_header(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_table_elt_t *h;
+
+ h = *(ngx_table_elt_t **) ((char *) r + data);
+
+ if (h) {
+ v->len = h->value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = h->value.data;
+
+ } else {
+ v->not_found = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_headers(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ size_t len;
+ u_char *p, *end;
+ ngx_uint_t i, n;
+ ngx_array_t *a;
+ ngx_table_elt_t **h;
+
+ a = (ngx_array_t *) ((char *) r + data);
+
+ n = a->nelts;
+ h = a->elts;
+
+ len = 0;
+
+ for (i = 0; i < n; i++) {
+
+ if (h[i]->hash == 0) {
+ continue;
+ }
+
+ len += h[i]->value.len + sizeof("; ") - 1;
+ }
+
+ if (len == 0) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ len -= sizeof("; ") - 1;
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (n == 1) {
+ v->len = (*h)->value.len;
+ v->data = (*h)->value.data;
+
+ return NGX_OK;
+ }
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = len;
+ v->data = p;
+
+ end = p + len;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (h[i]->hash == 0) {
+ continue;
+ }
+
+ p = ngx_copy(p, h[i]->value.data, h[i]->value.len);
+
+ if (p == end) {
+ break;
+ }
+
+ *p++ = ';'; *p++ = ' ';
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_unknown_header_in(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+ &r->headers_in.headers.part,
+ sizeof("http_") - 1);
+}
+
+
+static ngx_int_t
+ngx_http_variable_unknown_header_out(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ return ngx_http_variable_unknown_header(v, (ngx_str_t *) data,
+ &r->headers_out.headers.part,
+ sizeof("sent_http_") - 1);
+}
+
+
+ngx_int_t
+ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var,
+ ngx_list_part_t *part, size_t prefix)
+{
+ u_char ch;
+ ngx_uint_t i, n;
+ ngx_table_elt_t *header;
+
+ header = part->elts;
+
+ for (i = 0; /* void */ ; i++) {
+
+ if (i >= part->nelts) {
+ if (part->next == NULL) {
+ break;
+ }
+
+ part = part->next;
+ header = part->elts;
+ i = 0;
+ }
+
+ if (header[i].hash == 0) {
+ continue;
+ }
+
+ for (n = 0; n + prefix < var->len && n < header[i].key.len; n++) {
+ ch = header[i].key.data[n];
+
+ if (ch >= 'A' && ch <= 'Z') {
+ ch |= 0x20;
+
+ } else if (ch == '-') {
+ ch = '_';
+ }
+
+ if (var->data[n + prefix] != ch) {
+ break;
+ }
+ }
+
+ if (n + prefix == var->len && n == header[i].key.len) {
+ v->len = header[i].value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = header[i].value.data;
+
+ return NGX_OK;
+ }
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_line(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p, *s;
+
+ s = r->request_line.data;
+
+ if (s == NULL) {
+ s = r->request_start;
+
+ if (s == NULL) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ for (p = s; p < r->header_in->last; p++) {
+ if (*p == CR || *p == LF) {
+ break;
+ }
+ }
+
+ r->request_line.len = p - s;
+ r->request_line.data = s;
+ }
+
+ v->len = r->request_line.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = s;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_cookie(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_str_t *name = (ngx_str_t *) data;
+
+ ngx_str_t cookie, s;
+
+ s.len = name->len - (sizeof("cookie_") - 1);
+ s.data = name->data + sizeof("cookie_") - 1;
+
+ if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &s, &cookie)
+ == NGX_DECLINED)
+ {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->len = cookie.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = cookie.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_argument(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_str_t *name = (ngx_str_t *) data;
+
+ u_char *arg;
+ size_t len;
+ ngx_str_t value;
+
+ len = name->len - (sizeof("arg_") - 1);
+ arg = name->data + sizeof("arg_") - 1;
+
+ if (ngx_http_arg(r, arg, len, &value) != NGX_OK) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ v->data = value.data;
+ v->len = value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_host(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_core_srv_conf_t *cscf;
+
+ if (r->headers_in.server.len) {
+ v->len = r->headers_in.server.len;
+ v->data = r->headers_in.server.data;
+
+ } else {
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ v->len = cscf->server_name.len;
+ v->data = cscf->server_name.data;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_binary_remote_addr(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ switch (r->connection->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+
+ v->len = sizeof(struct in6_addr);
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = sin6->sin6_addr.s6_addr;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) r->connection->sockaddr;
+
+ v->len = sizeof(in_addr_t);
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) &sin->sin_addr;
+
+ break;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_remote_addr(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ v->len = r->connection->addr_text.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->connection->addr_text.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_remote_port(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_uint_t port;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ v->len = 0;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ switch (r->connection->sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
+ port = ntohs(sin6->sin6_port);
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) r->connection->sockaddr;
+ port = ntohs(sin->sin_port);
+ break;
+ }
+
+ if (port > 0 && port < 65536) {
+ v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_server_addr(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_str_t s;
+ u_char addr[NGX_SOCKADDR_STRLEN];
+
+ s.len = NGX_SOCKADDR_STRLEN;
+ s.data = addr;
+
+ if (ngx_connection_local_sockaddr(r->connection, &s, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ s.data = ngx_pnalloc(r->pool, s.len);
+ if (s.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s.data, addr, s.len);
+
+ v->len = s.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = s.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_server_port(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_uint_t port;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ v->len = 0;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (ngx_connection_local_sockaddr(r->connection, NULL, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ v->data = ngx_pnalloc(r->pool, sizeof("65535") - 1);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ switch (r->connection->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) r->connection->local_sockaddr;
+ port = ntohs(sin6->sin6_port);
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) r->connection->local_sockaddr;
+ port = ntohs(sin->sin_port);
+ break;
+ }
+
+ if (port > 0 && port < 65536) {
+ v->len = ngx_sprintf(v->data, "%ui", port) - v->data;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_scheme(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+#if (NGX_HTTP_SSL)
+
+ if (r->connection->ssl) {
+ v->len = sizeof("https") - 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) "https";
+
+ return NGX_OK;
+ }
+
+#endif
+
+ v->len = sizeof("http") - 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) "http";
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_is_args(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ if (r->args.len == 0) {
+ v->len = 0;
+ v->data = NULL;
+ return NGX_OK;
+ }
+
+ v->len = 1;
+ v->data = (u_char *) "?";
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_document_root(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_str_t path;
+ ngx_http_core_loc_conf_t *clcf;
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->root_lengths == NULL) {
+ v->len = clcf->root.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = clcf->root.data;
+
+ } else {
+ if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 0,
+ clcf->root_values->elts)
+ == NULL)
+ {
+ return NGX_ERROR;
+ }
+
+ if (ngx_conf_full_name((ngx_cycle_t *) ngx_cycle, &path, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+
+ v->len = path.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = path.data;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_realpath_root(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ size_t len;
+ ngx_str_t path;
+ ngx_http_core_loc_conf_t *clcf;
+ u_char real[NGX_MAX_PATH];
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->root_lengths == NULL) {
+ path = clcf->root;
+
+ } else {
+ if (ngx_http_script_run(r, &path, clcf->root_lengths->elts, 1,
+ clcf->root_values->elts)
+ == NULL)
+ {
+ return NGX_ERROR;
+ }
+
+ path.data[path.len - 1] = '\0';
+
+ if (ngx_conf_full_name((ngx_cycle_t *) ngx_cycle, &path, 0) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_realpath(path.data, real) == NULL) {
+ ngx_log_error(NGX_LOG_CRIT, r->connection->log, ngx_errno,
+ ngx_realpath_n " \"%s\" failed", path.data);
+ return NGX_ERROR;
+ }
+
+ len = ngx_strlen(real);
+
+ v->data = ngx_pnalloc(r->pool, len);
+ if (v->data == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ ngx_memcpy(v->data, real, len);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_filename(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ size_t root;
+ ngx_str_t path;
+
+ if (ngx_http_map_uri_to_path(r, &path, &root, 0) == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* ngx_http_map_uri_to_path() allocates memory for terminating '\0' */
+
+ v->len = path.len - 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = path.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_server_name(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_http_core_srv_conf_t *cscf;
+
+ cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
+
+ v->len = cscf->server_name.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = cscf->server_name.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_method(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->main->method_name.data) {
+ v->len = r->main->method_name.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->main->method_name.data;
+
+ } else {
+ v->not_found = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_remote_user(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_int_t rc;
+
+ rc = ngx_http_auth_basic_user(r);
+
+ if (rc == NGX_DECLINED) {
+ v->not_found = 1;
+ return NGX_OK;
+ }
+
+ if (rc == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ v->len = r->headers_in.user.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->headers_in.user.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_body_bytes_sent(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ off_t sent;
+ u_char *p;
+
+ sent = r->connection->sent - r->header_size;
+
+ if (sent < 0) {
+ sent = 0;
+ }
+
+ p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(p, "%O", sent) - p;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = p;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_content_type(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->headers_out.content_type.len) {
+ v->len = r->headers_out.content_type.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->headers_out.content_type.data;
+
+ } else {
+ v->not_found = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_content_length(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+
+ if (r->headers_out.content_length) {
+ v->len = r->headers_out.content_length->value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->headers_out.content_length->value.data;
+
+ return NGX_OK;
+ }
+
+ if (r->headers_out.content_length_n >= 0) {
+ p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(p, "%O", r->headers_out.content_length_n) - p;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = p;
+
+ return NGX_OK;
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_location(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_str_t name;
+
+ if (r->headers_out.location) {
+ v->len = r->headers_out.location->value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->headers_out.location->value.data;
+
+ return NGX_OK;
+ }
+
+ ngx_str_set(&name, "sent_http_location");
+
+ return ngx_http_variable_unknown_header(v, &name,
+ &r->headers_out.headers.part,
+ sizeof("sent_http_") - 1);
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_last_modified(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+
+ if (r->headers_out.last_modified) {
+ v->len = r->headers_out.last_modified->value.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->headers_out.last_modified->value.data;
+
+ return NGX_OK;
+ }
+
+ if (r->headers_out.last_modified_time >= 0) {
+ p = ngx_pnalloc(r->pool,
+ sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT") - 1);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_http_time(p, r->headers_out.last_modified_time) - p;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = p;
+
+ return NGX_OK;
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_connection(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ size_t len;
+ char *p;
+
+ if (r->keepalive) {
+ len = sizeof("keep-alive") - 1;
+ p = "keep-alive";
+
+ } else {
+ len = sizeof("close") - 1;
+ p = "close";
+ }
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) p;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_keep_alive(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ ngx_http_core_loc_conf_t *clcf;
+
+ if (r->keepalive) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ if (clcf->keepalive_header) {
+
+ p = ngx_pnalloc(r->pool, sizeof("timeout=") - 1 + NGX_TIME_T_LEN);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(p, "timeout=%T", clcf->keepalive_header) - p;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = p;
+
+ return NGX_OK;
+ }
+ }
+
+ v->not_found = 1;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_sent_transfer_encoding(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->chunked) {
+ v->len = sizeof("chunked") - 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) "chunked";
+
+ } else {
+ v->not_found = 1;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_completion(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->request_complete) {
+ v->len = 2;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) "OK";
+
+ return NGX_OK;
+ }
+
+ v->len = 0;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) "";
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_body(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+ size_t len;
+ ngx_buf_t *buf, *next;
+ ngx_chain_t *cl;
+
+ if (r->request_body == NULL
+ || r->request_body->bufs == NULL
+ || r->request_body->temp_file)
+ {
+ v->not_found = 1;
+
+ return NGX_OK;
+ }
+
+ cl = r->request_body->bufs;
+ buf = cl->buf;
+
+ if (cl->next == NULL) {
+ v->len = buf->last - buf->pos;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = buf->pos;
+
+ return NGX_OK;
+ }
+
+ next = cl->next->buf;
+ len = (buf->last - buf->pos) + (next->last - next->pos);
+
+ p = ngx_pnalloc(r->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->data = p;
+
+ p = ngx_cpymem(p, buf->pos, buf->last - buf->pos);
+ ngx_memcpy(p, next->pos, next->last - next->pos);
+
+ v->len = len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_request_body_file(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ if (r->request_body == NULL || r->request_body->temp_file == NULL) {
+ v->not_found = 1;
+
+ return NGX_OK;
+ }
+
+ v->len = r->request_body->temp_file->file.name.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = r->request_body->temp_file->file.name.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_nginx_version(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ v->len = sizeof(NGINX_VERSION) - 1;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = (u_char *) NGINX_VERSION;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_hostname(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ v->len = ngx_cycle->hostname.len;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = ngx_cycle->hostname.data;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_variable_pid(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ u_char *p;
+
+ p = ngx_pnalloc(r->pool, NGX_INT64_LEN);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(p, "%P", ngx_pid) - p;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->data = p;
+
+ return NGX_OK;
+}
+
+
+void *
+ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map, ngx_str_t *match)
+{
+ void *value;
+ u_char *low;
+ size_t len;
+ ngx_uint_t key;
+
+ len = match->len;
+
+ if (len) {
+ low = ngx_pnalloc(r->pool, len);
+ if (low == NULL) {
+ return NULL;
+ }
+
+ } else {
+ low = NULL;
+ }
+
+ key = ngx_hash_strlow(low, match->data, len);
+
+ value = ngx_hash_find_combined(&map->hash, key, low, len);
+ if (value) {
+ return value;
+ }
+
+#if (NGX_PCRE)
+
+ if (len && map->nregex) {
+ ngx_int_t n;
+ ngx_uint_t i;
+ ngx_http_map_regex_t *reg;
+
+ reg = map->regex;
+
+ for (i = 0; i < map->nregex; i++) {
+
+ n = ngx_http_regex_exec(r, reg[i].regex, match);
+
+ if (n == NGX_OK) {
+ return reg[i].value;
+ }
+
+ if (n == NGX_DECLINED) {
+ continue;
+ }
+
+ /* NGX_ERROR */
+
+ return NULL;
+ }
+ }
+
+#endif
+
+ return NULL;
+}
+
+
+#if (NGX_PCRE)
+
+static ngx_int_t
+ngx_http_variable_not_found(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ v->not_found = 1;
+ return NGX_OK;
+}
+
+
+ngx_http_regex_t *
+ngx_http_regex_compile(ngx_conf_t *cf, ngx_regex_compile_t *rc)
+{
+ u_char *p;
+ size_t size;
+ ngx_str_t name;
+ ngx_uint_t i, n;
+ ngx_http_variable_t *v;
+ ngx_http_regex_t *re;
+ ngx_http_regex_variable_t *rv;
+ ngx_http_core_main_conf_t *cmcf;
+
+ rc->pool = cf->pool;
+
+ if (ngx_regex_compile(rc) != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V", &rc->err);
+ return NULL;
+ }
+
+ re = ngx_pcalloc(cf->pool, sizeof(ngx_http_regex_t));
+ if (re == NULL) {
+ return NULL;
+ }
+
+ re->regex = rc->regex;
+ re->ncaptures = rc->captures;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+ cmcf->ncaptures = ngx_max(cmcf->ncaptures, re->ncaptures);
+
+ n = (ngx_uint_t) rc->named_captures;
+
+ if (n == 0) {
+ return re;
+ }
+
+ rv = ngx_palloc(rc->pool, n * sizeof(ngx_http_regex_variable_t));
+ if (rv == NULL) {
+ return NULL;
+ }
+
+ re->variables = rv;
+ re->nvariables = n;
+ re->name = rc->pattern;
+
+ size = rc->name_size;
+ p = rc->names;
+
+ for (i = 0; i < n; i++) {
+ rv[i].capture = 2 * ((p[0] << 8) + p[1]);
+
+ name.data = &p[2];
+ name.len = ngx_strlen(name.data);
+
+ v = ngx_http_add_variable(cf, &name, NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
+ return NULL;
+ }
+
+ rv[i].index = ngx_http_get_variable_index(cf, &name);
+ if (rv[i].index == NGX_ERROR) {
+ return NULL;
+ }
+
+ v->get_handler = ngx_http_variable_not_found;
+
+ p += size;
+ }
+
+ return re;
+}
+
+
+ngx_int_t
+ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re, ngx_str_t *s)
+{
+ ngx_int_t rc, index;
+ ngx_uint_t i, n, len;
+ ngx_http_variable_value_t *vv;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ if (re->ncaptures) {
+ len = cmcf->ncaptures;
+
+ if (r->captures == NULL) {
+ r->captures = ngx_palloc(r->pool, len * sizeof(int));
+ if (r->captures == NULL) {
+ return NGX_ERROR;
+ }
+ }
+
+ } else {
+ len = 0;
+ }
+
+ rc = ngx_regex_exec(re->regex, s, r->captures, len);
+
+ if (rc == NGX_REGEX_NO_MATCHED) {
+ return NGX_DECLINED;
+ }
+
+ if (rc < 0) {
+ ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+ ngx_regex_exec_n " failed: %i on \"%V\" using \"%V\"",
+ rc, s, &re->name);
+ return NGX_ERROR;
+ }
+
+ for (i = 0; i < re->nvariables; i++) {
+
+ n = re->variables[i].capture;
+ index = re->variables[i].index;
+ vv = &r->variables[index];
+
+ vv->len = r->captures[n + 1] - r->captures[n];
+ vv->valid = 1;
+ vv->no_cacheable = 0;
+ vv->not_found = 0;
+ vv->data = &s->data[r->captures[n]];
+
+#if (NGX_DEBUG)
+ {
+ ngx_http_variable_t *v;
+
+ v = cmcf->variables.elts;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http regex set $%V to \"%*s\"",
+ &v[index].name, vv->len, vv->data);
+ }
+#endif
+ }
+
+ r->ncaptures = rc * 2;
+ r->captures_data = s->data;
+
+ return NGX_OK;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_http_variables_add_core_vars(ngx_conf_t *cf)
+{
+ ngx_int_t rc;
+ ngx_http_variable_t *v;
+ ngx_http_core_main_conf_t *cmcf;
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,
+ sizeof(ngx_hash_keys_arrays_t));
+ if (cmcf->variables_keys == NULL) {
+ return NGX_ERROR;
+ }
+
+ cmcf->variables_keys->pool = cf->pool;
+ cmcf->variables_keys->temp_pool = cf->pool;
+
+ if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ for (v = ngx_http_core_variables; v->name.len; v++) {
+ rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v,
+ NGX_HASH_READONLY_KEY);
+
+ if (rc == NGX_OK) {
+ continue;
+ }
+
+ if (rc == NGX_BUSY) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "conflicting variable name \"%V\"", &v->name);
+ }
+
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_http_variables_init_vars(ngx_conf_t *cf)
+{
+ ngx_uint_t i, n;
+ ngx_hash_key_t *key;
+ ngx_hash_init_t hash;
+ ngx_http_variable_t *v, *av;
+ ngx_http_core_main_conf_t *cmcf;
+
+ /* set the handlers for the indexed http variables */
+
+ cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
+
+ v = cmcf->variables.elts;
+ key = cmcf->variables_keys->keys.elts;
+
+ for (i = 0; i < cmcf->variables.nelts; i++) {
+
+ for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+
+ av = key[n].value;
+
+ if (av->get_handler
+ && v[i].name.len == key[n].key.len
+ && ngx_strncmp(v[i].name.data, key[n].key.data, v[i].name.len)
+ == 0)
+ {
+ v[i].get_handler = av->get_handler;
+ v[i].data = av->data;
+
+ av->flags |= NGX_HTTP_VAR_INDEXED;
+ v[i].flags = av->flags;
+
+ av->index = i;
+
+ goto next;
+ }
+ }
+
+ if (ngx_strncmp(v[i].name.data, "http_", 5) == 0) {
+ v[i].get_handler = ngx_http_variable_unknown_header_in;
+ v[i].data = (uintptr_t) &v[i].name;
+
+ continue;
+ }
+
+ if (ngx_strncmp(v[i].name.data, "sent_http_", 10) == 0) {
+ v[i].get_handler = ngx_http_variable_unknown_header_out;
+ v[i].data = (uintptr_t) &v[i].name;
+
+ continue;
+ }
+
+ if (ngx_strncmp(v[i].name.data, "upstream_http_", 14) == 0) {
+ v[i].get_handler = ngx_http_upstream_header_variable;
+ v[i].data = (uintptr_t) &v[i].name;
+ v[i].flags = NGX_HTTP_VAR_NOCACHEABLE;
+
+ continue;
+ }
+
+ if (ngx_strncmp(v[i].name.data, "cookie_", 7) == 0) {
+ v[i].get_handler = ngx_http_variable_cookie;
+ v[i].data = (uintptr_t) &v[i].name;
+
+ continue;
+ }
+
+ if (ngx_strncmp(v[i].name.data, "arg_", 4) == 0) {
+ v[i].get_handler = ngx_http_variable_argument;
+ v[i].data = (uintptr_t) &v[i].name;
+ v[i].flags = NGX_HTTP_VAR_NOCACHEABLE;
+
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "unknown \"%V\" variable", &v[i].name);
+
+ return NGX_ERROR;
+
+ next:
+ continue;
+ }
+
+
+ for (n = 0; n < cmcf->variables_keys->keys.nelts; n++) {
+ av = key[n].value;
+
+ if (av->flags & NGX_HTTP_VAR_NOHASH) {
+ key[n].key.data = NULL;
+ }
+ }
+
+
+ hash.hash = &cmcf->variables_hash;
+ hash.key = ngx_hash_key;
+ hash.max_size = cmcf->variables_hash_max_size;
+ hash.bucket_size = cmcf->variables_hash_bucket_size;
+ hash.name = "variables_hash";
+ hash.pool = cf->pool;
+ hash.temp_pool = NULL;
+
+ if (ngx_hash_init(&hash, cmcf->variables_keys->keys.elts,
+ cmcf->variables_keys->keys.nelts)
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+ cmcf->variables_keys = NULL;
+
+ return NGX_OK;
+}
diff --git a/src/http/ngx_http_variables.h b/src/http/ngx_http_variables.h
new file mode 100644
index 0000000..d0730ea
--- /dev/null
+++ b/src/http/ngx_http_variables.h
@@ -0,0 +1,116 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_HTTP_VARIABLES_H_INCLUDED_
+#define _NGX_HTTP_VARIABLES_H_INCLUDED_
+
+
+#include
+#include
+#include
+
+
+typedef ngx_variable_value_t ngx_http_variable_value_t;
+
+#define ngx_http_variable(v) { sizeof(v) - 1, 1, 0, 0, 0, (u_char *) v }
+
+typedef struct ngx_http_variable_s ngx_http_variable_t;
+
+typedef void (*ngx_http_set_variable_pt) (ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+typedef ngx_int_t (*ngx_http_get_variable_pt) (ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data);
+
+
+#define NGX_HTTP_VAR_CHANGEABLE 1
+#define NGX_HTTP_VAR_NOCACHEABLE 2
+#define NGX_HTTP_VAR_INDEXED 4
+#define NGX_HTTP_VAR_NOHASH 8
+
+//²Î¿¼×ÊÁÏ£º
+//http://blog.csdn.net/dingyujie/article/details/7515904
+struct ngx_http_variable_s {
+ ngx_str_t name; /* must be first to build the hash */ //±äÁ¿µÄ×Ö·û´®Öµ
+ ngx_http_set_variable_pt set_handler; //ʹÓñäÁ¿ÖеÄÖµÉèÖÃrequestµÄij¸ö³ÉÔ±µÄÖµ
+ ngx_http_get_variable_pt get_handler; //¸ù¾ÝrequestÖгÉÔ±(Èçuri£¬argsµÈ)µÄÖµÀ´ÉèÖã¬r->variablesÖжÔÓ¦±äÁ¿µÄÄÚÈÝ
+ uintptr_t data; //ÔÚsetºÍget²Ù×÷ÖÐʹÓã¬Ò»°ãÊÇrÖÐij¸ö³ÉÔ±ÔÚrequest½á¹¹ÖеÄoffset
+ ngx_uint_t flags; //һЩÔÚsetºÍgetÖпØÖÆÌض¨¶¯×÷µÄ±êÖ¾
+ ngx_uint_t index; //ij¸ö±äÁ¿ÔÚr->variabels»òÕßcmcf->variabelsÖÐÊý×éÖеÄϱê
+};
+
+
+ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name,
+ ngx_uint_t flags);
+ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);
+ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r,
+ ngx_uint_t index);
+ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r,
+ ngx_uint_t index);
+
+ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r,
+ ngx_str_t *name, ngx_uint_t key);
+
+ngx_int_t ngx_http_variable_unknown_header(ngx_http_variable_value_t *v,
+ ngx_str_t *var, ngx_list_part_t *part, size_t prefix);
+
+
+#define ngx_http_clear_variable(r, index) r->variables0[index].text.data = NULL;
+
+
+#if (NGX_PCRE)
+
+typedef struct {
+ ngx_uint_t capture;
+ ngx_int_t index;
+} ngx_http_regex_variable_t;
+
+
+typedef struct {
+ ngx_regex_t *regex;
+ ngx_uint_t ncaptures;
+ ngx_http_regex_variable_t *variables;
+ ngx_uint_t nvariables;
+ ngx_str_t name;
+} ngx_http_regex_t;
+
+
+typedef struct {
+ ngx_http_regex_t *regex;
+ void *value;
+} ngx_http_map_regex_t;
+
+
+ngx_http_regex_t *ngx_http_regex_compile(ngx_conf_t *cf,
+ ngx_regex_compile_t *rc);
+ngx_int_t ngx_http_regex_exec(ngx_http_request_t *r, ngx_http_regex_t *re,
+ ngx_str_t *s);
+
+#endif
+
+
+typedef struct {
+ ngx_hash_combined_t hash;
+#if (NGX_PCRE)
+ ngx_http_map_regex_t *regex;
+ ngx_uint_t nregex;
+#endif
+} ngx_http_map_t;
+
+
+void *ngx_http_map_find(ngx_http_request_t *r, ngx_http_map_t *map,
+ ngx_str_t *match);
+
+
+ngx_int_t ngx_http_variables_add_core_vars(ngx_conf_t *cf);
+ngx_int_t ngx_http_variables_init_vars(ngx_conf_t *cf);
+
+
+extern ngx_http_variable_value_t ngx_http_variable_null_value;
+extern ngx_http_variable_value_t ngx_http_variable_true_value;
+
+
+#endif /* _NGX_HTTP_VARIABLES_H_INCLUDED_ */
diff --git a/src/http/ngx_http_write_filter_module.c b/src/http/ngx_http_write_filter_module.c
new file mode 100644
index 0000000..4768d31
--- /dev/null
+++ b/src/http/ngx_http_write_filter_module.c
@@ -0,0 +1,321 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+static ngx_int_t ngx_http_write_filter_init(ngx_conf_t *cf);
+
+
+static ngx_http_module_t ngx_http_write_filter_module_ctx = {
+ NULL, /* preconfiguration */
+ ngx_http_write_filter_init, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ NULL, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ NULL, /* create location configuration */
+ NULL, /* merge location configuration */
+};
+
+
+ngx_module_t ngx_http_write_filter_module = {
+ NGX_MODULE_V1,
+ &ngx_http_write_filter_module_ctx, /* module context */
+ NULL, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+ngx_int_t
+ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
+{
+ off_t size, sent, nsent, limit;
+ ngx_uint_t last, flush;
+ ngx_msec_t delay;
+ ngx_chain_t *cl, *ln, **ll, *chain;
+ ngx_connection_t *c;
+ ngx_http_core_loc_conf_t *clcf;
+
+ //µÃµ½µ±Ç°ËùÊôµÄÁ¬½Ó
+ c = r->connection;
+
+ if (c->error) {
+ return NGX_ERROR;
+ }
+
+ size = 0;
+ flush = 0;
+ last = 0;
+ //µÃµ½ÉÏ´ÎûÓз¢ËÍÍê±ÏµÄchain
+ ll = &r->out;
+
+ /* find the size, the flush point and the last link of the saved chain */
+
+ for (cl = r->out; cl; cl = cl->next) {
+ ll = &cl->next;
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "write old buf t:%d f:%d %p, pos %p, size: %z "
+ "file: %O, size: %z",
+ cl->buf->temporary, cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+
+#if 1
+ //Èç¹ûÓÐ0³¤¶ÈµÄbufÔò·µ»Ø´íÎó
+ if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "zero size buf in writer "
+ "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+ cl->buf->temporary,
+ cl->buf->recycled,
+ cl->buf->in_file,
+ cl->buf->start,
+ cl->buf->pos,
+ cl->buf->last,
+ cl->buf->file,
+ cl->buf->file_pos,
+ cl->buf->file_last);
+
+ ngx_debug_point();
+ return NGX_ERROR;
+ }
+#endif
+ //µÃµ½bufµÄ´óС
+ size += ngx_buf_size(cl->buf);
+
+ //¿´µ±´«ÊäÍê±ÏºóÊÇ·ñҪˢÐÂbuf
+ if (cl->buf->flush || cl->buf->recycled) {
+ flush = 1;
+ }
+
+ //¿´ÊÇ·ñÊÇ×îºóÒ»¸öbuf
+ if (cl->buf->last_buf) {
+ last = 1;
+ }
+ }
+
+ /* add the new chain to the existent one */
+
+ for (ln = in; ln; ln = ln->next) {
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ return NGX_ERROR;
+ }
+
+ cl->buf = ln->buf;
+ *ll = cl;
+ ll = &cl->next;
+
+ ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "write new buf t:%d f:%d %p, pos %p, size: %z "
+ "file: %O, size: %z",
+ cl->buf->temporary, cl->buf->in_file,
+ cl->buf->start, cl->buf->pos,
+ cl->buf->last - cl->buf->pos,
+ cl->buf->file_pos,
+ cl->buf->file_last - cl->buf->file_pos);
+
+#if 1
+ //УÑébuf
+ if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "zero size buf in writer "
+ "t:%d r:%d f:%d %p %p-%p %p %O-%O",
+ cl->buf->temporary,
+ cl->buf->recycled,
+ cl->buf->in_file,
+ cl->buf->start,
+ cl->buf->pos,
+ cl->buf->last,
+ cl->buf->file,
+ cl->buf->file_pos,
+ cl->buf->file_last);
+
+ ngx_debug_point();
+ return NGX_ERROR;
+ }
+#endif
+
+ size += ngx_buf_size(cl->buf);
+
+ if (cl->buf->flush || cl->buf->recycled) {
+ flush = 1;
+ }
+
+ if (cl->buf->last_buf) {
+ last = 1;
+ }
+ }
+
+ *ll = NULL;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http write filter: l:%d f:%d s:%O", last, flush, size);
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ /*
+ * avoid the output if there are no last buf, no flush point,
+ * there are the incoming bufs and the size of all bufs
+ * is smaller than "postpone_output" directive
+ */
+
+ if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
+ return NGX_OK;
+ }
+
+ if (c->write->delayed) {
+ c->buffered |= NGX_HTTP_WRITE_BUFFERED;
+ return NGX_AGAIN;
+ }
+
+ if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) {
+ if (last) {
+ r->out = NULL;
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
+ return NGX_OK;
+ }
+
+ if (flush) {
+ do {
+ r->out = r->out->next;
+ } while (r->out);
+
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "the http output chain is empty");
+
+ ngx_debug_point();
+
+ return NGX_ERROR;
+ }
+
+ if (r->limit_rate) {
+ limit = r->limit_rate * (ngx_time() - r->start_sec + 1)
+ - (c->sent - clcf->limit_rate_after);
+
+ if (limit <= 0) {
+ c->write->delayed = 1;
+ ngx_add_timer(c->write,
+ (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1));
+
+ c->buffered |= NGX_HTTP_WRITE_BUFFERED;
+
+ return NGX_AGAIN;
+ }
+
+ if (clcf->sendfile_max_chunk
+ && (off_t) clcf->sendfile_max_chunk < limit)
+ {
+ limit = clcf->sendfile_max_chunk;
+ }
+
+ } else {
+ limit = clcf->sendfile_max_chunk;
+ }
+
+ sent = c->sent;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http write filter limit %O", limit);
+
+ chain = c->send_chain(c, r->out, limit);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
+ "http write filter %p", chain);
+
+ if (chain == NGX_CHAIN_ERROR) {
+ c->error = 1;
+ return NGX_ERROR;
+ }
+
+ if (r->limit_rate) {
+
+ nsent = c->sent;
+
+ if (clcf->limit_rate_after) {
+
+ sent -= clcf->limit_rate_after;
+ if (sent < 0) {
+ sent = 0;
+ }
+
+ nsent -= clcf->limit_rate_after;
+ if (nsent < 0) {
+ nsent = 0;
+ }
+ }
+
+ delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate);
+
+ if (delay > 0) {
+ limit = 0;
+ c->write->delayed = 1;
+ ngx_add_timer(c->write, delay);
+ }
+ }
+
+ if (limit
+ && c->write->ready
+ && c->sent - sent >= limit - (off_t) (2 * ngx_pagesize))
+ {
+ c->write->delayed = 1;
+ ngx_add_timer(c->write, 1);
+ }
+
+ for (cl = r->out; cl && cl != chain; /* void */) {
+ ln = cl;
+ cl = cl->next;
+ ngx_free_chain(r->pool, ln);
+ }
+
+ r->out = chain;
+
+ if (chain) {
+ c->buffered |= NGX_HTTP_WRITE_BUFFERED;
+ return NGX_AGAIN;
+ }
+
+ c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;
+
+ if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
+ return NGX_AGAIN;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_write_filter_init(ngx_conf_t *cf)
+{
+ ngx_http_top_body_filter = ngx_http_write_filter;
+
+ return NGX_OK;
+}
diff --git a/src/mail/ngx_mail.c b/src/mail/ngx_mail.c
new file mode 100644
index 0000000..c94281a
--- /dev/null
+++ b/src/mail/ngx_mail.c
@@ -0,0 +1,542 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+
+
+static char *ngx_mail_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static ngx_int_t ngx_mail_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+ ngx_mail_listen_t *listen);
+static char *ngx_mail_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);
+static ngx_int_t ngx_mail_add_addrs(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr);
+#if (NGX_HAVE_INET6)
+static ngx_int_t ngx_mail_add_addrs6(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr);
+#endif
+static ngx_int_t ngx_mail_cmp_conf_addrs(const void *one, const void *two);
+
+
+ngx_uint_t ngx_mail_max_module;
+
+
+static ngx_command_t ngx_mail_commands[] = {
+
+ { ngx_string("mail"),
+ NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_mail_block,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("imap"),
+ NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_mail_block,
+ 0,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_mail_module_ctx = {
+ ngx_string("mail"),
+ NULL,
+ NULL
+};
+
+
+ngx_module_t ngx_mail_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_module_ctx, /* module context */
+ ngx_mail_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static char *
+ngx_mail_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ ngx_uint_t i, m, mi, s;
+ ngx_conf_t pcf;
+ ngx_array_t ports;
+ ngx_mail_listen_t *listen;
+ ngx_mail_module_t *module;
+ ngx_mail_conf_ctx_t *ctx;
+ ngx_mail_core_srv_conf_t **cscfp;
+ ngx_mail_core_main_conf_t *cmcf;
+
+ if (cmd->name.data[0] == 'i') {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "the \"imap\" directive is deprecated, "
+ "use the \"mail\" directive instead");
+ }
+
+ /* the main mail context */
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_mail_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *(ngx_mail_conf_ctx_t **) conf = ctx;
+
+ /* count the number of the http modules and set up their indices */
+
+ ngx_mail_max_module = 0;
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ ngx_modules[m]->ctx_index = ngx_mail_max_module++;
+ }
+
+
+ /* the mail main_conf context, it is the same in the all mail contexts */
+
+ ctx->main_conf = ngx_pcalloc(cf->pool,
+ sizeof(void *) * ngx_mail_max_module);
+ if (ctx->main_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /*
+ * the mail null srv_conf context, it is used to merge
+ * the server{}s' srv_conf's
+ */
+
+ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_mail_max_module);
+ if (ctx->srv_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+
+ /*
+ * create the main_conf's, the null srv_conf's, and the null loc_conf's
+ * of the all mail modules
+ */
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+ mi = ngx_modules[m]->ctx_index;
+
+ if (module->create_main_conf) {
+ ctx->main_conf[mi] = module->create_main_conf(cf);
+ if (ctx->main_conf[mi] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (module->create_srv_conf) {
+ ctx->srv_conf[mi] = module->create_srv_conf(cf);
+ if (ctx->srv_conf[mi] == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+ }
+
+
+ /* parse inside the mail{} block */
+
+ pcf = *cf;
+ cf->ctx = ctx;
+
+ cf->module_type = NGX_MAIL_MODULE;
+ cf->cmd_type = NGX_MAIL_MAIN_CONF;
+ rv = ngx_conf_parse(cf, NULL);
+
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+
+
+ /* init mail{} main_conf's, merge the server{}s' srv_conf's */
+
+ cmcf = ctx->main_conf[ngx_mail_core_module.ctx_index];
+ cscfp = cmcf->servers.elts;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+ mi = ngx_modules[m]->ctx_index;
+
+ /* init mail{} main_conf's */
+
+ cf->ctx = ctx;
+
+ if (module->init_main_conf) {
+ rv = module->init_main_conf(cf, ctx->main_conf[mi]);
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+ }
+
+ for (s = 0; s < cmcf->servers.nelts; s++) {
+
+ /* merge the server{}s' srv_conf's */
+
+ cf->ctx = cscfp[s]->ctx;
+
+ if (module->merge_srv_conf) {
+ rv = module->merge_srv_conf(cf,
+ ctx->srv_conf[mi],
+ cscfp[s]->ctx->srv_conf[mi]);
+ if (rv != NGX_CONF_OK) {
+ *cf = pcf;
+ return rv;
+ }
+ }
+ }
+ }
+
+ *cf = pcf;
+
+
+ if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_mail_conf_port_t))
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ listen = cmcf->listen.elts;
+
+ for (i = 0; i < cmcf->listen.nelts; i++) {
+ if (ngx_mail_add_ports(cf, &ports, &listen[i]) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ return ngx_mail_optimize_servers(cf, &ports);
+}
+
+
+static ngx_int_t
+ngx_mail_add_ports(ngx_conf_t *cf, ngx_array_t *ports,
+ ngx_mail_listen_t *listen)
+{
+ in_port_t p;
+ ngx_uint_t i;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ ngx_mail_conf_port_t *port;
+ ngx_mail_conf_addr_t *addr;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ sa = (struct sockaddr *) &listen->sockaddr;
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) sa;
+ p = sin6->sin6_port;
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) sa;
+ p = sin->sin_port;
+ break;
+ }
+
+ port = ports->elts;
+ for (i = 0; i < ports->nelts; i++) {
+ if (p == port[i].port && sa->sa_family == port[i].family) {
+
+ /* a port is already in the port list */
+
+ port = &port[i];
+ goto found;
+ }
+ }
+
+ /* add a port to the port list */
+
+ port = ngx_array_push(ports);
+ if (port == NULL) {
+ return NGX_ERROR;
+ }
+
+ port->family = sa->sa_family;
+ port->port = p;
+
+ if (ngx_array_init(&port->addrs, cf->temp_pool, 2,
+ sizeof(ngx_mail_conf_addr_t))
+ != NGX_OK)
+ {
+ return NGX_ERROR;
+ }
+
+found:
+
+ addr = ngx_array_push(&port->addrs);
+ if (addr == NULL) {
+ return NGX_ERROR;
+ }
+
+ addr->sockaddr = (struct sockaddr *) &listen->sockaddr;
+ addr->socklen = listen->socklen;
+ addr->ctx = listen->ctx;
+ addr->bind = listen->bind;
+ addr->wildcard = listen->wildcard;
+#if (NGX_MAIL_SSL)
+ addr->ssl = listen->ssl;
+#endif
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ addr->ipv6only = listen->ipv6only;
+#endif
+
+ return NGX_OK;
+}
+
+
+static char *
+ngx_mail_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
+{
+ ngx_uint_t i, p, last, bind_wildcard;
+ ngx_listening_t *ls;
+ ngx_mail_port_t *mport;
+ ngx_mail_conf_port_t *port;
+ ngx_mail_conf_addr_t *addr;
+
+ port = ports->elts;
+ for (p = 0; p < ports->nelts; p++) {
+
+ ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,
+ sizeof(ngx_mail_conf_addr_t), ngx_mail_cmp_conf_addrs);
+
+ addr = port[p].addrs.elts;
+ last = port[p].addrs.nelts;
+
+ /*
+ * if there is the binding to the "*:port" then we need to bind()
+ * to the "*:port" only and ignore the other bindings
+ */
+
+ if (addr[last - 1].wildcard) {
+ addr[last - 1].bind = 1;
+ bind_wildcard = 1;
+
+ } else {
+ bind_wildcard = 0;
+ }
+
+ i = 0;
+
+ while (i < last) {
+
+ if (bind_wildcard && !addr[i].bind) {
+ i++;
+ continue;
+ }
+
+ ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);
+ if (ls == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ls->addr_ntop = 1;
+ ls->handler = ngx_mail_init_connection;
+ ls->pool_size = 256;
+
+ /* TODO: error_log directive */
+ ls->logp = &cf->cycle->new_log;
+ ls->log.data = &ls->addr_text;
+ ls->log.handler = ngx_accept_log_error;
+
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ ls->ipv6only = addr[i].ipv6only;
+#endif
+
+ mport = ngx_palloc(cf->pool, sizeof(ngx_mail_port_t));
+ if (mport == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ls->servers = mport;
+
+ if (i == last - 1) {
+ mport->naddrs = last;
+
+ } else {
+ mport->naddrs = 1;
+ i = 0;
+ }
+
+ switch (ls->sockaddr->sa_family) {
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ if (ngx_mail_add_addrs6(cf, mport, addr) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ break;
+#endif
+ default: /* AF_INET */
+ if (ngx_mail_add_addrs(cf, mport, addr) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+ break;
+ }
+
+ addr++;
+ last--;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_add_addrs(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_mail_in_addr_t *addrs;
+ struct sockaddr_in *sin;
+ u_char buf[NGX_SOCKADDR_STRLEN];
+
+ mport->addrs = ngx_pcalloc(cf->pool,
+ mport->naddrs * sizeof(ngx_mail_in_addr_t));
+ if (mport->addrs == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs = mport->addrs;
+
+ for (i = 0; i < mport->naddrs; i++) {
+
+ sin = (struct sockaddr_in *) addr[i].sockaddr;
+ addrs[i].addr = sin->sin_addr.s_addr;
+
+ addrs[i].conf.ctx = addr[i].ctx;
+#if (NGX_MAIL_SSL)
+ addrs[i].conf.ssl = addr[i].ssl;
+#endif
+
+ len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
+
+ p = ngx_pnalloc(cf->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, buf, len);
+
+ addrs[i].conf.addr_text.len = len;
+ addrs[i].conf.addr_text.data = p;
+ }
+
+ return NGX_OK;
+}
+
+
+#if (NGX_HAVE_INET6)
+
+static ngx_int_t
+ngx_mail_add_addrs6(ngx_conf_t *cf, ngx_mail_port_t *mport,
+ ngx_mail_conf_addr_t *addr)
+{
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_mail_in6_addr_t *addrs6;
+ struct sockaddr_in6 *sin6;
+ u_char buf[NGX_SOCKADDR_STRLEN];
+
+ mport->addrs = ngx_pcalloc(cf->pool,
+ mport->naddrs * sizeof(ngx_mail_in6_addr_t));
+ if (mport->addrs == NULL) {
+ return NGX_ERROR;
+ }
+
+ addrs6 = mport->addrs;
+
+ for (i = 0; i < mport->naddrs; i++) {
+
+ sin6 = (struct sockaddr_in6 *) addr[i].sockaddr;
+ addrs6[i].addr6 = sin6->sin6_addr;
+
+ addrs6[i].conf.ctx = addr[i].ctx;
+#if (NGX_MAIL_SSL)
+ addrs6[i].conf.ssl = addr[i].ssl;
+#endif
+
+ len = ngx_sock_ntop(addr[i].sockaddr, buf, NGX_SOCKADDR_STRLEN, 1);
+
+ p = ngx_pnalloc(cf->pool, len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(p, buf, len);
+
+ addrs6[i].conf.addr_text.len = len;
+ addrs6[i].conf.addr_text.data = p;
+ }
+
+ return NGX_OK;
+}
+
+#endif
+
+
+static ngx_int_t
+ngx_mail_cmp_conf_addrs(const void *one, const void *two)
+{
+ ngx_mail_conf_addr_t *first, *second;
+
+ first = (ngx_mail_conf_addr_t *) one;
+ second = (ngx_mail_conf_addr_t *) two;
+
+ if (first->wildcard) {
+ /* a wildcard must be the last resort, shift it to the end */
+ return 1;
+ }
+
+ if (first->bind && !second->bind) {
+ /* shift explicit bind()ed addresses to the start */
+ return -1;
+ }
+
+ if (!first->bind && second->bind) {
+ /* shift explicit bind()ed addresses to the start */
+ return 1;
+ }
+
+ /* do not sort by default */
+
+ return 0;
+}
diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h
new file mode 100644
index 0000000..46a42df
--- /dev/null
+++ b/src/mail/ngx_mail.h
@@ -0,0 +1,407 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_H_INCLUDED_
+#define _NGX_MAIL_H_INCLUDED_
+
+
+#include
+#include
+#include
+#include
+
+#if (NGX_MAIL_SSL)
+#include
+#endif
+
+
+
+typedef struct {
+ void **main_conf;
+ void **srv_conf;
+} ngx_mail_conf_ctx_t;
+
+
+typedef struct {
+ u_char sockaddr[NGX_SOCKADDRLEN];
+ socklen_t socklen;
+
+ /* server ctx */
+ ngx_mail_conf_ctx_t *ctx;
+
+ unsigned bind:1;
+ unsigned wildcard:1;
+#if (NGX_MAIL_SSL)
+ unsigned ssl:1;
+#endif
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ unsigned ipv6only:2;
+#endif
+} ngx_mail_listen_t;
+
+
+typedef struct {
+ ngx_mail_conf_ctx_t *ctx;
+ ngx_str_t addr_text;
+#if (NGX_MAIL_SSL)
+ ngx_uint_t ssl; /* unsigned ssl:1; */
+#endif
+} ngx_mail_addr_conf_t;
+
+typedef struct {
+ in_addr_t addr;
+ ngx_mail_addr_conf_t conf;
+} ngx_mail_in_addr_t;
+
+
+#if (NGX_HAVE_INET6)
+
+typedef struct {
+ struct in6_addr addr6;
+ ngx_mail_addr_conf_t conf;
+} ngx_mail_in6_addr_t;
+
+#endif
+
+
+typedef struct {
+ /* ngx_mail_in_addr_t or ngx_mail_in6_addr_t */
+ void *addrs;
+ ngx_uint_t naddrs;
+} ngx_mail_port_t;
+
+
+typedef struct {
+ int family;
+ in_port_t port;
+ ngx_array_t addrs; /* array of ngx_mail_conf_addr_t */
+} ngx_mail_conf_port_t;
+
+
+typedef struct {
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+
+ ngx_mail_conf_ctx_t *ctx;
+
+ unsigned bind:1;
+ unsigned wildcard:1;
+#if (NGX_MAIL_SSL)
+ unsigned ssl:1;
+#endif
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ unsigned ipv6only:2;
+#endif
+} ngx_mail_conf_addr_t;
+
+
+typedef struct {
+ ngx_array_t servers; /* ngx_mail_core_srv_conf_t */
+ ngx_array_t listen; /* ngx_mail_listen_t */
+} ngx_mail_core_main_conf_t;
+
+
+#define NGX_MAIL_POP3_PROTOCOL 0
+#define NGX_MAIL_IMAP_PROTOCOL 1
+#define NGX_MAIL_SMTP_PROTOCOL 2
+
+
+typedef struct ngx_mail_protocol_s ngx_mail_protocol_t;
+
+
+typedef struct {
+ ngx_mail_protocol_t *protocol;
+
+ ngx_msec_t timeout;
+ ngx_msec_t resolver_timeout;
+
+ ngx_flag_t so_keepalive;
+
+ ngx_str_t server_name;
+
+ u_char *file_name;
+ ngx_int_t line;
+
+ ngx_resolver_t *resolver;
+
+ /* server ctx */
+ ngx_mail_conf_ctx_t *ctx;
+} ngx_mail_core_srv_conf_t;
+
+
+typedef enum {
+ ngx_pop3_start = 0,
+ ngx_pop3_user,
+ ngx_pop3_passwd,
+ ngx_pop3_auth_login_username,
+ ngx_pop3_auth_login_password,
+ ngx_pop3_auth_plain,
+ ngx_pop3_auth_cram_md5
+} ngx_pop3_state_e;
+
+
+typedef enum {
+ ngx_imap_start = 0,
+ ngx_imap_auth_login_username,
+ ngx_imap_auth_login_password,
+ ngx_imap_auth_plain,
+ ngx_imap_auth_cram_md5,
+ ngx_imap_login,
+ ngx_imap_user,
+ ngx_imap_passwd
+} ngx_imap_state_e;
+
+
+typedef enum {
+ ngx_smtp_start = 0,
+ ngx_smtp_auth_login_username,
+ ngx_smtp_auth_login_password,
+ ngx_smtp_auth_plain,
+ ngx_smtp_auth_cram_md5,
+ ngx_smtp_helo,
+ ngx_smtp_helo_xclient,
+ ngx_smtp_helo_from,
+ ngx_smtp_xclient,
+ ngx_smtp_xclient_from,
+ ngx_smtp_xclient_helo,
+ ngx_smtp_from,
+ ngx_smtp_to
+} ngx_smtp_state_e;
+
+
+typedef struct {
+ ngx_peer_connection_t upstream;
+ ngx_buf_t *buffer;
+} ngx_mail_proxy_ctx_t;
+
+
+typedef struct {
+ uint32_t signature; /* "MAIL" */
+
+ ngx_connection_t *connection;
+
+ ngx_str_t out;
+ ngx_buf_t *buffer;
+
+ void **ctx;
+ void **main_conf;
+ void **srv_conf;
+
+ ngx_resolver_ctx_t *resolver_ctx;
+
+ ngx_mail_proxy_ctx_t *proxy;
+
+ ngx_uint_t mail_state;
+
+ unsigned protocol:3;
+ unsigned blocked:1;
+ unsigned quit:1;
+ unsigned quoted:1;
+ unsigned backslash:1;
+ unsigned no_sync_literal:1;
+ unsigned starttls:1;
+ unsigned esmtp:1;
+ unsigned auth_method:3;
+ unsigned auth_wait:1;
+
+ ngx_str_t login;
+ ngx_str_t passwd;
+
+ ngx_str_t salt;
+ ngx_str_t tag;
+ ngx_str_t tagged_line;
+ ngx_str_t text;
+
+ ngx_str_t *addr_text;
+ ngx_str_t host;
+ ngx_str_t smtp_helo;
+ ngx_str_t smtp_from;
+ ngx_str_t smtp_to;
+
+ ngx_uint_t command;
+ ngx_array_t args;
+
+ ngx_uint_t login_attempt;
+
+ /* used to parse POP3/IMAP/SMTP command */
+
+ ngx_uint_t state;
+ u_char *cmd_start;
+ u_char *arg_start;
+ u_char *arg_end;
+ ngx_uint_t literal_len;
+} ngx_mail_session_t;
+
+
+typedef struct {
+ ngx_str_t *client;
+ ngx_mail_session_t *session;
+} ngx_mail_log_ctx_t;
+
+
+#define NGX_POP3_USER 1
+#define NGX_POP3_PASS 2
+#define NGX_POP3_CAPA 3
+#define NGX_POP3_QUIT 4
+#define NGX_POP3_NOOP 5
+#define NGX_POP3_STLS 6
+#define NGX_POP3_APOP 7
+#define NGX_POP3_AUTH 8
+#define NGX_POP3_STAT 9
+#define NGX_POP3_LIST 10
+#define NGX_POP3_RETR 11
+#define NGX_POP3_DELE 12
+#define NGX_POP3_RSET 13
+#define NGX_POP3_TOP 14
+#define NGX_POP3_UIDL 15
+
+
+#define NGX_IMAP_LOGIN 1
+#define NGX_IMAP_LOGOUT 2
+#define NGX_IMAP_CAPABILITY 3
+#define NGX_IMAP_NOOP 4
+#define NGX_IMAP_STARTTLS 5
+
+#define NGX_IMAP_NEXT 6
+
+#define NGX_IMAP_AUTHENTICATE 7
+
+
+#define NGX_SMTP_HELO 1
+#define NGX_SMTP_EHLO 2
+#define NGX_SMTP_AUTH 3
+#define NGX_SMTP_QUIT 4
+#define NGX_SMTP_NOOP 5
+#define NGX_SMTP_MAIL 6
+#define NGX_SMTP_RSET 7
+#define NGX_SMTP_RCPT 8
+#define NGX_SMTP_DATA 9
+#define NGX_SMTP_VRFY 10
+#define NGX_SMTP_EXPN 11
+#define NGX_SMTP_HELP 12
+#define NGX_SMTP_STARTTLS 13
+
+
+#define NGX_MAIL_AUTH_PLAIN 0
+#define NGX_MAIL_AUTH_LOGIN 1
+#define NGX_MAIL_AUTH_LOGIN_USERNAME 2
+#define NGX_MAIL_AUTH_APOP 3
+#define NGX_MAIL_AUTH_CRAM_MD5 4
+#define NGX_MAIL_AUTH_NONE 5
+
+
+#define NGX_MAIL_AUTH_PLAIN_ENABLED 0x0002
+#define NGX_MAIL_AUTH_LOGIN_ENABLED 0x0004
+#define NGX_MAIL_AUTH_APOP_ENABLED 0x0008
+#define NGX_MAIL_AUTH_CRAM_MD5_ENABLED 0x0010
+#define NGX_MAIL_AUTH_NONE_ENABLED 0x0020
+
+
+#define NGX_MAIL_PARSE_INVALID_COMMAND 20
+
+
+typedef void (*ngx_mail_init_session_pt)(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+typedef void (*ngx_mail_init_protocol_pt)(ngx_event_t *rev);
+typedef void (*ngx_mail_auth_state_pt)(ngx_event_t *rev);
+typedef ngx_int_t (*ngx_mail_parse_command_pt)(ngx_mail_session_t *s);
+
+
+struct ngx_mail_protocol_s {
+ ngx_str_t name;
+ in_port_t port[4];
+ ngx_uint_t type;
+
+ ngx_mail_init_session_pt init_session;
+ ngx_mail_init_protocol_pt init_protocol;
+ ngx_mail_parse_command_pt parse_command;
+ ngx_mail_auth_state_pt auth_state;
+
+ ngx_str_t internal_server_error;
+};
+
+
+typedef struct {
+ ngx_mail_protocol_t *protocol;
+
+ void *(*create_main_conf)(ngx_conf_t *cf);
+ char *(*init_main_conf)(ngx_conf_t *cf, void *conf);
+
+ void *(*create_srv_conf)(ngx_conf_t *cf);
+ char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,
+ void *conf);
+} ngx_mail_module_t;
+
+
+#define NGX_MAIL_MODULE 0x4C49414D /* "MAIL" */
+
+#define NGX_MAIL_MAIN_CONF 0x02000000
+#define NGX_MAIL_SRV_CONF 0x04000000
+
+
+#define NGX_MAIL_MAIN_CONF_OFFSET offsetof(ngx_mail_conf_ctx_t, main_conf)
+#define NGX_MAIL_SRV_CONF_OFFSET offsetof(ngx_mail_conf_ctx_t, srv_conf)
+
+
+#define ngx_mail_get_module_ctx(s, module) (s)->ctx[module.ctx_index]
+#define ngx_mail_set_ctx(s, c, module) s->ctx[module.ctx_index] = c;
+#define ngx_mail_delete_ctx(s, module) s->ctx[module.ctx_index] = NULL;
+
+
+#define ngx_mail_get_module_main_conf(s, module) \
+ (s)->main_conf[module.ctx_index]
+#define ngx_mail_get_module_srv_conf(s, module) (s)->srv_conf[module.ctx_index]
+
+#define ngx_mail_conf_get_module_main_conf(cf, module) \
+ ((ngx_mail_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]
+#define ngx_mail_conf_get_module_srv_conf(cf, module) \
+ ((ngx_mail_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]
+
+
+#if (NGX_MAIL_SSL)
+void ngx_mail_starttls_handler(ngx_event_t *rev);
+ngx_int_t ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c);
+#endif
+
+
+void ngx_mail_init_connection(ngx_connection_t *c);
+
+ngx_int_t ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_mail_core_srv_conf_t *cscf);
+ngx_int_t ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_uint_t n);
+ngx_int_t ngx_mail_auth_login_username(ngx_mail_session_t *s,
+ ngx_connection_t *c, ngx_uint_t n);
+ngx_int_t ngx_mail_auth_login_password(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s,
+ ngx_connection_t *c, char *prefix, size_t len);
+ngx_int_t ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c);
+ngx_int_t ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c);
+
+void ngx_mail_send(ngx_event_t *wev);
+ngx_int_t ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_close_connection(ngx_connection_t *c);
+void ngx_mail_session_internal_server_error(ngx_mail_session_t *s);
+u_char *ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len);
+
+
+char *ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
+
+/* STUB */
+void ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer);
+void ngx_mail_auth_http_init(ngx_mail_session_t *s);
+/**/
+
+
+extern ngx_uint_t ngx_mail_max_module;
+extern ngx_module_t ngx_mail_core_module;
+
+
+#endif /* _NGX_MAIL_H_INCLUDED_ */
diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c
new file mode 100644
index 0000000..c23c4ba
--- /dev/null
+++ b/src/mail/ngx_mail_auth_http_module.c
@@ -0,0 +1,1452 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+typedef struct {
+ ngx_addr_t *peer;
+
+ ngx_msec_t timeout;
+
+ ngx_str_t host_header;
+ ngx_str_t uri;
+ ngx_str_t header;
+
+ ngx_array_t *headers;
+
+ u_char *file;
+ ngx_uint_t line;
+} ngx_mail_auth_http_conf_t;
+
+
+typedef struct ngx_mail_auth_http_ctx_s ngx_mail_auth_http_ctx_t;
+
+typedef void (*ngx_mail_auth_http_handler_pt)(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+
+struct ngx_mail_auth_http_ctx_s {
+ ngx_buf_t *request;
+ ngx_buf_t *response;
+ ngx_peer_connection_t peer;
+
+ ngx_mail_auth_http_handler_pt handler;
+
+ ngx_uint_t state;
+
+ u_char *header_name_start;
+ u_char *header_name_end;
+ u_char *header_start;
+ u_char *header_end;
+
+ ngx_str_t addr;
+ ngx_str_t port;
+ ngx_str_t err;
+ ngx_str_t errmsg;
+ ngx_str_t errcode;
+
+ time_t sleep;
+
+ ngx_pool_t *pool;
+};
+
+
+static void ngx_mail_auth_http_write_handler(ngx_event_t *wev);
+static void ngx_mail_auth_http_read_handler(ngx_event_t *rev);
+static void ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_sleep_handler(ngx_event_t *rev);
+static ngx_int_t ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx);
+static void ngx_mail_auth_http_block_read(ngx_event_t *rev);
+static void ngx_mail_auth_http_dummy_handler(ngx_event_t *ev);
+static ngx_buf_t *ngx_mail_auth_http_create_request(ngx_mail_session_t *s,
+ ngx_pool_t *pool, ngx_mail_auth_http_conf_t *ahcf);
+static ngx_int_t ngx_mail_auth_http_escape(ngx_pool_t *pool, ngx_str_t *text,
+ ngx_str_t *escaped);
+
+static void *ngx_mail_auth_http_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_auth_http_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_mail_auth_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_mail_auth_http_header(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_mail_auth_http_commands[] = {
+
+ { ngx_string("auth_http"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_auth_http,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("auth_http_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_auth_http_conf_t, timeout),
+ NULL },
+
+ { ngx_string("auth_http_header"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE2,
+ ngx_mail_auth_http_header,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_auth_http_module_ctx = {
+ NULL, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_auth_http_create_conf, /* create server configuration */
+ ngx_mail_auth_http_merge_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_auth_http_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_auth_http_module_ctx, /* module context */
+ ngx_mail_auth_http_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_mail_auth_http_method[] = {
+ ngx_string("plain"),
+ ngx_string("plain"),
+ ngx_string("plain"),
+ ngx_string("apop"),
+ ngx_string("cram-md5"),
+ ngx_string("none")
+};
+
+static ngx_str_t ngx_mail_smtp_errcode = ngx_string("535 5.7.0");
+
+
+void
+ngx_mail_auth_http_init(ngx_mail_session_t *s)
+{
+ ngx_int_t rc;
+ ngx_pool_t *pool;
+ ngx_mail_auth_http_ctx_t *ctx;
+ ngx_mail_auth_http_conf_t *ahcf;
+
+ s->connection->log->action = "in http auth state";
+
+ pool = ngx_create_pool(2048, s->connection->log);
+ if (pool == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx = ngx_pcalloc(pool, sizeof(ngx_mail_auth_http_ctx_t));
+ if (ctx == NULL) {
+ ngx_destroy_pool(pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->pool = pool;
+
+ ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module);
+
+ ctx->request = ngx_mail_auth_http_create_request(s, pool, ahcf);
+ if (ctx->request == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_mail_set_ctx(s, ctx, ngx_mail_auth_http_module);
+
+ ctx->peer.sockaddr = ahcf->peer->sockaddr;
+ ctx->peer.socklen = ahcf->peer->socklen;
+ ctx->peer.name = &ahcf->peer->name;
+ ctx->peer.get = ngx_event_get_peer;
+ ctx->peer.log = s->connection->log;
+ ctx->peer.log_error = NGX_ERROR_ERR;
+
+ rc = ngx_event_connect_peer(&ctx->peer);
+
+ if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+ if (ctx->peer.connection) {
+ ngx_close_connection(ctx->peer.connection);
+ }
+
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->peer.connection->data = s;
+ ctx->peer.connection->pool = s->connection->pool;
+
+ s->connection->read->handler = ngx_mail_auth_http_block_read;
+ ctx->peer.connection->read->handler = ngx_mail_auth_http_read_handler;
+ ctx->peer.connection->write->handler = ngx_mail_auth_http_write_handler;
+
+ ctx->handler = ngx_mail_auth_http_ignore_status_line;
+
+ ngx_add_timer(ctx->peer.connection->read, ahcf->timeout);
+ ngx_add_timer(ctx->peer.connection->write, ahcf->timeout);
+
+ if (rc == NGX_OK) {
+ ngx_mail_auth_http_write_handler(ctx->peer.connection->write);
+ return;
+ }
+}
+
+
+static void
+ngx_mail_auth_http_write_handler(ngx_event_t *wev)
+{
+ ssize_t n, size;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_auth_http_ctx_t *ctx;
+ ngx_mail_auth_http_conf_t *ahcf;
+
+ c = wev->data;
+ s = c->data;
+
+ ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0,
+ "mail auth http write handler");
+
+ if (wev->timedout) {
+ ngx_log_error(NGX_LOG_ERR, wev->log, NGX_ETIMEDOUT,
+ "auth http server %V timed out", ctx->peer.name);
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ size = ctx->request->last - ctx->request->pos;
+
+ n = ngx_send(c, ctx->request->pos, size);
+
+ if (n == NGX_ERROR) {
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ if (n > 0) {
+ ctx->request->pos += n;
+
+ if (n == size) {
+ wev->handler = ngx_mail_auth_http_dummy_handler;
+
+ if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ }
+
+ return;
+ }
+ }
+
+ if (!wev->timer_set) {
+ ahcf = ngx_mail_get_module_srv_conf(s, ngx_mail_auth_http_module);
+ ngx_add_timer(wev, ahcf->timeout);
+ }
+}
+
+
+static void
+ngx_mail_auth_http_read_handler(ngx_event_t *rev)
+{
+ ssize_t n, size;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_auth_http_ctx_t *ctx;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail auth http read handler");
+
+ ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_ERR, rev->log, NGX_ETIMEDOUT,
+ "auth http server %V timed out", ctx->peer.name);
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ if (ctx->response == NULL) {
+ ctx->response = ngx_create_temp_buf(ctx->pool, 1024);
+ if (ctx->response == NULL) {
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ size = ctx->response->end - ctx->response->last;
+
+ n = ngx_recv(c, ctx->response->pos, size);
+
+ if (n > 0) {
+ ctx->response->last += n;
+
+ ctx->handler(s, ctx);
+ return;
+ }
+
+ if (n == NGX_AGAIN) {
+ return;
+ }
+
+ ngx_close_connection(c);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+}
+
+
+static void
+ngx_mail_auth_http_ignore_status_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx)
+{
+ u_char *p, ch;
+ enum {
+ sw_start = 0,
+ sw_H,
+ sw_HT,
+ sw_HTT,
+ sw_HTTP,
+ sw_skip,
+ sw_almost_done
+ } state;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http process status line");
+
+ state = ctx->state;
+
+ for (p = ctx->response->pos; p < ctx->response->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* "HTTP/" */
+ case sw_start:
+ if (ch == 'H') {
+ state = sw_H;
+ break;
+ }
+ goto next;
+
+ case sw_H:
+ if (ch == 'T') {
+ state = sw_HT;
+ break;
+ }
+ goto next;
+
+ case sw_HT:
+ if (ch == 'T') {
+ state = sw_HTT;
+ break;
+ }
+ goto next;
+
+ case sw_HTT:
+ if (ch == 'P') {
+ state = sw_HTTP;
+ break;
+ }
+ goto next;
+
+ case sw_HTTP:
+ if (ch == '/') {
+ state = sw_skip;
+ break;
+ }
+ goto next;
+
+ /* any text until end of line */
+ case sw_skip:
+ switch (ch) {
+ case CR:
+ state = sw_almost_done;
+
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+
+ /* end of status line */
+ case sw_almost_done:
+ if (ch == LF) {
+ goto done;
+ }
+
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server &V sent invalid response",
+ ctx->peer.name);
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ ctx->response->pos = p;
+ ctx->state = state;
+
+ return;
+
+next:
+
+ p = ctx->response->start - 1;
+
+done:
+
+ ctx->response->pos = p + 1;
+ ctx->state = 0;
+ ctx->handler = ngx_mail_auth_http_process_headers;
+ ctx->handler(s, ctx);
+}
+
+
+static void
+ngx_mail_auth_http_process_headers(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx)
+{
+ u_char *p;
+ time_t timer;
+ size_t len, size;
+ ngx_int_t rc, port, n;
+ ngx_addr_t *peer;
+ struct sockaddr_in *sin;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http process headers");
+
+ for ( ;; ) {
+ rc = ngx_mail_auth_http_parse_header_line(s, ctx);
+
+ if (rc == NGX_OK) {
+
+#if (NGX_DEBUG)
+ {
+ ngx_str_t key, value;
+
+ key.len = ctx->header_name_end - ctx->header_name_start;
+ key.data = ctx->header_name_start;
+ value.len = ctx->header_end - ctx->header_start;
+ value.data = ctx->header_start;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http header: \"%V: %V\"",
+ &key, &value);
+ }
+#endif
+
+ len = ctx->header_name_end - ctx->header_name_start;
+
+ if (len == sizeof("Auth-Status") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Status",
+ sizeof("Auth-Status") - 1)
+ == 0)
+ {
+ len = ctx->header_end - ctx->header_start;
+
+ if (len == 2
+ && ctx->header_start[0] == 'O'
+ && ctx->header_start[1] == 'K')
+ {
+ continue;
+ }
+
+ if (len == 4
+ && ctx->header_start[0] == 'W'
+ && ctx->header_start[1] == 'A'
+ && ctx->header_start[2] == 'I'
+ && ctx->header_start[3] == 'T')
+ {
+ s->auth_wait = 1;
+ continue;
+ }
+
+ ctx->errmsg.len = len;
+ ctx->errmsg.data = ctx->header_start;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ size = sizeof("-ERR ") - 1 + len + sizeof(CRLF) - 1;
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ size = s->tag.len + sizeof("NO ") - 1 + len
+ + sizeof(CRLF) - 1;
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ ctx->err = ctx->errmsg;
+ continue;
+ }
+
+ p = ngx_pnalloc(s->connection->pool, size);
+ if (p == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->err.data = p;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ *p++ = '-'; *p++ = 'E'; *p++ = 'R'; *p++ = 'R'; *p++ = ' ';
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ p = ngx_cpymem(p, s->tag.data, s->tag.len);
+ *p++ = 'N'; *p++ = 'O'; *p++ = ' ';
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ break;
+ }
+
+ p = ngx_cpymem(p, ctx->header_start, len);
+ *p++ = CR; *p++ = LF;
+
+ ctx->err.len = p - ctx->err.data;
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Server") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Server",
+ sizeof("Auth-Server") - 1)
+ == 0)
+ {
+ ctx->addr.len = ctx->header_end - ctx->header_start;
+ ctx->addr.data = ctx->header_start;
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Port") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Port",
+ sizeof("Auth-Port") - 1)
+ == 0)
+ {
+ ctx->port.len = ctx->header_end - ctx->header_start;
+ ctx->port.data = ctx->header_start;
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-User") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-User",
+ sizeof("Auth-User") - 1)
+ == 0)
+ {
+ s->login.len = ctx->header_end - ctx->header_start;
+
+ s->login.data = ngx_pnalloc(s->connection->pool, s->login.len);
+ if (s->login.data == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_memcpy(s->login.data, ctx->header_start, s->login.len);
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Pass") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Pass",
+ sizeof("Auth-Pass") - 1)
+ == 0)
+ {
+ s->passwd.len = ctx->header_end - ctx->header_start;
+
+ s->passwd.data = ngx_pnalloc(s->connection->pool,
+ s->passwd.len);
+ if (s->passwd.data == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_memcpy(s->passwd.data, ctx->header_start, s->passwd.len);
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Wait") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Wait",
+ sizeof("Auth-Wait") - 1)
+ == 0)
+ {
+ n = ngx_atoi(ctx->header_start,
+ ctx->header_end - ctx->header_start);
+
+ if (n != NGX_ERROR) {
+ ctx->sleep = n;
+ }
+
+ continue;
+ }
+
+ if (len == sizeof("Auth-Error-Code") - 1
+ && ngx_strncasecmp(ctx->header_name_start,
+ (u_char *) "Auth-Error-Code",
+ sizeof("Auth-Error-Code") - 1)
+ == 0)
+ {
+ ctx->errcode.len = ctx->header_end - ctx->header_start;
+
+ ctx->errcode.data = ngx_pnalloc(s->connection->pool,
+ ctx->errcode.len);
+ if (ctx->errcode.data == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ngx_memcpy(ctx->errcode.data, ctx->header_start,
+ ctx->errcode.len);
+
+ continue;
+ }
+
+ /* ignore other headers */
+
+ continue;
+ }
+
+ if (rc == NGX_DONE) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http header done");
+
+ ngx_close_connection(ctx->peer.connection);
+
+ if (ctx->err.len) {
+
+ ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+ "client login failed: \"%V\"", &ctx->errmsg);
+
+ if (s->protocol == NGX_MAIL_SMTP_PROTOCOL) {
+
+ if (ctx->errcode.len == 0) {
+ ctx->errcode = ngx_mail_smtp_errcode;
+ }
+
+ ctx->err.len = ctx->errcode.len + ctx->errmsg.len
+ + sizeof(" " CRLF) - 1;
+
+ p = ngx_pnalloc(s->connection->pool, ctx->err.len);
+ if (p == NULL) {
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ ctx->err.data = p;
+
+ p = ngx_cpymem(p, ctx->errcode.data, ctx->errcode.len);
+ *p++ = ' ';
+ p = ngx_cpymem(p, ctx->errmsg.data, ctx->errmsg.len);
+ *p++ = CR; *p = LF;
+ }
+
+ s->out = ctx->err;
+ timer = ctx->sleep;
+
+ ngx_destroy_pool(ctx->pool);
+
+ if (timer == 0) {
+ s->quit = 1;
+ ngx_mail_send(s->connection->write);
+ return;
+ }
+
+ ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000));
+
+ s->connection->read->handler = ngx_mail_auth_sleep_handler;
+
+ return;
+ }
+
+ if (s->auth_wait) {
+ timer = ctx->sleep;
+
+ ngx_destroy_pool(ctx->pool);
+
+ if (timer == 0) {
+ ngx_mail_auth_http_init(s);
+ return;
+ }
+
+ ngx_add_timer(s->connection->read, (ngx_msec_t) (timer * 1000));
+
+ s->connection->read->handler = ngx_mail_auth_sleep_handler;
+
+ return;
+ }
+
+ if (ctx->addr.len == 0 || ctx->port.len == 0) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V did not send server or port",
+ ctx->peer.name);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ if (s->passwd.data == NULL
+ && s->protocol != NGX_MAIL_SMTP_PROTOCOL)
+ {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V did not send password",
+ ctx->peer.name);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ peer = ngx_pcalloc(s->connection->pool, sizeof(ngx_addr_t));
+ if (peer == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ /* AF_INET only */
+
+ sin = ngx_pcalloc(s->connection->pool, sizeof(struct sockaddr_in));
+ if (sin == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ sin->sin_family = AF_INET;
+
+ port = ngx_atoi(ctx->port.data, ctx->port.len);
+ if (port == NGX_ERROR || port < 1 || port > 65535) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V sent invalid server "
+ "port:\"%V\"",
+ ctx->peer.name, &ctx->port);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ sin->sin_port = htons((in_port_t) port);
+
+ sin->sin_addr.s_addr = ngx_inet_addr(ctx->addr.data, ctx->addr.len);
+ if (sin->sin_addr.s_addr == INADDR_NONE) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V sent invalid server "
+ "address:\"%V\"",
+ ctx->peer.name, &ctx->addr);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ peer->sockaddr = (struct sockaddr *) sin;
+ peer->socklen = sizeof(struct sockaddr_in);
+
+ len = ctx->addr.len + 1 + ctx->port.len;
+
+ peer->name.len = len;
+
+ peer->name.data = ngx_pnalloc(s->connection->pool, len);
+ if (peer->name.data == NULL) {
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ len = ctx->addr.len;
+
+ ngx_memcpy(peer->name.data, ctx->addr.data, len);
+
+ peer->name.data[len++] = ':';
+
+ ngx_memcpy(peer->name.data + len, ctx->port.data, ctx->port.len);
+
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_proxy_init(s, peer);
+
+ return;
+ }
+
+ if (rc == NGX_AGAIN ) {
+ return;
+ }
+
+ /* rc == NGX_ERROR */
+
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "auth http server %V sent invalid header in response",
+ ctx->peer.name);
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+
+ return;
+ }
+}
+
+
+static void
+ngx_mail_auth_sleep_handler(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail auth sleep handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+
+ rev->timedout = 0;
+
+ if (s->auth_wait) {
+ s->auth_wait = 0;
+ ngx_mail_auth_http_init(s);
+ return;
+ }
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ rev->handler = cscf->protocol->auth_state;
+
+ s->mail_state = 0;
+ s->auth_method = NGX_MAIL_AUTH_PLAIN;
+
+ c->log->action = "in auth state";
+
+ ngx_mail_send(c->write);
+
+ if (c->destroyed) {
+ return;
+ }
+
+ ngx_add_timer(rev, cscf->timeout);
+
+ if (rev->ready) {
+ rev->handler(rev);
+ return;
+ }
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ return;
+ }
+
+ if (rev->active) {
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+ }
+}
+
+
+static ngx_int_t
+ngx_mail_auth_http_parse_header_line(ngx_mail_session_t *s,
+ ngx_mail_auth_http_ctx_t *ctx)
+{
+ u_char c, ch, *p;
+ enum {
+ sw_start = 0,
+ sw_name,
+ sw_space_before_value,
+ sw_value,
+ sw_space_after_value,
+ sw_almost_done,
+ sw_header_almost_done
+ } state;
+
+ state = ctx->state;
+
+ for (p = ctx->response->pos; p < ctx->response->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* first char */
+ case sw_start:
+
+ switch (ch) {
+ case CR:
+ ctx->header_end = p;
+ state = sw_header_almost_done;
+ break;
+ case LF:
+ ctx->header_end = p;
+ goto header_done;
+ default:
+ state = sw_name;
+ ctx->header_name_start = p;
+
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ break;
+ }
+
+ return NGX_ERROR;
+ }
+ break;
+
+ /* header name */
+ case sw_name:
+ c = (u_char) (ch | 0x20);
+ if (c >= 'a' && c <= 'z') {
+ break;
+ }
+
+ if (ch == ':') {
+ ctx->header_name_end = p;
+ state = sw_space_before_value;
+ break;
+ }
+
+ if (ch == '-') {
+ break;
+ }
+
+ if (ch >= '0' && ch <= '9') {
+ break;
+ }
+
+ if (ch == CR) {
+ ctx->header_name_end = p;
+ ctx->header_start = p;
+ ctx->header_end = p;
+ state = sw_almost_done;
+ break;
+ }
+
+ if (ch == LF) {
+ ctx->header_name_end = p;
+ ctx->header_start = p;
+ ctx->header_end = p;
+ goto done;
+ }
+
+ return NGX_ERROR;
+
+ /* space* before header value */
+ case sw_space_before_value:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ ctx->header_start = p;
+ ctx->header_end = p;
+ state = sw_almost_done;
+ break;
+ case LF:
+ ctx->header_start = p;
+ ctx->header_end = p;
+ goto done;
+ default:
+ ctx->header_start = p;
+ state = sw_value;
+ break;
+ }
+ break;
+
+ /* header value */
+ case sw_value:
+ switch (ch) {
+ case ' ':
+ ctx->header_end = p;
+ state = sw_space_after_value;
+ break;
+ case CR:
+ ctx->header_end = p;
+ state = sw_almost_done;
+ break;
+ case LF:
+ ctx->header_end = p;
+ goto done;
+ }
+ break;
+
+ /* space* before end of header line */
+ case sw_space_after_value:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ default:
+ state = sw_value;
+ break;
+ }
+ break;
+
+ /* end of header line */
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ return NGX_ERROR;
+ }
+
+ /* end of header */
+ case sw_header_almost_done:
+ switch (ch) {
+ case LF:
+ goto header_done;
+ default:
+ return NGX_ERROR;
+ }
+ }
+ }
+
+ ctx->response->pos = p;
+ ctx->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ ctx->response->pos = p + 1;
+ ctx->state = sw_start;
+
+ return NGX_OK;
+
+header_done:
+
+ ctx->response->pos = p + 1;
+ ctx->state = sw_start;
+
+ return NGX_DONE;
+}
+
+
+static void
+ngx_mail_auth_http_block_read(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_auth_http_ctx_t *ctx;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail auth http block read");
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ c = rev->data;
+ s = c->data;
+
+ ctx = ngx_mail_get_module_ctx(s, ngx_mail_auth_http_module);
+
+ ngx_close_connection(ctx->peer.connection);
+ ngx_destroy_pool(ctx->pool);
+ ngx_mail_session_internal_server_error(s);
+ }
+}
+
+
+static void
+ngx_mail_auth_http_dummy_handler(ngx_event_t *ev)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, ev->log, 0,
+ "mail auth http dummy handler");
+}
+
+
+static ngx_buf_t *
+ngx_mail_auth_http_create_request(ngx_mail_session_t *s, ngx_pool_t *pool,
+ ngx_mail_auth_http_conf_t *ahcf)
+{
+ size_t len;
+ ngx_buf_t *b;
+ ngx_str_t login, passwd;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ if (ngx_mail_auth_http_escape(pool, &s->login, &login) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ngx_mail_auth_http_escape(pool, &s->passwd, &passwd) != NGX_OK) {
+ return NULL;
+ }
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ len = sizeof("GET ") - 1 + ahcf->uri.len + sizeof(" HTTP/1.0" CRLF) - 1
+ + sizeof("Host: ") - 1 + ahcf->host_header.len + sizeof(CRLF) - 1
+ + sizeof("Auth-Method: ") - 1
+ + ngx_mail_auth_http_method[s->auth_method].len
+ + sizeof(CRLF) - 1
+ + sizeof("Auth-User: ") - 1 + login.len + sizeof(CRLF) - 1
+ + sizeof("Auth-Pass: ") - 1 + passwd.len + sizeof(CRLF) - 1
+ + sizeof("Auth-Salt: ") - 1 + s->salt.len
+ + sizeof("Auth-Protocol: ") - 1 + cscf->protocol->name.len
+ + sizeof(CRLF) - 1
+ + sizeof("Auth-Login-Attempt: ") - 1 + NGX_INT_T_LEN
+ + sizeof(CRLF) - 1
+ + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len
+ + sizeof(CRLF) - 1
+ + sizeof("Client-Host: ") - 1 + s->host.len + sizeof(CRLF) - 1
+ + sizeof("Auth-SMTP-Helo: ") - 1 + s->smtp_helo.len
+ + sizeof("Auth-SMTP-From: ") - 1 + s->smtp_from.len
+ + sizeof("Auth-SMTP-To: ") - 1 + s->smtp_to.len
+ + ahcf->header.len
+ + sizeof(CRLF) - 1;
+
+ b = ngx_create_temp_buf(pool, len);
+ if (b == NULL) {
+ return NULL;
+ }
+
+ b->last = ngx_cpymem(b->last, "GET ", sizeof("GET ") - 1);
+ b->last = ngx_copy(b->last, ahcf->uri.data, ahcf->uri.len);
+ b->last = ngx_cpymem(b->last, " HTTP/1.0" CRLF,
+ sizeof(" HTTP/1.0" CRLF) - 1);
+
+ b->last = ngx_cpymem(b->last, "Host: ", sizeof("Host: ") - 1);
+ b->last = ngx_copy(b->last, ahcf->host_header.data,
+ ahcf->host_header.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-Method: ",
+ sizeof("Auth-Method: ") - 1);
+ b->last = ngx_cpymem(b->last,
+ ngx_mail_auth_http_method[s->auth_method].data,
+ ngx_mail_auth_http_method[s->auth_method].len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-User: ", sizeof("Auth-User: ") - 1);
+ b->last = ngx_copy(b->last, login.data, login.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-Pass: ", sizeof("Auth-Pass: ") - 1);
+ b->last = ngx_copy(b->last, passwd.data, passwd.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ if (s->auth_method != NGX_MAIL_AUTH_PLAIN && s->salt.len) {
+ b->last = ngx_cpymem(b->last, "Auth-Salt: ", sizeof("Auth-Salt: ") - 1);
+ b->last = ngx_copy(b->last, s->salt.data, s->salt.len);
+
+ s->passwd.data = NULL;
+ }
+
+ b->last = ngx_cpymem(b->last, "Auth-Protocol: ",
+ sizeof("Auth-Protocol: ") - 1);
+ b->last = ngx_cpymem(b->last, cscf->protocol->name.data,
+ cscf->protocol->name.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_sprintf(b->last, "Auth-Login-Attempt: %ui" CRLF,
+ s->login_attempt);
+
+ b->last = ngx_cpymem(b->last, "Client-IP: ", sizeof("Client-IP: ") - 1);
+ b->last = ngx_copy(b->last, s->connection->addr_text.data,
+ s->connection->addr_text.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ if (s->host.len) {
+ b->last = ngx_cpymem(b->last, "Client-Host: ",
+ sizeof("Client-Host: ") - 1);
+ b->last = ngx_copy(b->last, s->host.data, s->host.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+
+ /* HELO, MAIL FROM, and RCPT TO can't contain CRLF, no need to escape */
+
+ b->last = ngx_cpymem(b->last, "Auth-SMTP-Helo: ",
+ sizeof("Auth-SMTP-Helo: ") - 1);
+ b->last = ngx_copy(b->last, s->smtp_helo.data, s->smtp_helo.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-SMTP-From: ",
+ sizeof("Auth-SMTP-From: ") - 1);
+ b->last = ngx_copy(b->last, s->smtp_from.data, s->smtp_from.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ b->last = ngx_cpymem(b->last, "Auth-SMTP-To: ",
+ sizeof("Auth-SMTP-To: ") - 1);
+ b->last = ngx_copy(b->last, s->smtp_to.data, s->smtp_to.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ }
+
+ if (ahcf->header.len) {
+ b->last = ngx_copy(b->last, ahcf->header.data, ahcf->header.len);
+ }
+
+ /* add "\r\n" at the header end */
+ *b->last++ = CR; *b->last++ = LF;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ {
+ ngx_str_t l;
+
+ l.len = b->last - b->pos;
+ l.data = b->pos;
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "mail auth http header:\n\"%V\"", &l);
+ }
+#endif
+
+ return b;
+}
+
+
+static ngx_int_t
+ngx_mail_auth_http_escape(ngx_pool_t *pool, ngx_str_t *text, ngx_str_t *escaped)
+{
+ u_char *p;
+ uintptr_t n;
+
+ n = ngx_escape_uri(NULL, text->data, text->len, NGX_ESCAPE_MAIL_AUTH);
+
+ if (n == 0) {
+ *escaped = *text;
+ return NGX_OK;
+ }
+
+ escaped->len = text->len + n * 2;
+
+ p = ngx_pnalloc(pool, escaped->len);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ (void) ngx_escape_uri(p, text->data, text->len, NGX_ESCAPE_MAIL_AUTH);
+
+ escaped->data = p;
+
+ return NGX_OK;
+}
+
+
+static void *
+ngx_mail_auth_http_create_conf(ngx_conf_t *cf)
+{
+ ngx_mail_auth_http_conf_t *ahcf;
+
+ ahcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_auth_http_conf_t));
+ if (ahcf == NULL) {
+ return NULL;
+ }
+
+ ahcf->timeout = NGX_CONF_UNSET_MSEC;
+
+ ahcf->file = cf->conf_file->file.name.data;
+ ahcf->line = cf->conf_file->line;
+
+ return ahcf;
+}
+
+
+static char *
+ngx_mail_auth_http_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_auth_http_conf_t *prev = parent;
+ ngx_mail_auth_http_conf_t *conf = child;
+
+ u_char *p;
+ size_t len;
+ ngx_uint_t i;
+ ngx_table_elt_t *header;
+
+ if (conf->peer == NULL) {
+ conf->peer = prev->peer;
+ conf->host_header = prev->host_header;
+ conf->uri = prev->uri;
+
+ if (conf->peer == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"http_auth\" is defined for server in %s:%ui",
+ conf->file, conf->line);
+
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+
+ if (conf->headers == NULL) {
+ conf->headers = prev->headers;
+ conf->header = prev->header;
+ }
+
+ if (conf->headers && conf->header.len == 0) {
+ len = 0;
+ header = conf->headers->elts;
+ for (i = 0; i < conf->headers->nelts; i++) {
+ len += header[i].key.len + 2 + header[i].value.len + 2;
+ }
+
+ p = ngx_pnalloc(cf->pool, len);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->header.len = len;
+ conf->header.data = p;
+
+ for (i = 0; i < conf->headers->nelts; i++) {
+ p = ngx_cpymem(p, header[i].key.data, header[i].key.len);
+ *p++ = ':'; *p++ = ' ';
+ p = ngx_cpymem(p, header[i].value.data, header[i].value.len);
+ *p++ = CR; *p++ = LF;
+ }
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_auth_http(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_auth_http_conf_t *ahcf = conf;
+
+ ngx_str_t *value;
+ ngx_url_t u;
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.default_port = 80;
+ u.uri_part = 1;
+ u.one_addr = 1;
+
+ if (ngx_strncmp(u.url.data, "http://", 7) == 0) {
+ u.url.len -= 7;
+ u.url.data += 7;
+ }
+
+ if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%s in auth_http \"%V\"", u.err, &u.url);
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ ahcf->peer = u.addrs;
+
+ if (u.family != AF_UNIX) {
+ ahcf->host_header = u.host;
+
+ } else {
+ ngx_str_set(&ahcf->host_header, "localhost");
+ }
+
+ ahcf->uri = u.uri;
+
+ if (ahcf->uri.len == 0) {
+ ngx_str_set(&ahcf->uri, "/");
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_auth_http_header(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_auth_http_conf_t *ahcf = conf;
+
+ ngx_str_t *value;
+ ngx_table_elt_t *header;
+
+ if (ahcf->headers == NULL) {
+ ahcf->headers = ngx_array_create(cf->pool, 1, sizeof(ngx_table_elt_t));
+ if (ahcf->headers == NULL) {
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ header = ngx_array_push(ahcf->headers);
+ if (header == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ value = cf->args->elts;
+
+ header->key = value[1];
+ header->value = value[2];
+
+ return NGX_CONF_OK;
+}
diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c
new file mode 100644
index 0000000..c352859
--- /dev/null
+++ b/src/mail/ngx_mail_core_module.c
@@ -0,0 +1,553 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+
+
+static void *ngx_mail_core_create_main_conf(ngx_conf_t *cf);
+static void *ngx_mail_core_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_core_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+static char *ngx_mail_core_server(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_command_t ngx_mail_core_commands[] = {
+
+ { ngx_string("server"),
+ NGX_MAIL_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
+ ngx_mail_core_server,
+ 0,
+ 0,
+ NULL },
+
+ { ngx_string("listen"),
+ NGX_MAIL_SRV_CONF|NGX_CONF_TAKE12,
+ ngx_mail_core_listen,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("protocol"),
+ NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_core_protocol,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("so_keepalive"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, so_keepalive),
+ NULL },
+
+ { ngx_string("timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, timeout),
+ NULL },
+
+ { ngx_string("server_name"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, server_name),
+ NULL },
+
+ { ngx_string("resolver"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_core_resolver,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("resolver_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_core_srv_conf_t, resolver_timeout),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_core_module_ctx = {
+ NULL, /* protocol */
+
+ ngx_mail_core_create_main_conf, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_core_create_srv_conf, /* create server configuration */
+ ngx_mail_core_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_core_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_core_module_ctx, /* module context */
+ ngx_mail_core_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_core_create_main_conf(ngx_conf_t *cf)
+{
+ ngx_mail_core_main_conf_t *cmcf;
+
+ cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_core_main_conf_t));
+ if (cmcf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&cmcf->servers, cf->pool, 4,
+ sizeof(ngx_mail_core_srv_conf_t *))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_mail_listen_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return cmcf;
+}
+
+
+static void *
+ngx_mail_core_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_core_srv_conf_t));
+ if (cscf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * cscf->protocol = NULL;
+ */
+
+ cscf->timeout = NGX_CONF_UNSET_MSEC;
+ cscf->resolver_timeout = NGX_CONF_UNSET_MSEC;
+ cscf->so_keepalive = NGX_CONF_UNSET;
+
+ cscf->resolver = NGX_CONF_UNSET_PTR;
+
+ cscf->file_name = cf->conf_file->file.name.data;
+ cscf->line = cf->conf_file->line;
+
+ return cscf;
+}
+
+
+static char *
+ngx_mail_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_core_srv_conf_t *prev = parent;
+ ngx_mail_core_srv_conf_t *conf = child;
+
+ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+ ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout,
+ 30000);
+
+ ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0);
+
+
+ ngx_conf_merge_str_value(conf->server_name, prev->server_name, "");
+
+ if (conf->server_name.len == 0) {
+ conf->server_name = cf->cycle->hostname;
+ }
+
+ if (conf->protocol == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "unknown mail protocol for server in %s:%ui",
+ conf->file_name, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_ptr_value(conf->resolver, prev->resolver, NULL);
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *rv;
+ void *mconf;
+ ngx_uint_t m;
+ ngx_conf_t pcf;
+ ngx_mail_module_t *module;
+ ngx_mail_conf_ctx_t *ctx, *mail_ctx;
+ ngx_mail_core_srv_conf_t *cscf, **cscfp;
+ ngx_mail_core_main_conf_t *cmcf;
+
+ ctx = ngx_pcalloc(cf->pool, sizeof(ngx_mail_conf_ctx_t));
+ if (ctx == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ mail_ctx = cf->ctx;
+ ctx->main_conf = mail_ctx->main_conf;
+
+ /* the server{}'s srv_conf */
+
+ ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_mail_max_module);
+ if (ctx->srv_conf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->create_srv_conf) {
+ mconf = module->create_srv_conf(cf);
+ if (mconf == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ctx->srv_conf[ngx_modules[m]->ctx_index] = mconf;
+ }
+ }
+
+ /* the server configuration context */
+
+ cscf = ctx->srv_conf[ngx_mail_core_module.ctx_index];
+ cscf->ctx = ctx;
+
+ cmcf = ctx->main_conf[ngx_mail_core_module.ctx_index];
+
+ cscfp = ngx_array_push(&cmcf->servers);
+ if (cscfp == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *cscfp = cscf;
+
+
+ /* parse inside server{} */
+
+ pcf = *cf;
+ cf->ctx = ctx;
+ cf->cmd_type = NGX_MAIL_SRV_CONF;
+
+ rv = ngx_conf_parse(cf, NULL);
+
+ *cf = pcf;
+
+ return rv;
+}
+
+
+static char *
+ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_core_srv_conf_t *cscf = conf;
+
+ size_t len, off;
+ in_port_t port;
+ ngx_str_t *value;
+ ngx_url_t u;
+ ngx_uint_t i, m;
+ struct sockaddr *sa;
+ ngx_mail_listen_t *ls;
+ ngx_mail_module_t *module;
+ struct sockaddr_in *sin;
+ ngx_mail_core_main_conf_t *cmcf;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+#endif
+
+ value = cf->args->elts;
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.listen = 1;
+
+ if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "%s in \"%V\" of the \"listen\" directive",
+ u.err, &u.url);
+ }
+
+ return NGX_CONF_ERROR;
+ }
+
+ cmcf = ngx_mail_conf_get_module_main_conf(cf, ngx_mail_core_module);
+
+ ls = cmcf->listen.elts;
+
+ for (i = 0; i < cmcf->listen.nelts; i++) {
+
+ sa = (struct sockaddr *) ls[i].sockaddr;
+
+ if (sa->sa_family != u.family) {
+ continue;
+ }
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ off = offsetof(struct sockaddr_in6, sin6_addr);
+ len = 16;
+ sin6 = (struct sockaddr_in6 *) sa;
+ port = sin6->sin6_port;
+ break;
+#endif
+
+ default: /* AF_INET */
+ off = offsetof(struct sockaddr_in, sin_addr);
+ len = 4;
+ sin = (struct sockaddr_in *) sa;
+ port = sin->sin_port;
+ break;
+ }
+
+ if (ngx_memcmp(ls[i].sockaddr + off, u.sockaddr + off, len) != 0) {
+ continue;
+ }
+
+ if (port != u.port) {
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "duplicate \"%V\" address and port pair", &u.url);
+ return NGX_CONF_ERROR;
+ }
+
+ ls = ngx_array_push(&cmcf->listen);
+ if (ls == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(ls, sizeof(ngx_mail_listen_t));
+
+ ngx_memcpy(ls->sockaddr, u.sockaddr, u.socklen);
+
+ ls->socklen = u.socklen;
+ ls->wildcard = u.wildcard;
+ ls->ctx = cf->ctx;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->protocol == NULL) {
+ continue;
+ }
+
+ for (i = 0; module->protocol->port[i]; i++) {
+ if (module->protocol->port[i] == u.port) {
+ cscf->protocol = module->protocol;
+ break;
+ }
+ }
+ }
+
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strcmp(value[i].data, "bind") == 0) {
+ ls->bind = 1;
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) {
+#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)
+ struct sockaddr *sa;
+ u_char buf[NGX_SOCKADDR_STRLEN];
+
+ sa = (struct sockaddr *) ls->sockaddr;
+
+ if (sa->sa_family == AF_INET6) {
+
+ if (ngx_strcmp(&value[i].data[10], "n") == 0) {
+ ls->ipv6only = 1;
+
+ } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) {
+ ls->ipv6only = 2;
+
+ } else {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid ipv6only flags \"%s\"",
+ &value[i].data[9]);
+ return NGX_CONF_ERROR;
+ }
+
+ ls->bind = 1;
+
+ } else {
+ len = ngx_sock_ntop(sa, buf, NGX_SOCKADDR_STRLEN, 1);
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "ipv6only is not supported "
+ "on addr \"%*s\", ignored", len, buf);
+ }
+
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "bind ipv6only is not supported "
+ "on this platform");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ if (ngx_strcmp(value[i].data, "ssl") == 0) {
+#if (NGX_MAIL_SSL)
+ ls->ssl = 1;
+ continue;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the \"ssl\" parameter requires "
+ "ngx_mail_ssl_module");
+ return NGX_CONF_ERROR;
+#endif
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "the invalid \"%V\" parameter", &value[i]);
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_core_protocol(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_core_srv_conf_t *cscf = conf;
+
+ ngx_str_t *value;
+ ngx_uint_t m;
+ ngx_mail_module_t *module;
+
+ value = cf->args->elts;
+
+ for (m = 0; ngx_modules[m]; m++) {
+ if (ngx_modules[m]->type != NGX_MAIL_MODULE) {
+ continue;
+ }
+
+ module = ngx_modules[m]->ctx;
+
+ if (module->protocol
+ && ngx_strcmp(module->protocol->name.data, value[1].data) == 0)
+ {
+ cscf->protocol = module->protocol;
+
+ return NGX_CONF_OK;
+ }
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "unknown protocol \"%V\"", &value[1]);
+ return NGX_CONF_ERROR;
+}
+
+
+static char *
+ngx_mail_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_core_srv_conf_t *cscf = conf;
+
+ ngx_url_t u;
+ ngx_str_t *value;
+
+ value = cf->args->elts;
+
+ if (cscf->resolver != NGX_CONF_UNSET_PTR) {
+ return "is duplicate";
+ }
+
+ if (ngx_strcmp(value[1].data, "off") == 0) {
+ cscf->resolver = NULL;
+ return NGX_CONF_OK;
+ }
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.host = value[1];
+ u.port = 53;
+
+ if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%V: %s", &u.host, u.err);
+ return NGX_CONF_ERROR;
+ }
+
+ cscf->resolver = ngx_resolver_create(cf, &u.addrs[0]);
+ if (cscf->resolver == NULL) {
+ return NGX_CONF_OK;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+char *
+ngx_mail_capabilities(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ char *p = conf;
+
+ ngx_str_t *c, *value;
+ ngx_uint_t i;
+ ngx_array_t *a;
+
+ a = (ngx_array_t *) (p + cmd->offset);
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+ c = ngx_array_push(a);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = value[i];
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c
new file mode 100644
index 0000000..a1c4464
--- /dev/null
+++ b/src/mail/ngx_mail_handler.c
@@ -0,0 +1,773 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+
+
+static void ngx_mail_init_session(ngx_connection_t *c);
+
+#if (NGX_MAIL_SSL)
+static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c);
+static void ngx_mail_ssl_handshake_handler(ngx_connection_t *c);
+#endif
+
+
+void
+ngx_mail_init_connection(ngx_connection_t *c)
+{
+ ngx_uint_t i;
+ ngx_mail_port_t *port;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ ngx_mail_log_ctx_t *ctx;
+ ngx_mail_in_addr_t *addr;
+ ngx_mail_session_t *s;
+ ngx_mail_addr_conf_t *addr_conf;
+#if (NGX_HAVE_INET6)
+ struct sockaddr_in6 *sin6;
+ ngx_mail_in6_addr_t *addr6;
+#endif
+
+
+ /* find the server configuration for the address:port */
+
+ /* AF_INET only */
+
+ port = c->listening->servers;
+
+ if (port->naddrs > 1) {
+
+ /*
+ * There are several addresses on this port and one of them
+ * is the "*:port" wildcard so getsockname() is needed to determine
+ * the server address.
+ *
+ * AcceptEx() already gave this address.
+ */
+
+ if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ sa = c->local_sockaddr;
+
+ switch (sa->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) sa;
+
+ addr6 = port->addrs;
+
+ /* the last address is "*" */
+
+ for (i = 0; i < port->naddrs - 1; i++) {
+ if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
+ break;
+ }
+ }
+
+ addr_conf = &addr6[i].conf;
+
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) sa;
+
+ addr = port->addrs;
+
+ /* the last address is "*" */
+
+ for (i = 0; i < port->naddrs - 1; i++) {
+ if (addr[i].addr == sin->sin_addr.s_addr) {
+ break;
+ }
+ }
+
+ addr_conf = &addr[i].conf;
+
+ break;
+ }
+
+ } else {
+ switch (c->local_sockaddr->sa_family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ addr6 = port->addrs;
+ addr_conf = &addr6[0].conf;
+ break;
+#endif
+
+ default: /* AF_INET */
+ addr = port->addrs;
+ addr_conf = &addr[0].conf;
+ break;
+ }
+ }
+
+ s = ngx_pcalloc(c->pool, sizeof(ngx_mail_session_t));
+ if (s == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s->main_conf = addr_conf->ctx->main_conf;
+ s->srv_conf = addr_conf->ctx->srv_conf;
+
+ s->addr_text = &addr_conf->addr_text;
+
+ c->data = s;
+ s->connection = c;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client %V connected to %V",
+ c->number, &c->addr_text, s->addr_text);
+
+ ctx = ngx_palloc(c->pool, sizeof(ngx_mail_log_ctx_t));
+ if (ctx == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ ctx->client = &c->addr_text;
+ ctx->session = s;
+
+ c->log->connection = c->number;
+ c->log->handler = ngx_mail_log_error;
+ c->log->data = ctx;
+ c->log->action = "sending client greeting line";
+
+ c->log_error = NGX_ERROR_INFO;
+
+#if (NGX_MAIL_SSL)
+ {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->enable) {
+ c->log->action = "SSL handshaking";
+
+ ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+ return;
+ }
+
+ if (addr_conf->ssl) {
+
+ c->log->action = "SSL handshaking";
+
+ if (sslcf->ssl.ctx == NULL) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "no \"ssl_certificate\" is defined "
+ "in server listening on SSL port");
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+ return;
+ }
+
+ }
+#endif
+
+ ngx_mail_init_session(c);
+}
+
+
+#if (NGX_MAIL_SSL)
+
+void
+ngx_mail_starttls_handler(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_ssl_conf_t *sslcf;
+
+ c = rev->data;
+ s = c->data;
+ s->starttls = 1;
+
+ c->log->action = "in starttls state";
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ ngx_mail_ssl_init_connection(&sslcf->ssl, c);
+}
+
+
+static void
+ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c)
+{
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ if (ngx_ssl_create_connection(ssl, c, 0) == NGX_ERROR) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (ngx_ssl_handshake(c) == NGX_AGAIN) {
+
+ s = c->data;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ c->ssl->handler = ngx_mail_ssl_handshake_handler;
+
+ return;
+ }
+
+ ngx_mail_ssl_handshake_handler(c);
+}
+
+
+static void
+ngx_mail_ssl_handshake_handler(ngx_connection_t *c)
+{
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ if (c->ssl->handshaked) {
+
+ s = c->data;
+
+ if (s->starttls) {
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ c->read->handler = cscf->protocol->init_protocol;
+ c->write->handler = ngx_mail_send;
+
+ cscf->protocol->init_protocol(c->read);
+
+ return;
+ }
+
+ c->read->ready = 0;
+
+ ngx_mail_init_session(c);
+ return;
+ }
+
+ ngx_mail_close_connection(c);
+}
+
+#endif
+
+
+static void
+ngx_mail_init_session(ngx_connection_t *c)
+{
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ s = c->data;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ s->protocol = cscf->protocol->type;
+
+ s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_mail_max_module);
+ if (s->ctx == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ c->write->handler = ngx_mail_send;
+
+ cscf->protocol->init_session(s, c);
+}
+
+
+ngx_int_t
+ngx_mail_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_mail_core_srv_conf_t *cscf)
+{
+ s->salt.data = ngx_pnalloc(c->pool,
+ sizeof(" <18446744073709551616.@>" CRLF) - 1
+ + NGX_TIME_T_LEN
+ + cscf->server_name.len);
+ if (s->salt.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ s->salt.len = ngx_sprintf(s->salt.data, "<%ul.%T@%V>" CRLF,
+ ngx_random(), ngx_time(), &cscf->server_name)
+ - s->salt.data;
+
+ return NGX_OK;
+}
+
+
+#if (NGX_MAIL_SSL)
+
+ngx_int_t
+ngx_mail_starttls_only(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl) {
+ return 0;
+ }
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ return 1;
+ }
+
+ return 0;
+}
+
+#endif
+
+
+ngx_int_t
+ngx_mail_auth_plain(ngx_mail_session_t *s, ngx_connection_t *c, ngx_uint_t n)
+{
+ u_char *p, *last;
+ ngx_str_t *arg, plain;
+
+ arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth plain: \"%V\"", &arg[n]);
+#endif
+
+ plain.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+ if (plain.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&plain, &arg[n]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH PLAIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ p = plain.data;
+ last = p + plain.len;
+
+ while (p < last && *p++) { /* void */ }
+
+ if (p == last) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid login in AUTH PLAIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ s->login.data = p;
+
+ while (p < last && *p) { p++; }
+
+ if (p == last) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid password in AUTH PLAIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ s->login.len = p++ - s->login.data;
+
+ s->passwd.len = last - p;
+ s->passwd.data = p;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth plain: \"%V\" \"%V\"", &s->login, &s->passwd);
+#endif
+
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_login_username(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_uint_t n)
+{
+ ngx_str_t *arg;
+
+ arg = s->args.elts;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login username: \"%V\"", &arg[n]);
+
+ s->login.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[n].len));
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&s->login, &arg[n]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH LOGIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login username: \"%V\"", &s->login);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_login_password(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+ arg = s->args.elts;
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login password: \"%V\"", &arg[0]);
+#endif
+
+ s->passwd.data = ngx_pnalloc(c->pool,
+ ngx_base64_decoded_length(arg[0].len));
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&s->passwd, &arg[0]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH LOGIN command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth login password: \"%V\"", &s->passwd);
+#endif
+
+ return NGX_DONE;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5_salt(ngx_mail_session_t *s, ngx_connection_t *c,
+ char *prefix, size_t len)
+{
+ u_char *p;
+ ngx_str_t salt;
+ ngx_uint_t n;
+
+ p = ngx_pnalloc(c->pool, len + ngx_base64_encoded_length(s->salt.len) + 2);
+ if (p == NULL) {
+ return NGX_ERROR;
+ }
+
+ salt.data = ngx_cpymem(p, prefix, len);
+ s->salt.len -= 2;
+
+ ngx_encode_base64(&salt, &s->salt);
+
+ s->salt.len += 2;
+ n = len + salt.len;
+ p[n++] = CR; p[n++] = LF;
+
+ s->out.len = n;
+ s->out.data = p;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_mail_auth_cram_md5(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char *p, *last;
+ ngx_str_t *arg;
+
+ arg = s->args.elts;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth cram-md5: \"%V\"", &arg[0]);
+
+ s->login.data = ngx_pnalloc(c->pool, ngx_base64_decoded_length(arg[0].len));
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ if (ngx_decode_base64(&s->login, &arg[0]) != NGX_OK) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid base64 encoding in AUTH CRAM-MD5 command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ p = s->login.data;
+ last = p + s->login.len;
+
+ while (p < last) {
+ if (*p++ == ' ') {
+ s->login.len = p - s->login.data - 1;
+ s->passwd.len = last - p;
+ s->passwd.data = p;
+ break;
+ }
+ }
+
+ if (s->passwd.len != 32) {
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent invalid CRAM-MD5 hash in AUTH CRAM-MD5 command");
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "mail auth cram-md5: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+ s->auth_method = NGX_MAIL_AUTH_CRAM_MD5;
+
+ return NGX_DONE;
+}
+
+
+void
+ngx_mail_send(ngx_event_t *wev)
+{
+ ngx_int_t n;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ c = wev->data;
+ s = c->data;
+
+ if (wev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len == 0) {
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ return;
+ }
+
+ n = c->send(c, s->out.data, s->out.len);
+
+ if (n > 0) {
+ s->out.len -= n;
+
+ if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+
+ if (s->quit) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->blocked) {
+ c->read->handler(c->read);
+ }
+
+ return;
+ }
+
+ if (n == NGX_ERROR) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ /* n == NGX_AGAIN */
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ngx_add_timer(c->write, cscf->timeout);
+
+ if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+}
+
+
+ngx_int_t
+ngx_mail_read_command(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ssize_t n;
+ ngx_int_t rc;
+ ngx_str_t l;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_mail_close_connection(c);
+ return NGX_ERROR;
+ }
+
+ if (n > 0) {
+ s->buffer->last += n;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ rc = cscf->protocol->parse_command(s);
+
+ if (rc == NGX_AGAIN) {
+
+ if (s->buffer->last < s->buffer->end) {
+ return rc;
+ }
+
+ l.len = s->buffer->last - s->buffer->start;
+ l.data = s->buffer->start;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0,
+ "client sent too long command \"%V\"", &l);
+
+ s->quit = 1;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (rc == NGX_IMAP_NEXT || rc == NGX_MAIL_PARSE_INVALID_COMMAND) {
+ return rc;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_close_connection(c);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_mail_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ s->args.nelts = 0;
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+ s->state = 0;
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ s->login_attempt++;
+
+ ngx_mail_auth_http_init(s);
+}
+
+
+void
+ngx_mail_session_internal_server_error(ngx_mail_session_t *s)
+{
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ s->out = cscf->protocol->internal_server_error;
+ s->quit = 1;
+
+ ngx_mail_send(s->connection->write);
+}
+
+
+void
+ngx_mail_close_connection(ngx_connection_t *c)
+{
+ ngx_pool_t *pool;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "close mail connection: %d", c->fd);
+
+#if (NGX_MAIL_SSL)
+
+ if (c->ssl) {
+ if (ngx_ssl_shutdown(c) == NGX_AGAIN) {
+ c->ssl->handler = ngx_mail_close_connection;
+ return;
+ }
+ }
+
+#endif
+
+#if (NGX_STAT_STUB)
+ (void) ngx_atomic_fetch_add(ngx_stat_active, -1);
+#endif
+
+ c->destroyed = 1;
+
+ pool = c->pool;
+
+ ngx_close_connection(c);
+
+ ngx_destroy_pool(pool);
+}
+
+
+u_char *
+ngx_mail_log_error(ngx_log_t *log, u_char *buf, size_t len)
+{
+ u_char *p;
+ ngx_mail_session_t *s;
+ ngx_mail_log_ctx_t *ctx;
+
+ if (log->action) {
+ p = ngx_snprintf(buf, len, " while %s", log->action);
+ len -= p - buf;
+ buf = p;
+ }
+
+ ctx = log->data;
+
+ p = ngx_snprintf(buf, len, ", client: %V", ctx->client);
+ len -= p - buf;
+ buf = p;
+
+ s = ctx->session;
+
+ if (s == NULL) {
+ return p;
+ }
+
+ p = ngx_snprintf(buf, len, "%s, server: %V",
+ s->starttls ? " using starttls" : "",
+ s->addr_text);
+ len -= p - buf;
+ buf = p;
+
+ if (s->login.len == 0) {
+ return p;
+ }
+
+ p = ngx_snprintf(buf, len, ", login: \"%V\"", &s->login);
+ len -= p - buf;
+ buf = p;
+
+ if (s->proxy == NULL) {
+ return p;
+ }
+
+ p = ngx_snprintf(buf, len, ", upstream: %V", s->proxy->upstream.name);
+
+ return p;
+}
diff --git a/src/mail/ngx_mail_imap_handler.c b/src/mail/ngx_mail_imap_handler.c
new file mode 100644
index 0000000..57e2fb7
--- /dev/null
+++ b/src/mail/ngx_mail_imap_handler.c
@@ -0,0 +1,457 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+static ngx_int_t ngx_mail_imap_login(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_authenticate(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_capability(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_imap_starttls(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+
+
+static u_char imap_greeting[] = "* OK IMAP4 ready" CRLF;
+static u_char imap_star[] = "* ";
+static u_char imap_ok[] = "OK completed" CRLF;
+static u_char imap_next[] = "+ OK" CRLF;
+static u_char imap_plain_next[] = "+ " CRLF;
+static u_char imap_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char imap_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char imap_bye[] = "* BYE" CRLF;
+static u_char imap_invalid_command[] = "BAD invalid command" CRLF;
+
+
+void
+ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ngx_str_set(&s->out, imap_greeting);
+
+ c->read->handler = ngx_mail_imap_init_protocol;
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_imap_init_protocol(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_imap_srv_conf_t *iscf;
+
+ c = rev->data;
+
+ c->log->action = "in auth state";
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s = c->data;
+
+ if (s->buffer == NULL) {
+ if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+ == NGX_ERROR)
+ {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+ s->buffer = ngx_create_temp_buf(c->pool, iscf->client_buffer_size);
+ if (s->buffer == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ s->mail_state = ngx_imap_start;
+ c->read->handler = ngx_mail_imap_auth_state;
+
+ ngx_mail_imap_auth_state(rev);
+}
+
+
+void
+ngx_mail_imap_auth_state(ngx_event_t *rev)
+{
+ u_char *p, *dst, *src, *end;
+ ngx_str_t *arg;
+ ngx_int_t rc;
+ ngx_uint_t tag, i;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth state");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap send handler busy");
+ s->blocked = 1;
+ return;
+ }
+
+ s->blocked = 0;
+
+ rc = ngx_mail_read_command(s, c);
+
+ if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+ return;
+ }
+
+ tag = 1;
+ s->text.len = 0;
+ ngx_str_set(&s->out, imap_ok);
+
+ if (rc == NGX_OK) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0, "imap auth command: %i",
+ s->command);
+
+ if (s->backslash) {
+
+ arg = s->args.elts;
+
+ for (i = 0; i < s->args.nelts; i++) {
+ dst = arg[i].data;
+ end = dst + arg[i].len;
+
+ for (src = dst; src < end; dst++) {
+ *dst = *src;
+ if (*src++ == '\\') {
+ *dst = *src++;
+ }
+ }
+
+ arg[i].len = dst - arg[i].data;
+ }
+
+ s->backslash = 0;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_imap_start:
+
+ switch (s->command) {
+
+ case NGX_IMAP_LOGIN:
+ rc = ngx_mail_imap_login(s, c);
+ break;
+
+ case NGX_IMAP_AUTHENTICATE:
+ rc = ngx_mail_imap_authenticate(s, c);
+ tag = (rc != NGX_OK);
+ break;
+
+ case NGX_IMAP_CAPABILITY:
+ rc = ngx_mail_imap_capability(s, c);
+ break;
+
+ case NGX_IMAP_LOGOUT:
+ s->quit = 1;
+ ngx_str_set(&s->text, imap_bye);
+ break;
+
+ case NGX_IMAP_NOOP:
+ break;
+
+ case NGX_IMAP_STARTTLS:
+ rc = ngx_mail_imap_starttls(s, c);
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ case ngx_imap_auth_login_username:
+ rc = ngx_mail_auth_login_username(s, c, 0);
+
+ tag = 0;
+ ngx_str_set(&s->out, imap_password);
+ s->mail_state = ngx_imap_auth_login_password;
+
+ break;
+
+ case ngx_imap_auth_login_password:
+ rc = ngx_mail_auth_login_password(s, c);
+ break;
+
+ case ngx_imap_auth_plain:
+ rc = ngx_mail_auth_plain(s, c, 0);
+ break;
+
+ case ngx_imap_auth_cram_md5:
+ rc = ngx_mail_auth_cram_md5(s, c);
+ break;
+ }
+
+ } else if (rc == NGX_IMAP_NEXT) {
+ tag = 0;
+ ngx_str_set(&s->out, imap_next);
+ }
+
+ switch (rc) {
+
+ case NGX_DONE:
+ ngx_mail_auth(s, c);
+ return;
+
+ case NGX_ERROR:
+ ngx_mail_session_internal_server_error(s);
+ return;
+
+ case NGX_MAIL_PARSE_INVALID_COMMAND:
+ s->state = 0;
+ ngx_str_set(&s->out, imap_invalid_command);
+ s->mail_state = ngx_imap_start;
+ break;
+ }
+
+ if (tag) {
+ if (s->tag.len == 0) {
+ ngx_str_set(&s->tag, imap_star);
+ }
+
+ if (s->tagged_line.len < s->tag.len + s->text.len + s->out.len) {
+ s->tagged_line.len = s->tag.len + s->text.len + s->out.len;
+ s->tagged_line.data = ngx_pnalloc(c->pool, s->tagged_line.len);
+ if (s->tagged_line.data == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+ }
+
+ p = s->tagged_line.data;
+
+ if (s->text.len) {
+ p = ngx_cpymem(p, s->text.data, s->text.len);
+ }
+
+ p = ngx_cpymem(p, s->tag.data, s->tag.len);
+ ngx_memcpy(p, s->out.data, s->out.len);
+
+ s->out.len = s->text.len + s->tag.len + s->out.len;
+ s->out.data = s->tagged_line.data;
+ }
+
+ if (rc != NGX_IMAP_NEXT) {
+ s->args.nelts = 0;
+
+ if (s->state) {
+ /* preserve tag */
+ s->arg_start = s->buffer->start + s->tag.len;
+ s->buffer->pos = s->arg_start;
+ s->buffer->last = s->arg_start;
+
+ } else {
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+ s->tag.len = 0;
+ }
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+static ngx_int_t
+ngx_mail_imap_login(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ arg = s->args.elts;
+
+ if (s->args.nelts != 2 || arg[0].len == 0) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ s->login.len = arg[0].len;
+ s->login.data = ngx_pnalloc(c->pool, s->login.len);
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+ s->passwd.len = arg[1].len;
+ s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "imap login:\"%V\" passwd:\"%V\"",
+ &s->login, &s->passwd);
+#else
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "imap login:\"%V\"", &s->login);
+#endif
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_authenticate(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_int_t rc;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_imap_srv_conf_t *iscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ rc = ngx_mail_auth_parse(s, c);
+
+ switch (rc) {
+
+ case NGX_MAIL_AUTH_LOGIN:
+
+ ngx_str_set(&s->out, imap_username);
+ s->mail_state = ngx_imap_auth_login_username;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+ ngx_str_set(&s->out, imap_password);
+ s->mail_state = ngx_imap_auth_login_password;
+
+ return ngx_mail_auth_login_username(s, c, 1);
+
+ case NGX_MAIL_AUTH_PLAIN:
+
+ ngx_str_set(&s->out, imap_plain_next);
+ s->mail_state = ngx_imap_auth_plain;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_CRAM_MD5:
+
+ iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+ if (!(iscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (s->salt.data == NULL) {
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+ s->mail_state = ngx_imap_auth_cram_md5;
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_capability(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_imap_srv_conf_t *iscf;
+
+ iscf = ngx_mail_get_module_srv_conf(s, ngx_mail_imap_module);
+
+#if (NGX_MAIL_SSL)
+
+ if (c->ssl == NULL) {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+ s->text = iscf->starttls_capability;
+ return NGX_OK;
+ }
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ s->text = iscf->starttls_only_capability;
+ return NGX_OK;
+ }
+ }
+#endif
+
+ s->text = iscf->capability;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_imap_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl == NULL) {
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+ if (sslcf->starttls) {
+ c->read->handler = ngx_mail_starttls_handler;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
diff --git a/src/mail/ngx_mail_imap_module.c b/src/mail/ngx_mail_imap_module.c
new file mode 100644
index 0000000..dc80b4f
--- /dev/null
+++ b/src/mail/ngx_mail_imap_module.c
@@ -0,0 +1,253 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+static void *ngx_mail_imap_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_str_t ngx_mail_imap_default_capabilities[] = {
+ ngx_string("IMAP4"),
+ ngx_string("IMAP4rev1"),
+ ngx_string("UIDPLUS"),
+ ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t ngx_mail_imap_auth_methods[] = {
+ { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+ { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+ { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t ngx_mail_imap_auth_methods_names[] = {
+ ngx_string("AUTH=PLAIN"),
+ ngx_string("AUTH=LOGIN"),
+ ngx_null_string, /* APOP */
+ ngx_string("AUTH=CRAM-MD5"),
+ ngx_null_string /* NONE */
+};
+
+
+static ngx_mail_protocol_t ngx_mail_imap_protocol = {
+ ngx_string("imap"),
+ { 143, 993, 0, 0 },
+ NGX_MAIL_IMAP_PROTOCOL,
+
+ ngx_mail_imap_init_session,
+ ngx_mail_imap_init_protocol,
+ ngx_mail_imap_parse_command,
+ ngx_mail_imap_auth_state,
+
+ ngx_string("* BAD internal server error" CRLF)
+};
+
+
+static ngx_command_t ngx_mail_imap_commands[] = {
+
+ { ngx_string("imap_client_buffer"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_imap_srv_conf_t, client_buffer_size),
+ NULL },
+
+ { ngx_string("imap_capabilities"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_mail_capabilities,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_imap_srv_conf_t, capabilities),
+ NULL },
+
+ { ngx_string("imap_auth"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_imap_srv_conf_t, auth_methods),
+ &ngx_mail_imap_auth_methods },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_imap_module_ctx = {
+ &ngx_mail_imap_protocol, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_imap_create_srv_conf, /* create server configuration */
+ ngx_mail_imap_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_imap_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_imap_module_ctx, /* module context */
+ ngx_mail_imap_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_imap_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_imap_srv_conf_t *iscf;
+
+ iscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_imap_srv_conf_t));
+ if (iscf == NULL) {
+ return NULL;
+ }
+
+ iscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+
+ if (ngx_array_init(&iscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return iscf;
+}
+
+
+static char *
+ngx_mail_imap_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_imap_srv_conf_t *prev = parent;
+ ngx_mail_imap_srv_conf_t *conf = child;
+
+ u_char *p, *auth;
+ size_t size;
+ ngx_str_t *c, *d;
+ ngx_uint_t i, m;
+
+ ngx_conf_merge_size_value(conf->client_buffer_size,
+ prev->client_buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_bitmask_value(conf->auth_methods,
+ prev->auth_methods,
+ (NGX_CONF_BITMASK_SET
+ |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+
+ if (conf->capabilities.nelts == 0) {
+ conf->capabilities = prev->capabilities;
+ }
+
+ if (conf->capabilities.nelts == 0) {
+
+ for (d = ngx_mail_imap_default_capabilities; d->len; d++) {
+ c = ngx_array_push(&conf->capabilities);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = *d;
+ }
+ }
+
+ size = sizeof("* CAPABILITY" CRLF) - 1;
+
+ c = conf->capabilities.elts;
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ size += 1 + c[i].len;
+ }
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ size += 1 + ngx_mail_imap_auth_methods_names[i].len;
+ }
+ }
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->capability.len = size;
+ conf->capability.data = p;
+
+ p = ngx_cpymem(p, "* CAPABILITY", sizeof("* CAPABILITY") - 1);
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ *p++ = ' ';
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ }
+
+ auth = p;
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ *p++ = ' ';
+ p = ngx_cpymem(p, ngx_mail_imap_auth_methods_names[i].data,
+ ngx_mail_imap_auth_methods_names[i].len);
+ }
+ }
+
+ *p++ = CR; *p = LF;
+
+
+ size += sizeof(" STARTTLS") - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_capability.len = size;
+ conf->starttls_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data,
+ conf->capability.len - (sizeof(CRLF) - 1));
+ p = ngx_cpymem(p, " STARTTLS", sizeof(" STARTTLS") - 1);
+ *p++ = CR; *p = LF;
+
+
+ size = (auth - conf->capability.data) + sizeof(CRLF) - 1
+ + sizeof(" STARTTLS LOGINDISABLED") - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_only_capability.len = size;
+ conf->starttls_only_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data,
+ auth - conf->capability.data);
+ p = ngx_cpymem(p, " STARTTLS LOGINDISABLED",
+ sizeof(" STARTTLS LOGINDISABLED") - 1);
+ *p++ = CR; *p = LF;
+
+ return NGX_CONF_OK;
+}
diff --git a/src/mail/ngx_mail_imap_module.h b/src/mail/ngx_mail_imap_module.h
new file mode 100644
index 0000000..131b445
--- /dev/null
+++ b/src/mail/ngx_mail_imap_module.h
@@ -0,0 +1,39 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_IMAP_MODULE_H_INCLUDED_
+
+
+#include
+#include
+#include
+
+
+typedef struct {
+ size_t client_buffer_size;
+
+ ngx_str_t capability;
+ ngx_str_t starttls_capability;
+ ngx_str_t starttls_only_capability;
+
+ ngx_uint_t auth_methods;
+
+ ngx_array_t capabilities;
+} ngx_mail_imap_srv_conf_t;
+
+
+void ngx_mail_imap_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_imap_init_protocol(ngx_event_t *rev);
+void ngx_mail_imap_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_imap_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t ngx_mail_imap_module;
+
+
+#endif /* _NGX_MAIL_IMAP_MODULE_H_INCLUDED_ */
diff --git a/src/mail/ngx_mail_parse.c b/src/mail/ngx_mail_parse.c
new file mode 100644
index 0000000..ae0f7e3
--- /dev/null
+++ b/src/mail/ngx_mail_parse.c
@@ -0,0 +1,885 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+
+
+ngx_int_t
+ngx_mail_pop3_parse_command(ngx_mail_session_t *s)
+{
+ u_char ch, *p, *c, c0, c1, c2, c3;
+ ngx_str_t *arg;
+ enum {
+ sw_start = 0,
+ sw_spaces_before_argument,
+ sw_argument,
+ sw_almost_done
+ } state;
+
+ state = s->state;
+
+ for (p = s->buffer->pos; p < s->buffer->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* POP3 command */
+ case sw_start:
+ if (ch == ' ' || ch == CR || ch == LF) {
+ c = s->buffer->start;
+
+ if (p - c == 4) {
+
+ c0 = ngx_toupper(c[0]);
+ c1 = ngx_toupper(c[1]);
+ c2 = ngx_toupper(c[2]);
+ c3 = ngx_toupper(c[3]);
+
+ if (c0 == 'U' && c1 == 'S' && c2 == 'E' && c3 == 'R')
+ {
+ s->command = NGX_POP3_USER;
+
+ } else if (c0 == 'P' && c1 == 'A' && c2 == 'S' && c3 == 'S')
+ {
+ s->command = NGX_POP3_PASS;
+
+ } else if (c0 == 'A' && c1 == 'P' && c2 == 'O' && c3 == 'P')
+ {
+ s->command = NGX_POP3_APOP;
+
+ } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
+ {
+ s->command = NGX_POP3_QUIT;
+
+ } else if (c0 == 'C' && c1 == 'A' && c2 == 'P' && c3 == 'A')
+ {
+ s->command = NGX_POP3_CAPA;
+
+ } else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
+ {
+ s->command = NGX_POP3_AUTH;
+
+ } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
+ {
+ s->command = NGX_POP3_NOOP;
+#if (NGX_MAIL_SSL)
+ } else if (c0 == 'S' && c1 == 'T' && c2 == 'L' && c3 == 'S')
+ {
+ s->command = NGX_POP3_STLS;
+#endif
+ } else {
+ goto invalid;
+ }
+
+ } else {
+ goto invalid;
+ }
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+ goto invalid;
+ }
+
+ break;
+
+ case sw_spaces_before_argument:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ s->arg_end = p;
+ break;
+ case LF:
+ s->arg_end = p;
+ goto done;
+ default:
+ if (s->args.nelts <= 2) {
+ state = sw_argument;
+ s->arg_start = p;
+ break;
+ }
+ goto invalid;
+ }
+ break;
+
+ case sw_argument:
+ switch (ch) {
+
+ case ' ':
+
+ /*
+ * the space should be considered as part of the at username
+ * or password, but not of argument in other commands
+ */
+
+ if (s->command == NGX_POP3_USER
+ || s->command == NGX_POP3_PASS)
+ {
+ break;
+ }
+
+ /* fall through */
+
+ case CR:
+ case LF:
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ goto invalid;
+ }
+ }
+ }
+
+ s->buffer->pos = p;
+ s->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ s->buffer->pos = p + 1;
+
+ if (s->arg_start) {
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = s->arg_end - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+ }
+
+ s->state = (s->command != NGX_POP3_AUTH) ? sw_start : sw_argument;
+
+ return NGX_OK;
+
+invalid:
+
+ s->state = sw_start;
+ s->arg_start = NULL;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_imap_parse_command(ngx_mail_session_t *s)
+{
+ u_char ch, *p, *c;
+ ngx_str_t *arg;
+ enum {
+ sw_start = 0,
+ sw_spaces_before_command,
+ sw_command,
+ sw_spaces_before_argument,
+ sw_argument,
+ sw_backslash,
+ sw_literal,
+ sw_no_sync_literal_argument,
+ sw_start_literal_argument,
+ sw_literal_argument,
+ sw_end_literal_argument,
+ sw_almost_done
+ } state;
+
+ state = s->state;
+
+ for (p = s->buffer->pos; p < s->buffer->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* IMAP tag */
+ case sw_start:
+ switch (ch) {
+ case ' ':
+ s->tag.len = p - s->buffer->start + 1;
+ s->tag.data = s->buffer->start;
+ state = sw_spaces_before_command;
+ break;
+ case CR:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ case LF:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+ break;
+
+ case sw_spaces_before_command:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ case LF:
+ s->state = sw_start;
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ default:
+ s->cmd_start = p;
+ state = sw_command;
+ break;
+ }
+ break;
+
+ case sw_command:
+ if (ch == ' ' || ch == CR || ch == LF) {
+
+ c = s->cmd_start;
+
+ switch (p - c) {
+
+ case 4:
+ if ((c[0] == 'N' || c[0] == 'n')
+ && (c[1] == 'O'|| c[1] == 'o')
+ && (c[2] == 'O'|| c[2] == 'o')
+ && (c[3] == 'P'|| c[3] == 'p'))
+ {
+ s->command = NGX_IMAP_NOOP;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ case 5:
+ if ((c[0] == 'L'|| c[0] == 'l')
+ && (c[1] == 'O'|| c[1] == 'o')
+ && (c[2] == 'G'|| c[2] == 'g')
+ && (c[3] == 'I'|| c[3] == 'i')
+ && (c[4] == 'N'|| c[4] == 'n'))
+ {
+ s->command = NGX_IMAP_LOGIN;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ case 6:
+ if ((c[0] == 'L'|| c[0] == 'l')
+ && (c[1] == 'O'|| c[1] == 'o')
+ && (c[2] == 'G'|| c[2] == 'g')
+ && (c[3] == 'O'|| c[3] == 'o')
+ && (c[4] == 'U'|| c[4] == 'u')
+ && (c[5] == 'T'|| c[5] == 't'))
+ {
+ s->command = NGX_IMAP_LOGOUT;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+#if (NGX_MAIL_SSL)
+ case 8:
+ if ((c[0] == 'S'|| c[0] == 's')
+ && (c[1] == 'T'|| c[1] == 't')
+ && (c[2] == 'A'|| c[2] == 'a')
+ && (c[3] == 'R'|| c[3] == 'r')
+ && (c[4] == 'T'|| c[4] == 't')
+ && (c[5] == 'T'|| c[5] == 't')
+ && (c[6] == 'L'|| c[6] == 'l')
+ && (c[7] == 'S'|| c[7] == 's'))
+ {
+ s->command = NGX_IMAP_STARTTLS;
+
+ } else {
+ goto invalid;
+ }
+ break;
+#endif
+
+ case 10:
+ if ((c[0] == 'C'|| c[0] == 'c')
+ && (c[1] == 'A'|| c[1] == 'a')
+ && (c[2] == 'P'|| c[2] == 'p')
+ && (c[3] == 'A'|| c[3] == 'a')
+ && (c[4] == 'B'|| c[4] == 'b')
+ && (c[5] == 'I'|| c[5] == 'i')
+ && (c[6] == 'L'|| c[6] == 'l')
+ && (c[7] == 'I'|| c[7] == 'i')
+ && (c[8] == 'T'|| c[8] == 't')
+ && (c[9] == 'Y'|| c[9] == 'y'))
+ {
+ s->command = NGX_IMAP_CAPABILITY;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ case 12:
+ if ((c[0] == 'A'|| c[0] == 'a')
+ && (c[1] == 'U'|| c[1] == 'u')
+ && (c[2] == 'T'|| c[2] == 't')
+ && (c[3] == 'H'|| c[3] == 'h')
+ && (c[4] == 'E'|| c[4] == 'e')
+ && (c[5] == 'N'|| c[5] == 'n')
+ && (c[6] == 'T'|| c[6] == 't')
+ && (c[7] == 'I'|| c[7] == 'i')
+ && (c[8] == 'C'|| c[8] == 'c')
+ && (c[9] == 'A'|| c[9] == 'a')
+ && (c[10] == 'T'|| c[10] == 't')
+ && (c[11] == 'E'|| c[11] == 'e'))
+ {
+ s->command = NGX_IMAP_AUTHENTICATE;
+
+ } else {
+ goto invalid;
+ }
+ break;
+
+ default:
+ goto invalid;
+ }
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+ goto invalid;
+ }
+
+ break;
+
+ case sw_spaces_before_argument:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ s->arg_end = p;
+ break;
+ case LF:
+ s->arg_end = p;
+ goto done;
+ case '"':
+ if (s->args.nelts <= 2) {
+ s->quoted = 1;
+ s->arg_start = p + 1;
+ state = sw_argument;
+ break;
+ }
+ goto invalid;
+ case '{':
+ if (s->args.nelts <= 2) {
+ state = sw_literal;
+ break;
+ }
+ goto invalid;
+ default:
+ if (s->args.nelts <= 2) {
+ s->arg_start = p;
+ state = sw_argument;
+ break;
+ }
+ goto invalid;
+ }
+ break;
+
+ case sw_argument:
+ if (ch == ' ' && s->quoted) {
+ break;
+ }
+
+ switch (ch) {
+ case '"':
+ if (!s->quoted) {
+ break;
+ }
+ s->quoted = 0;
+ /* fall through */
+ case ' ':
+ case CR:
+ case LF:
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+
+ switch (ch) {
+ case '"':
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ case '\\':
+ if (s->quoted) {
+ s->backslash = 1;
+ state = sw_backslash;
+ }
+ break;
+ }
+ break;
+
+ case sw_backslash:
+ switch (ch) {
+ case CR:
+ case LF:
+ goto invalid;
+ default:
+ state = sw_argument;
+ }
+ break;
+
+ case sw_literal:
+ if (ch >= '0' && ch <= '9') {
+ s->literal_len = s->literal_len * 10 + (ch - '0');
+ break;
+ }
+ if (ch == '}') {
+ state = sw_start_literal_argument;
+ break;
+ }
+ if (ch == '+') {
+ state = sw_no_sync_literal_argument;
+ break;
+ }
+ goto invalid;
+
+ case sw_no_sync_literal_argument:
+ if (ch == '}') {
+ s->no_sync_literal = 1;
+ state = sw_start_literal_argument;
+ break;
+ }
+ goto invalid;
+
+ case sw_start_literal_argument:
+ switch (ch) {
+ case CR:
+ break;
+ case LF:
+ s->buffer->pos = p + 1;
+ s->arg_start = p + 1;
+ if (s->no_sync_literal == 0) {
+ s->state = sw_literal_argument;
+ return NGX_IMAP_NEXT;
+ }
+ state = sw_literal_argument;
+ s->no_sync_literal = 0;
+ break;
+ default:
+ goto invalid;
+ }
+ break;
+
+ case sw_literal_argument:
+ if (s->literal_len && --s->literal_len) {
+ break;
+ }
+
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p + 1 - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+ state = sw_end_literal_argument;
+
+ break;
+
+ case sw_end_literal_argument:
+ switch (ch) {
+ case '{':
+ if (s->args.nelts <= 2) {
+ state = sw_literal;
+ break;
+ }
+ goto invalid;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ default:
+ state = sw_spaces_before_argument;
+ break;
+ }
+ break;
+
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ goto invalid;
+ }
+ }
+ }
+
+ s->buffer->pos = p;
+ s->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ s->buffer->pos = p + 1;
+
+ if (s->arg_start) {
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = s->arg_end - s->arg_start;
+ arg->data = s->arg_start;
+
+ s->arg_start = NULL;
+ s->cmd_start = NULL;
+ s->quoted = 0;
+ s->no_sync_literal = 0;
+ s->literal_len = 0;
+ }
+
+ s->state = (s->command != NGX_IMAP_AUTHENTICATE) ? sw_start : sw_argument;
+
+ return NGX_OK;
+
+invalid:
+
+ s->state = sw_start;
+ s->quoted = 0;
+ s->no_sync_literal = 0;
+ s->literal_len = 0;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_smtp_parse_command(ngx_mail_session_t *s)
+{
+ u_char ch, *p, *c, c0, c1, c2, c3;
+ ngx_str_t *arg;
+ enum {
+ sw_start = 0,
+ sw_spaces_before_argument,
+ sw_argument,
+ sw_almost_done
+ } state;
+
+ state = s->state;
+
+ for (p = s->buffer->pos; p < s->buffer->last; p++) {
+ ch = *p;
+
+ switch (state) {
+
+ /* SMTP command */
+ case sw_start:
+ if (ch == ' ' || ch == CR || ch == LF) {
+ c = s->buffer->start;
+
+ if (p - c == 4) {
+
+ c0 = ngx_toupper(c[0]);
+ c1 = ngx_toupper(c[1]);
+ c2 = ngx_toupper(c[2]);
+ c3 = ngx_toupper(c[3]);
+
+ if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'O')
+ {
+ s->command = NGX_SMTP_HELO;
+
+ } else if (c0 == 'E' && c1 == 'H' && c2 == 'L' && c3 == 'O')
+ {
+ s->command = NGX_SMTP_EHLO;
+
+ } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
+ {
+ s->command = NGX_SMTP_QUIT;
+
+ } else if (c0 == 'A' && c1 == 'U' && c2 == 'T' && c3 == 'H')
+ {
+ s->command = NGX_SMTP_AUTH;
+
+ } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
+ {
+ s->command = NGX_SMTP_NOOP;
+
+ } else if (c0 == 'M' && c1 == 'A' && c2 == 'I' && c3 == 'L')
+ {
+ s->command = NGX_SMTP_MAIL;
+
+ } else if (c0 == 'R' && c1 == 'S' && c2 == 'E' && c3 == 'T')
+ {
+ s->command = NGX_SMTP_RSET;
+
+ } else if (c0 == 'R' && c1 == 'C' && c2 == 'P' && c3 == 'T')
+ {
+ s->command = NGX_SMTP_RCPT;
+
+ } else if (c0 == 'V' && c1 == 'R' && c2 == 'F' && c3 == 'Y')
+ {
+ s->command = NGX_SMTP_VRFY;
+
+ } else if (c0 == 'E' && c1 == 'X' && c2 == 'P' && c3 == 'N')
+ {
+ s->command = NGX_SMTP_EXPN;
+
+ } else if (c0 == 'H' && c1 == 'E' && c2 == 'L' && c3 == 'P')
+ {
+ s->command = NGX_SMTP_HELP;
+
+ } else {
+ goto invalid;
+ }
+#if (NGX_MAIL_SSL)
+ } else if (p - c == 8) {
+
+ if ((c[0] == 'S'|| c[0] == 's')
+ && (c[1] == 'T'|| c[1] == 't')
+ && (c[2] == 'A'|| c[2] == 'a')
+ && (c[3] == 'R'|| c[3] == 'r')
+ && (c[4] == 'T'|| c[4] == 't')
+ && (c[5] == 'T'|| c[5] == 't')
+ && (c[6] == 'L'|| c[6] == 'l')
+ && (c[7] == 'S'|| c[7] == 's'))
+ {
+ s->command = NGX_SMTP_STARTTLS;
+
+ } else {
+ goto invalid;
+ }
+#endif
+ } else {
+ goto invalid;
+ }
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+ }
+
+ if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+ goto invalid;
+ }
+
+ break;
+
+ case sw_spaces_before_argument:
+ switch (ch) {
+ case ' ':
+ break;
+ case CR:
+ state = sw_almost_done;
+ s->arg_end = p;
+ break;
+ case LF:
+ s->arg_end = p;
+ goto done;
+ default:
+ if (s->args.nelts <= 10) {
+ state = sw_argument;
+ s->arg_start = p;
+ break;
+ }
+ goto invalid;
+ }
+ break;
+
+ case sw_argument:
+ switch (ch) {
+ case ' ':
+ case CR:
+ case LF:
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = p - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+
+ switch (ch) {
+ case ' ':
+ state = sw_spaces_before_argument;
+ break;
+ case CR:
+ state = sw_almost_done;
+ break;
+ case LF:
+ goto done;
+ }
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case sw_almost_done:
+ switch (ch) {
+ case LF:
+ goto done;
+ default:
+ goto invalid;
+ }
+ }
+ }
+
+ s->buffer->pos = p;
+ s->state = state;
+
+ return NGX_AGAIN;
+
+done:
+
+ s->buffer->pos = p + 1;
+
+ if (s->arg_start) {
+ arg = ngx_array_push(&s->args);
+ if (arg == NULL) {
+ return NGX_ERROR;
+ }
+ arg->len = s->arg_end - s->arg_start;
+ arg->data = s->arg_start;
+ s->arg_start = NULL;
+ }
+
+ s->state = (s->command != NGX_SMTP_AUTH) ? sw_start : sw_argument;
+
+ return NGX_OK;
+
+invalid:
+
+ s->state = sw_start;
+ s->arg_start = NULL;
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t
+ngx_mail_auth_parse(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ arg = s->args.elts;
+
+ if (arg[0].len == 5) {
+
+ if (ngx_strncasecmp(arg[0].data, (u_char *) "LOGIN", 5) == 0) {
+
+ if (s->args.nelts == 1) {
+ return NGX_MAIL_AUTH_LOGIN;
+ }
+
+ if (s->args.nelts == 2) {
+ return NGX_MAIL_AUTH_LOGIN_USERNAME;
+ }
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (ngx_strncasecmp(arg[0].data, (u_char *) "PLAIN", 5) == 0) {
+
+ if (s->args.nelts == 1) {
+ return NGX_MAIL_AUTH_PLAIN;
+ }
+
+ if (s->args.nelts == 2) {
+ return ngx_mail_auth_plain(s, c, 1);
+ }
+ }
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (arg[0].len == 8) {
+
+ if (s->args.nelts != 1) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (ngx_strncasecmp(arg[0].data, (u_char *) "CRAM-MD5", 8) == 0) {
+ return NGX_MAIL_AUTH_CRAM_MD5;
+ }
+ }
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
diff --git a/src/mail/ngx_mail_pop3_handler.c b/src/mail/ngx_mail_pop3_handler.c
new file mode 100644
index 0000000..51bc257
--- /dev/null
+++ b/src/mail/ngx_mail_pop3_handler.c
@@ -0,0 +1,500 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+static ngx_int_t ngx_mail_pop3_user(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c,
+ ngx_int_t stls);
+static ngx_int_t ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+
+
+static u_char pop3_greeting[] = "+OK POP3 ready" CRLF;
+static u_char pop3_ok[] = "+OK" CRLF;
+static u_char pop3_next[] = "+ " CRLF;
+static u_char pop3_username[] = "+ VXNlcm5hbWU6" CRLF;
+static u_char pop3_password[] = "+ UGFzc3dvcmQ6" CRLF;
+static u_char pop3_invalid_command[] = "-ERR invalid command" CRLF;
+
+
+void
+ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char *p;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (pscf->auth_methods
+ & (NGX_MAIL_AUTH_APOP_ENABLED|NGX_MAIL_AUTH_CRAM_MD5_ENABLED))
+ {
+ if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->out.data = ngx_pnalloc(c->pool, sizeof(pop3_greeting) + s->salt.len);
+ if (s->out.data == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(s->out.data, pop3_greeting, sizeof(pop3_greeting) - 3);
+ *p++ = ' ';
+ p = ngx_cpymem(p, s->salt.data, s->salt.len);
+
+ s->out.len = p - s->out.data;
+
+ } else {
+ ngx_str_set(&s->out, pop3_greeting);
+ }
+
+ c->read->handler = ngx_mail_pop3_init_protocol;
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_pop3_init_protocol(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+
+ c->log->action = "in auth state";
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s = c->data;
+
+ if (s->buffer == NULL) {
+ if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t))
+ == NGX_ERROR)
+ {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->buffer = ngx_create_temp_buf(c->pool, 128);
+ if (s->buffer == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+ }
+
+ s->mail_state = ngx_pop3_start;
+ c->read->handler = ngx_mail_pop3_auth_state;
+
+ ngx_mail_pop3_auth_state(rev);
+}
+
+
+void
+ngx_mail_pop3_auth_state(ngx_event_t *rev)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 auth state");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "pop3 send handler busy");
+ s->blocked = 1;
+ return;
+ }
+
+ s->blocked = 0;
+
+ rc = ngx_mail_read_command(s, c);
+
+ if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+ return;
+ }
+
+ ngx_str_set(&s->out, pop3_ok);
+
+ if (rc == NGX_OK) {
+ switch (s->mail_state) {
+
+ case ngx_pop3_start:
+
+ switch (s->command) {
+
+ case NGX_POP3_USER:
+ rc = ngx_mail_pop3_user(s, c);
+ break;
+
+ case NGX_POP3_CAPA:
+ rc = ngx_mail_pop3_capa(s, c, 1);
+ break;
+
+ case NGX_POP3_APOP:
+ rc = ngx_mail_pop3_apop(s, c);
+ break;
+
+ case NGX_POP3_AUTH:
+ rc = ngx_mail_pop3_auth(s, c);
+ break;
+
+ case NGX_POP3_QUIT:
+ s->quit = 1;
+ break;
+
+ case NGX_POP3_NOOP:
+ break;
+
+ case NGX_POP3_STLS:
+ rc = ngx_mail_pop3_stls(s, c);
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ case ngx_pop3_user:
+
+ switch (s->command) {
+
+ case NGX_POP3_PASS:
+ rc = ngx_mail_pop3_pass(s, c);
+ break;
+
+ case NGX_POP3_CAPA:
+ rc = ngx_mail_pop3_capa(s, c, 0);
+ break;
+
+ case NGX_POP3_QUIT:
+ s->quit = 1;
+ break;
+
+ case NGX_POP3_NOOP:
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ /* suppress warnings */
+ case ngx_pop3_passwd:
+ break;
+
+ case ngx_pop3_auth_login_username:
+ rc = ngx_mail_auth_login_username(s, c, 0);
+
+ ngx_str_set(&s->out, pop3_password);
+ s->mail_state = ngx_pop3_auth_login_password;
+ break;
+
+ case ngx_pop3_auth_login_password:
+ rc = ngx_mail_auth_login_password(s, c);
+ break;
+
+ case ngx_pop3_auth_plain:
+ rc = ngx_mail_auth_plain(s, c, 0);
+ break;
+
+ case ngx_pop3_auth_cram_md5:
+ rc = ngx_mail_auth_cram_md5(s, c);
+ break;
+ }
+ }
+
+ switch (rc) {
+
+ case NGX_DONE:
+ ngx_mail_auth(s, c);
+ return;
+
+ case NGX_ERROR:
+ ngx_mail_session_internal_server_error(s);
+ return;
+
+ case NGX_MAIL_PARSE_INVALID_COMMAND:
+ s->mail_state = ngx_pop3_start;
+ s->state = 0;
+
+ ngx_str_set(&s->out, pop3_invalid_command);
+
+ /* fall through */
+
+ case NGX_OK:
+
+ s->args.nelts = 0;
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+
+ if (s->state) {
+ s->arg_start = s->buffer->start;
+ }
+
+ ngx_mail_send(c->write);
+ }
+}
+
+static ngx_int_t
+ngx_mail_pop3_user(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ if (s->args.nelts != 1) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ arg = s->args.elts;
+ s->login.len = arg[0].len;
+ s->login.data = ngx_pnalloc(c->pool, s->login.len);
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "pop3 login: \"%V\"", &s->login);
+
+ s->mail_state = ngx_pop3_user;
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_pass(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+
+ if (s->args.nelts != 1) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ arg = s->args.elts;
+ s->passwd.len = arg[0].len;
+ s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->passwd.data, arg[0].data, s->passwd.len);
+
+#if (NGX_DEBUG_MAIL_PASSWD)
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "pop3 passwd: \"%V\"", &s->passwd);
+#endif
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_capa(ngx_mail_session_t *s, ngx_connection_t *c, ngx_int_t stls)
+{
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+#if (NGX_MAIL_SSL)
+
+ if (stls && c->ssl == NULL) {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+ s->out = pscf->starttls_capability;
+ return NGX_OK;
+ }
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ s->out = pscf->starttls_only_capability;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ s->out = pscf->capability;
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_stls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl == NULL) {
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+ if (sslcf->starttls) {
+ c->read->handler = ngx_mail_starttls_handler;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_apop(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ if (s->args.nelts != 2) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+ if (!(pscf->auth_methods & NGX_MAIL_AUTH_APOP_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ arg = s->args.elts;
+
+ s->login.len = arg[0].len;
+ s->login.data = ngx_pnalloc(c->pool, s->login.len);
+ if (s->login.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+ s->passwd.len = arg[1].len;
+ s->passwd.data = ngx_pnalloc(c->pool, s->passwd.len);
+ if (s->passwd.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "pop3 apop: \"%V\" \"%V\"", &s->login, &s->passwd);
+
+ s->auth_method = NGX_MAIL_AUTH_APOP;
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_pop3_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_int_t rc;
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ pscf = ngx_mail_get_module_srv_conf(s, ngx_mail_pop3_module);
+
+ if (s->args.nelts == 0) {
+ s->out = pscf->auth_capability;
+ s->state = 0;
+
+ return NGX_OK;
+ }
+
+ rc = ngx_mail_auth_parse(s, c);
+
+ switch (rc) {
+
+ case NGX_MAIL_AUTH_LOGIN:
+
+ ngx_str_set(&s->out, pop3_username);
+ s->mail_state = ngx_pop3_auth_login_username;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+ ngx_str_set(&s->out, pop3_password);
+ s->mail_state = ngx_pop3_auth_login_password;
+
+ return ngx_mail_auth_login_username(s, c, 1);
+
+ case NGX_MAIL_AUTH_PLAIN:
+
+ ngx_str_set(&s->out, pop3_next);
+ s->mail_state = ngx_pop3_auth_plain;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_CRAM_MD5:
+
+ if (!(pscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (ngx_mail_auth_cram_md5_salt(s, c, "+ ", 2) == NGX_OK) {
+ s->mail_state = ngx_pop3_auth_cram_md5;
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+ }
+
+ return rc;
+}
diff --git a/src/mail/ngx_mail_pop3_module.c b/src/mail/ngx_mail_pop3_module.c
new file mode 100644
index 0000000..b597472
--- /dev/null
+++ b/src/mail/ngx_mail_pop3_module.c
@@ -0,0 +1,264 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+static void *ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_str_t ngx_mail_pop3_default_capabilities[] = {
+ ngx_string("TOP"),
+ ngx_string("USER"),
+ ngx_string("UIDL"),
+ ngx_null_string
+};
+
+
+static ngx_conf_bitmask_t ngx_mail_pop3_auth_methods[] = {
+ { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+ { ngx_string("apop"), NGX_MAIL_AUTH_APOP_ENABLED },
+ { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t ngx_mail_pop3_auth_plain_capability =
+ ngx_string("+OK methods supported:" CRLF
+ "LOGIN" CRLF
+ "PLAIN" CRLF
+ "." CRLF);
+
+
+static ngx_str_t ngx_mail_pop3_auth_cram_md5_capability =
+ ngx_string("+OK methods supported:" CRLF
+ "LOGIN" CRLF
+ "PLAIN" CRLF
+ "CRAM-MD5" CRLF
+ "." CRLF);
+
+
+static ngx_mail_protocol_t ngx_mail_pop3_protocol = {
+ ngx_string("pop3"),
+ { 110, 995, 0, 0 },
+ NGX_MAIL_POP3_PROTOCOL,
+
+ ngx_mail_pop3_init_session,
+ ngx_mail_pop3_init_protocol,
+ ngx_mail_pop3_parse_command,
+ ngx_mail_pop3_auth_state,
+
+ ngx_string("-ERR internal server error" CRLF)
+};
+
+
+static ngx_command_t ngx_mail_pop3_commands[] = {
+
+ { ngx_string("pop3_capabilities"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_mail_capabilities,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_pop3_srv_conf_t, capabilities),
+ NULL },
+
+ { ngx_string("pop3_auth"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_pop3_srv_conf_t, auth_methods),
+ &ngx_mail_pop3_auth_methods },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_pop3_module_ctx = {
+ &ngx_mail_pop3_protocol, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_pop3_create_srv_conf, /* create server configuration */
+ ngx_mail_pop3_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_pop3_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_pop3_module_ctx, /* module context */
+ ngx_mail_pop3_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_pop3_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_pop3_srv_conf_t *pscf;
+
+ pscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_pop3_srv_conf_t));
+ if (pscf == NULL) {
+ return NULL;
+ }
+
+ if (ngx_array_init(&pscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return pscf;
+}
+
+
+static char *
+ngx_mail_pop3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_pop3_srv_conf_t *prev = parent;
+ ngx_mail_pop3_srv_conf_t *conf = child;
+
+ u_char *p;
+ size_t size, stls_only_size;
+ ngx_str_t *c, *d;
+ ngx_uint_t i;
+
+ ngx_conf_merge_bitmask_value(conf->auth_methods,
+ prev->auth_methods,
+ (NGX_CONF_BITMASK_SET
+ |NGX_MAIL_AUTH_PLAIN_ENABLED));
+
+ if (conf->capabilities.nelts == 0) {
+ conf->capabilities = prev->capabilities;
+ }
+
+ if (conf->capabilities.nelts == 0) {
+
+ for (d = ngx_mail_pop3_default_capabilities; d->len; d++) {
+ c = ngx_array_push(&conf->capabilities);
+ if (c == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ *c = *d;
+ }
+ }
+
+ size = sizeof("+OK Capability list follows" CRLF) - 1
+ + sizeof("." CRLF) - 1;
+
+ stls_only_size = size + sizeof("STLS" CRLF) - 1;
+
+ c = conf->capabilities.elts;
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ size += c[i].len + sizeof(CRLF) - 1;
+
+ if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+ continue;
+ }
+
+ stls_only_size += c[i].len + sizeof(CRLF) - 1;
+ }
+
+ if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+ size += sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1;
+
+ } else {
+ size += sizeof("SASL LOGIN PLAIN" CRLF) - 1;
+ }
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->capability.len = size;
+ conf->capability.data = p;
+
+ p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+ sizeof("+OK Capability list follows" CRLF) - 1);
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ *p++ = CR; *p++ = LF;
+ }
+
+ if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+ p = ngx_cpymem(p, "SASL LOGIN PLAIN CRAM-MD5" CRLF,
+ sizeof("SASL LOGIN PLAIN CRAM-MD5" CRLF) - 1);
+
+ } else {
+ p = ngx_cpymem(p, "SASL LOGIN PLAIN" CRLF,
+ sizeof("SASL LOGIN PLAIN" CRLF) - 1);
+ }
+
+ *p++ = '.'; *p++ = CR; *p = LF;
+
+
+ size += sizeof("STLS" CRLF) - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_capability.len = size;
+ conf->starttls_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data,
+ conf->capability.len - (sizeof("." CRLF) - 1));
+
+ p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+ *p++ = '.'; *p++ = CR; *p = LF;
+
+
+ if (conf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED) {
+ conf->auth_capability = ngx_mail_pop3_auth_cram_md5_capability;
+
+ } else {
+ conf->auth_capability = ngx_mail_pop3_auth_plain_capability;
+ }
+
+
+ p = ngx_pnalloc(cf->pool, stls_only_size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_only_capability.len = stls_only_size;
+ conf->starttls_only_capability.data = p;
+
+ p = ngx_cpymem(p, "+OK Capability list follows" CRLF,
+ sizeof("+OK Capability list follows" CRLF) - 1);
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ if (ngx_strcasecmp(c[i].data, (u_char *) "USER") == 0) {
+ continue;
+ }
+
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ *p++ = CR; *p++ = LF;
+ }
+
+ p = ngx_cpymem(p, "STLS" CRLF, sizeof("STLS" CRLF) - 1);
+ *p++ = '.'; *p++ = CR; *p = LF;
+
+ return NGX_CONF_OK;
+}
diff --git a/src/mail/ngx_mail_pop3_module.h b/src/mail/ngx_mail_pop3_module.h
new file mode 100644
index 0000000..86947a7
--- /dev/null
+++ b/src/mail/ngx_mail_pop3_module.h
@@ -0,0 +1,38 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+#define _NGX_MAIL_POP3_MODULE_H_INCLUDED_
+
+
+#include
+#include
+#include
+
+
+typedef struct {
+ ngx_str_t capability;
+ ngx_str_t starttls_capability;
+ ngx_str_t starttls_only_capability;
+ ngx_str_t auth_capability;
+
+ ngx_uint_t auth_methods;
+
+ ngx_array_t capabilities;
+} ngx_mail_pop3_srv_conf_t;
+
+
+void ngx_mail_pop3_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_pop3_init_protocol(ngx_event_t *rev);
+void ngx_mail_pop3_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_pop3_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t ngx_mail_pop3_module;
+
+
+#endif /* _NGX_MAIL_POP3_MODULE_H_INCLUDED_ */
diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c
new file mode 100644
index 0000000..4ea608c
--- /dev/null
+++ b/src/mail/ngx_mail_proxy_module.c
@@ -0,0 +1,1089 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t pass_error_message;
+ ngx_flag_t xclient;
+ size_t buffer_size;
+ ngx_msec_t timeout;
+} ngx_mail_proxy_conf_t;
+
+
+static void ngx_mail_proxy_block_read(ngx_event_t *rev);
+static void ngx_mail_proxy_pop3_handler(ngx_event_t *rev);
+static void ngx_mail_proxy_imap_handler(ngx_event_t *rev);
+static void ngx_mail_proxy_smtp_handler(ngx_event_t *rev);
+static void ngx_mail_proxy_dummy_handler(ngx_event_t *ev);
+static ngx_int_t ngx_mail_proxy_read_response(ngx_mail_session_t *s,
+ ngx_uint_t state);
+static void ngx_mail_proxy_handler(ngx_event_t *ev);
+static void ngx_mail_proxy_upstream_error(ngx_mail_session_t *s);
+static void ngx_mail_proxy_internal_server_error(ngx_mail_session_t *s);
+static void ngx_mail_proxy_close_session(ngx_mail_session_t *s);
+static void *ngx_mail_proxy_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_proxy_merge_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_command_t ngx_mail_proxy_commands[] = {
+
+ { ngx_string("proxy"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, enable),
+ NULL },
+
+ { ngx_string("proxy_buffer"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, buffer_size),
+ NULL },
+
+ { ngx_string("proxy_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, timeout),
+ NULL },
+
+ { ngx_string("proxy_pass_error_message"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, pass_error_message),
+ NULL },
+
+ { ngx_string("xclient"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_proxy_conf_t, xclient),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_proxy_module_ctx = {
+ NULL, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_proxy_create_conf, /* create server configuration */
+ ngx_mail_proxy_merge_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_proxy_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_proxy_module_ctx, /* module context */
+ ngx_mail_proxy_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static u_char smtp_auth_ok[] = "235 2.0.0 OK" CRLF;
+
+
+void
+ngx_mail_proxy_init(ngx_mail_session_t *s, ngx_addr_t *peer)
+{
+ int keepalive;
+ ngx_int_t rc;
+ ngx_mail_proxy_ctx_t *p;
+ ngx_mail_proxy_conf_t *pcf;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ s->connection->log->action = "connecting to upstream";
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (cscf->so_keepalive) {
+ keepalive = 1;
+
+ if (setsockopt(s->connection->fd, SOL_SOCKET, SO_KEEPALIVE,
+ (const void *) &keepalive, sizeof(int))
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, s->connection->log, ngx_socket_errno,
+ "setsockopt(SO_KEEPALIVE) failed");
+ }
+ }
+
+ p = ngx_pcalloc(s->connection->pool, sizeof(ngx_mail_proxy_ctx_t));
+ if (p == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->proxy = p;
+
+ p->upstream.sockaddr = peer->sockaddr;
+ p->upstream.socklen = peer->socklen;
+ p->upstream.name = &peer->name;
+ p->upstream.get = ngx_event_get_peer;
+ p->upstream.log = s->connection->log;
+ p->upstream.log_error = NGX_ERROR_ERR;
+
+ rc = ngx_event_connect_peer(&p->upstream);
+
+ if (rc == NGX_ERROR || rc == NGX_BUSY || rc == NGX_DECLINED) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ ngx_add_timer(p->upstream.connection->read, cscf->timeout);
+
+ p->upstream.connection->data = s;
+ p->upstream.connection->pool = s->connection->pool;
+
+ s->connection->read->handler = ngx_mail_proxy_block_read;
+ p->upstream.connection->write->handler = ngx_mail_proxy_dummy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+ s->proxy->buffer = ngx_create_temp_buf(s->connection->pool,
+ pcf->buffer_size);
+ if (s->proxy->buffer == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->out.len = 0;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ p->upstream.connection->read->handler = ngx_mail_proxy_pop3_handler;
+ s->mail_state = ngx_pop3_start;
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ p->upstream.connection->read->handler = ngx_mail_proxy_imap_handler;
+ s->mail_state = ngx_imap_start;
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ p->upstream.connection->read->handler = ngx_mail_proxy_smtp_handler;
+ s->mail_state = ngx_smtp_start;
+ break;
+ }
+}
+
+
+static void
+ngx_mail_proxy_block_read(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy block read");
+
+ if (ngx_handle_read_event(rev, 0) != NGX_OK) {
+ c = rev->data;
+ s = c->data;
+
+ ngx_mail_proxy_close_session(s);
+ }
+}
+
+
+static void
+ngx_mail_proxy_pop3_handler(ngx_event_t *rev)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t line;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy pop3 auth handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ c->timedout = 1;
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ rc = ngx_mail_proxy_read_response(s, 0);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_proxy_upstream_error(s);
+ return;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_pop3_start:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send user");
+
+ s->connection->log->action = "sending user name to upstream";
+
+ line.len = sizeof("USER ") - 1 + s->login.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, "USER ", sizeof("USER ") - 1);
+ p = ngx_cpymem(p, s->login.data, s->login.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_pop3_user;
+ break;
+
+ case ngx_pop3_user:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send pass");
+
+ s->connection->log->action = "sending password to upstream";
+
+ line.len = sizeof("PASS ") - 1 + s->passwd.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, "PASS ", sizeof("PASS ") - 1);
+ p = ngx_cpymem(p, s->passwd.data, s->passwd.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_pop3_passwd;
+ break;
+
+ case ngx_pop3_passwd:
+ s->connection->read->handler = ngx_mail_proxy_handler;
+ s->connection->write->handler = ngx_mail_proxy_handler;
+ rev->handler = ngx_mail_proxy_handler;
+ c->write->handler = ngx_mail_proxy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(s->connection->read, pcf->timeout);
+ ngx_del_timer(c->read);
+
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+ ngx_mail_proxy_handler(s->connection->write);
+
+ return;
+
+ default:
+#if (NGX_SUPPRESS_WARN)
+ ngx_str_null(&line);
+#endif
+ break;
+ }
+
+ if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+ /*
+ * we treat the incomplete sending as NGX_ERROR
+ * because it is very strange here
+ */
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->proxy->buffer->pos = s->proxy->buffer->start;
+ s->proxy->buffer->last = s->proxy->buffer->start;
+}
+
+
+static void
+ngx_mail_proxy_imap_handler(ngx_event_t *rev)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t line;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy imap auth handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ c->timedout = 1;
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ rc = ngx_mail_proxy_read_response(s, s->mail_state);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_proxy_upstream_error(s);
+ return;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_imap_start:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send login");
+
+ s->connection->log->action = "sending LOGIN command to upstream";
+
+ line.len = s->tag.len + sizeof("LOGIN ") - 1
+ + 1 + NGX_SIZE_T_LEN + 1 + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data, "%VLOGIN {%uz}" CRLF,
+ &s->tag, s->login.len)
+ - line.data;
+
+ s->mail_state = ngx_imap_login;
+ break;
+
+ case ngx_imap_login:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send user");
+
+ s->connection->log->action = "sending user name to upstream";
+
+ line.len = s->login.len + 1 + 1 + NGX_SIZE_T_LEN + 1 + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data, "%V {%uz}" CRLF,
+ &s->login, s->passwd.len)
+ - line.data;
+
+ s->mail_state = ngx_imap_user;
+ break;
+
+ case ngx_imap_user:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send passwd");
+
+ s->connection->log->action = "sending password to upstream";
+
+ line.len = s->passwd.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, s->passwd.data, s->passwd.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_imap_passwd;
+ break;
+
+ case ngx_imap_passwd:
+ s->connection->read->handler = ngx_mail_proxy_handler;
+ s->connection->write->handler = ngx_mail_proxy_handler;
+ rev->handler = ngx_mail_proxy_handler;
+ c->write->handler = ngx_mail_proxy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(s->connection->read, pcf->timeout);
+ ngx_del_timer(c->read);
+
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+ ngx_mail_proxy_handler(s->connection->write);
+
+ return;
+
+ default:
+#if (NGX_SUPPRESS_WARN)
+ ngx_str_null(&line);
+#endif
+ break;
+ }
+
+ if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+ /*
+ * we treat the incomplete sending as NGX_ERROR
+ * because it is very strange here
+ */
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->proxy->buffer->pos = s->proxy->buffer->start;
+ s->proxy->buffer->last = s->proxy->buffer->start;
+}
+
+
+static void
+ngx_mail_proxy_smtp_handler(ngx_event_t *rev)
+{
+ u_char *p;
+ ngx_int_t rc;
+ ngx_str_t line;
+ ngx_buf_t *b;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy smtp auth handler");
+
+ c = rev->data;
+ s = c->data;
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ c->timedout = 1;
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ rc = ngx_mail_proxy_read_response(s, s->mail_state);
+
+ if (rc == NGX_AGAIN) {
+ return;
+ }
+
+ if (rc == NGX_ERROR) {
+ ngx_mail_proxy_upstream_error(s);
+ return;
+ }
+
+ switch (s->mail_state) {
+
+ case ngx_smtp_start:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0, "mail proxy send ehlo");
+
+ s->connection->log->action = "sending HELO/EHLO to upstream";
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ line.len = sizeof("HELO ") - 1 + cscf->server_name.len + 2;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+ p = ngx_cpymem(line.data,
+ ((s->esmtp || pcf->xclient) ? "EHLO " : "HELO "),
+ sizeof("HELO ") - 1);
+
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ *p++ = CR; *p = LF;
+
+ if (pcf->xclient) {
+ s->mail_state = ngx_smtp_helo_xclient;
+
+ } else if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+ s->mail_state = ngx_smtp_helo_from;
+
+ } else {
+ s->mail_state = ngx_smtp_helo;
+ }
+
+ break;
+
+ case ngx_smtp_helo_xclient:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send xclient");
+
+ s->connection->log->action = "sending XCLIENT to upstream";
+
+ line.len = sizeof("XCLIENT ADDR= LOGIN= NAME="
+ CRLF) - 1
+ + s->connection->addr_text.len + s->login.len + s->host.len;
+
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data,
+ "XCLIENT ADDR=%V%s%V NAME=%V" CRLF,
+ &s->connection->addr_text,
+ (s->login.len ? " LOGIN=" : ""), &s->login, &s->host)
+ - line.data;
+
+ if (s->smtp_helo.len) {
+ s->mail_state = ngx_smtp_xclient_helo;
+
+ } else if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+ s->mail_state = ngx_smtp_xclient_from;
+
+ } else {
+ s->mail_state = ngx_smtp_xclient;
+ }
+
+ break;
+
+ case ngx_smtp_xclient_helo:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send client ehlo");
+
+ s->connection->log->action = "sending client HELO/EHLO to upstream";
+
+ line.len = sizeof("HELO " CRLF) - 1 + s->smtp_helo.len;
+
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ line.len = ngx_sprintf(line.data,
+ ((s->esmtp) ? "EHLO %V" CRLF : "HELO %V" CRLF),
+ &s->smtp_helo)
+ - line.data;
+
+ s->mail_state = (s->auth_method == NGX_MAIL_AUTH_NONE) ?
+ ngx_smtp_helo_from : ngx_smtp_helo;
+
+ break;
+
+ case ngx_smtp_helo_from:
+ case ngx_smtp_xclient_from:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send mail from");
+
+ s->connection->log->action = "sending MAIL FROM to upstream";
+
+ line.len = s->smtp_from.len + sizeof(CRLF) - 1;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, s->smtp_from.data, s->smtp_from.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_smtp_from;
+
+ break;
+
+ case ngx_smtp_from:
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, rev->log, 0,
+ "mail proxy send rcpt to");
+
+ s->connection->log->action = "sending RCPT TO to upstream";
+
+ line.len = s->smtp_to.len + sizeof(CRLF) - 1;
+ line.data = ngx_pnalloc(c->pool, line.len);
+ if (line.data == NULL) {
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ p = ngx_cpymem(line.data, s->smtp_to.data, s->smtp_to.len);
+ *p++ = CR; *p = LF;
+
+ s->mail_state = ngx_smtp_to;
+
+ break;
+
+ case ngx_smtp_helo:
+ case ngx_smtp_xclient:
+ case ngx_smtp_to:
+
+ b = s->proxy->buffer;
+
+ if (s->auth_method == NGX_MAIL_AUTH_NONE) {
+ b->pos = b->start;
+
+ } else {
+ ngx_memcpy(b->start, smtp_auth_ok, sizeof(smtp_auth_ok) - 1);
+ b->last = b->start + sizeof(smtp_auth_ok) - 1;
+ }
+
+ s->connection->read->handler = ngx_mail_proxy_handler;
+ s->connection->write->handler = ngx_mail_proxy_handler;
+ rev->handler = ngx_mail_proxy_handler;
+ c->write->handler = ngx_mail_proxy_handler;
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(s->connection->read, pcf->timeout);
+ ngx_del_timer(c->read);
+
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in");
+
+ ngx_mail_proxy_handler(s->connection->write);
+
+ return;
+
+ default:
+#if (NGX_SUPPRESS_WARN)
+ ngx_str_null(&line);
+#endif
+ break;
+ }
+
+ if (c->send(c, line.data, line.len) < (ssize_t) line.len) {
+ /*
+ * we treat the incomplete sending as NGX_ERROR
+ * because it is very strange here
+ */
+ ngx_mail_proxy_internal_server_error(s);
+ return;
+ }
+
+ s->proxy->buffer->pos = s->proxy->buffer->start;
+ s->proxy->buffer->last = s->proxy->buffer->start;
+}
+
+
+static void
+ngx_mail_proxy_dummy_handler(ngx_event_t *wev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, wev->log, 0, "mail proxy dummy handler");
+
+ if (ngx_handle_write_event(wev, 0) != NGX_OK) {
+ c = wev->data;
+ s = c->data;
+
+ ngx_mail_proxy_close_session(s);
+ }
+}
+
+
+static ngx_int_t
+ngx_mail_proxy_read_response(ngx_mail_session_t *s, ngx_uint_t state)
+{
+ u_char *p;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_mail_proxy_conf_t *pcf;
+
+ s->connection->log->action = "reading response from upstream";
+
+ b = s->proxy->buffer;
+
+ n = s->proxy->upstream.connection->recv(s->proxy->upstream.connection,
+ b->last, b->end - b->last);
+
+ if (n == NGX_ERROR || n == 0) {
+ return NGX_ERROR;
+ }
+
+ if (n == NGX_AGAIN) {
+ return NGX_AGAIN;
+ }
+
+ b->last += n;
+
+ if (b->last - b->pos < 4) {
+ return NGX_AGAIN;
+ }
+
+ if (*(b->last - 2) != CR || *(b->last - 1) != LF) {
+ if (b->last == b->end) {
+ *(b->last - 1) = '\0';
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "upstream sent too long response line: \"%s\"",
+ b->pos);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ p = b->pos;
+
+ switch (s->protocol) {
+
+ case NGX_MAIL_POP3_PROTOCOL:
+ if (p[0] == '+' && p[1] == 'O' && p[2] == 'K') {
+ return NGX_OK;
+ }
+ break;
+
+ case NGX_MAIL_IMAP_PROTOCOL:
+ switch (state) {
+
+ case ngx_imap_start:
+ if (p[0] == '*' && p[1] == ' ' && p[2] == 'O' && p[3] == 'K') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_imap_login:
+ case ngx_imap_user:
+ if (p[0] == '+') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_imap_passwd:
+ if (ngx_strncmp(p, s->tag.data, s->tag.len) == 0) {
+ p += s->tag.len;
+ if (p[0] == 'O' && p[1] == 'K') {
+ return NGX_OK;
+ }
+ }
+ break;
+ }
+
+ break;
+
+ default: /* NGX_MAIL_SMTP_PROTOCOL */
+ switch (state) {
+
+ case ngx_smtp_start:
+ if (p[0] == '2' && p[1] == '2' && p[2] == '0') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_smtp_helo:
+ case ngx_smtp_helo_xclient:
+ case ngx_smtp_helo_from:
+ case ngx_smtp_from:
+ if (p[0] == '2' && p[1] == '5' && p[2] == '0') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_smtp_xclient:
+ case ngx_smtp_xclient_from:
+ case ngx_smtp_xclient_helo:
+ if (p[0] == '2' && (p[1] == '2' || p[1] == '5') && p[2] == '0') {
+ return NGX_OK;
+ }
+ break;
+
+ case ngx_smtp_to:
+ return NGX_OK;
+ }
+
+ break;
+ }
+
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+
+ if (pcf->pass_error_message == 0) {
+ *(b->last - 2) = '\0';
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "upstream sent invalid response: \"%s\"", p);
+ return NGX_ERROR;
+ }
+
+ s->out.len = b->last - p - 2;
+ s->out.data = p;
+
+ ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
+ "upstream sent invalid response: \"%V\"", &s->out);
+
+ s->out.len = b->last - b->pos;
+ s->out.data = b->pos;
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_mail_proxy_handler(ngx_event_t *ev)
+{
+ char *action, *recv_action, *send_action;
+ size_t size;
+ ssize_t n;
+ ngx_buf_t *b;
+ ngx_uint_t do_write;
+ ngx_connection_t *c, *src, *dst;
+ ngx_mail_session_t *s;
+ ngx_mail_proxy_conf_t *pcf;
+
+ c = ev->data;
+ s = c->data;
+
+ if (ev->timedout) {
+ c->log->action = "proxying";
+
+ if (c == s->connection) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "client timed out");
+ c->timedout = 1;
+
+ } else {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ }
+
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (c == s->connection) {
+ if (ev->write) {
+ recv_action = "proxying and reading from upstream";
+ send_action = "proxying and sending to client";
+ src = s->proxy->upstream.connection;
+ dst = c;
+ b = s->proxy->buffer;
+
+ } else {
+ recv_action = "proxying and reading from client";
+ send_action = "proxying and sending to upstream";
+ src = c;
+ dst = s->proxy->upstream.connection;
+ b = s->buffer;
+ }
+
+ } else {
+ if (ev->write) {
+ recv_action = "proxying and reading from client";
+ send_action = "proxying and sending to upstream";
+ src = s->connection;
+ dst = c;
+ b = s->buffer;
+
+ } else {
+ recv_action = "proxying and reading from upstream";
+ send_action = "proxying and sending to client";
+ src = c;
+ dst = s->connection;
+ b = s->proxy->buffer;
+ }
+ }
+
+ do_write = ev->write ? 1 : 0;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_MAIL, ev->log, 0,
+ "mail proxy handler: %d, #%d > #%d",
+ do_write, src->fd, dst->fd);
+
+ for ( ;; ) {
+
+ if (do_write) {
+
+ size = b->last - b->pos;
+
+ if (size && dst->write->ready) {
+ c->log->action = send_action;
+
+ n = dst->send(dst, b->pos, size);
+
+ if (n == NGX_ERROR) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (n > 0) {
+ b->pos += n;
+
+ if (b->pos == b->last) {
+ b->pos = b->start;
+ b->last = b->start;
+ }
+ }
+ }
+ }
+
+ size = b->end - b->last;
+
+ if (size && src->read->ready) {
+ c->log->action = recv_action;
+
+ n = src->recv(src, b->last, size);
+
+ if (n == NGX_AGAIN || n == 0) {
+ break;
+ }
+
+ if (n > 0) {
+ do_write = 1;
+ b->last += n;
+
+ continue;
+ }
+
+ if (n == NGX_ERROR) {
+ src->read->eof = 1;
+ }
+ }
+
+ break;
+ }
+
+ c->log->action = "proxying";
+
+ if ((s->connection->read->eof && s->buffer->pos == s->buffer->last)
+ || (s->proxy->upstream.connection->read->eof
+ && s->proxy->buffer->pos == s->proxy->buffer->last)
+ || (s->connection->read->eof
+ && s->proxy->upstream.connection->read->eof))
+ {
+ action = c->log->action;
+ c->log->action = NULL;
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, "proxied session done");
+ c->log->action = action;
+
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_write_event(dst->write, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_read_event(dst->read, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_write_event(src->write, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (ngx_handle_read_event(src->read, 0) != NGX_OK) {
+ ngx_mail_proxy_close_session(s);
+ return;
+ }
+
+ if (c == s->connection) {
+ pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
+ ngx_add_timer(c->read, pcf->timeout);
+ }
+}
+
+
+static void
+ngx_mail_proxy_upstream_error(ngx_mail_session_t *s)
+{
+ if (s->proxy->upstream.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "close mail proxy connection: %d",
+ s->proxy->upstream.connection->fd);
+
+ ngx_close_connection(s->proxy->upstream.connection);
+ }
+
+ if (s->out.len == 0) {
+ ngx_mail_session_internal_server_error(s);
+ return;
+ }
+
+ s->quit = 1;
+ ngx_mail_send(s->connection->write);
+}
+
+
+static void
+ngx_mail_proxy_internal_server_error(ngx_mail_session_t *s)
+{
+ if (s->proxy->upstream.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "close mail proxy connection: %d",
+ s->proxy->upstream.connection->fd);
+
+ ngx_close_connection(s->proxy->upstream.connection);
+ }
+
+ ngx_mail_session_internal_server_error(s);
+}
+
+
+static void
+ngx_mail_proxy_close_session(ngx_mail_session_t *s)
+{
+ if (s->proxy->upstream.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, s->connection->log, 0,
+ "close mail proxy connection: %d",
+ s->proxy->upstream.connection->fd);
+
+ ngx_close_connection(s->proxy->upstream.connection);
+ }
+
+ ngx_mail_close_connection(s->connection);
+}
+
+
+static void *
+ngx_mail_proxy_create_conf(ngx_conf_t *cf)
+{
+ ngx_mail_proxy_conf_t *pcf;
+
+ pcf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_proxy_conf_t));
+ if (pcf == NULL) {
+ return NULL;
+ }
+
+ pcf->enable = NGX_CONF_UNSET;
+ pcf->pass_error_message = NGX_CONF_UNSET;
+ pcf->xclient = NGX_CONF_UNSET;
+ pcf->buffer_size = NGX_CONF_UNSET_SIZE;
+ pcf->timeout = NGX_CONF_UNSET_MSEC;
+
+ return pcf;
+}
+
+
+static char *
+ngx_mail_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_proxy_conf_t *prev = parent;
+ ngx_mail_proxy_conf_t *conf = child;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_value(conf->pass_error_message, prev->pass_error_message, 0);
+ ngx_conf_merge_value(conf->xclient, prev->xclient, 1);
+ ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size,
+ (size_t) ngx_pagesize);
+ ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 24 * 60 * 60000);
+
+ return NGX_CONF_OK;
+}
diff --git a/src/mail/ngx_mail_smtp_handler.c b/src/mail/ngx_mail_smtp_handler.c
new file mode 100644
index 0000000..2171423
--- /dev/null
+++ b/src/mail/ngx_mail_smtp_handler.c
@@ -0,0 +1,872 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+static void ngx_mail_smtp_resolve_addr_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_mail_smtp_resolve_name(ngx_event_t *rev);
+static void ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx);
+static void ngx_mail_smtp_greeting(ngx_mail_session_t *s, ngx_connection_t *c);
+static void ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev);
+static ngx_int_t ngx_mail_smtp_create_buffer(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_starttls(ngx_mail_session_t *s,
+ ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_rset(ngx_mail_session_t *s, ngx_connection_t *c);
+static ngx_int_t ngx_mail_smtp_rcpt(ngx_mail_session_t *s, ngx_connection_t *c);
+
+static ngx_int_t ngx_mail_smtp_discard_command(ngx_mail_session_t *s,
+ ngx_connection_t *c, char *err);
+static void ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s,
+ ngx_connection_t *c, char *err);
+
+
+static u_char smtp_ok[] = "250 2.0.0 OK" CRLF;
+static u_char smtp_bye[] = "221 2.0.0 Bye" CRLF;
+static u_char smtp_starttls[] = "220 2.0.0 Start TLS" CRLF;
+static u_char smtp_next[] = "334 " CRLF;
+static u_char smtp_username[] = "334 VXNlcm5hbWU6" CRLF;
+static u_char smtp_password[] = "334 UGFzc3dvcmQ6" CRLF;
+static u_char smtp_invalid_command[] = "500 5.5.1 Invalid command" CRLF;
+static u_char smtp_invalid_pipelining[] =
+ "503 5.5.0 Improper use of SMTP command pipelining" CRLF;
+static u_char smtp_invalid_argument[] = "501 5.5.4 Invalid argument" CRLF;
+static u_char smtp_auth_required[] = "530 5.7.1 Authentication required" CRLF;
+static u_char smtp_bad_sequence[] = "503 5.5.1 Bad sequence of commands" CRLF;
+
+
+static ngx_str_t smtp_unavailable = ngx_string("[UNAVAILABLE]");
+static ngx_str_t smtp_tempunavail = ngx_string("[TEMPUNAVAIL]");
+
+
+void
+ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ struct sockaddr_in *sin;
+ ngx_resolver_ctx_t *ctx;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (cscf->resolver == NULL) {
+ s->host = smtp_unavailable;
+ ngx_mail_smtp_greeting(s, c);
+ return;
+ }
+
+ if (c->sockaddr->sa_family != AF_INET) {
+ s->host = smtp_tempunavail;
+ ngx_mail_smtp_greeting(s, c);
+ return;
+ }
+
+ c->log->action = "in resolving client address";
+
+ ctx = ngx_resolve_start(cscf->resolver, NULL);
+ if (ctx == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ /* AF_INET only */
+
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ ctx->addr = sin->sin_addr.s_addr;
+ ctx->handler = ngx_mail_smtp_resolve_addr_handler;
+ ctx->data = s;
+ ctx->timeout = cscf->resolver_timeout;
+
+ if (ngx_resolve_addr(ctx) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+}
+
+
+static void
+ngx_mail_smtp_resolve_addr_handler(ngx_resolver_ctx_t *ctx)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ s = ctx->data;
+ c = s->connection;
+
+ if (ctx->state) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "%V could not be resolved (%i: %s)",
+ &c->addr_text, ctx->state,
+ ngx_resolver_strerror(ctx->state));
+
+ if (ctx->state == NGX_RESOLVE_NXDOMAIN) {
+ s->host = smtp_unavailable;
+
+ } else {
+ s->host = smtp_tempunavail;
+ }
+
+ ngx_resolve_addr_done(ctx);
+
+ ngx_mail_smtp_greeting(s, s->connection);
+
+ return;
+ }
+
+ c->log->action = "in resolving client hostname";
+
+ s->host.data = ngx_pstrdup(c->pool, &ctx->name);
+ if (s->host.data == NULL) {
+ ngx_resolve_addr_done(ctx);
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s->host.len = ctx->name.len;
+
+ ngx_resolve_addr_done(ctx);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "address resolved: %V", &s->host);
+
+ c->read->handler = ngx_mail_smtp_resolve_name;
+
+ ngx_post_event(c->read, &ngx_posted_events);
+}
+
+
+static void
+ngx_mail_smtp_resolve_name(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_resolver_ctx_t *ctx;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ c = rev->data;
+ s = c->data;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ ctx = ngx_resolve_start(cscf->resolver, NULL);
+ if (ctx == NULL) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ ctx->name = s->host;
+ ctx->type = NGX_RESOLVE_A;
+ ctx->handler = ngx_mail_smtp_resolve_name_handler;
+ ctx->data = s;
+ ctx->timeout = cscf->resolver_timeout;
+
+ if (ngx_resolve_name(ctx) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+}
+
+
+static void
+ngx_mail_smtp_resolve_name_handler(ngx_resolver_ctx_t *ctx)
+{
+ in_addr_t addr;
+ ngx_uint_t i;
+ ngx_connection_t *c;
+ struct sockaddr_in *sin;
+ ngx_mail_session_t *s;
+
+ s = ctx->data;
+ c = s->connection;
+
+ if (ctx->state) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "\"%V\" could not be resolved (%i: %s)",
+ &ctx->name, ctx->state,
+ ngx_resolver_strerror(ctx->state));
+
+ if (ctx->state == NGX_RESOLVE_NXDOMAIN) {
+ s->host = smtp_unavailable;
+
+ } else {
+ s->host = smtp_tempunavail;
+ }
+
+ } else {
+
+ /* AF_INET only */
+
+ sin = (struct sockaddr_in *) c->sockaddr;
+
+ for (i = 0; i < ctx->naddrs; i++) {
+
+ addr = ctx->addrs[i];
+
+ ngx_log_debug4(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "name was resolved to %ud.%ud.%ud.%ud",
+ (ntohl(addr) >> 24) & 0xff,
+ (ntohl(addr) >> 16) & 0xff,
+ (ntohl(addr) >> 8) & 0xff,
+ ntohl(addr) & 0xff);
+
+ if (addr == sin->sin_addr.s_addr) {
+ goto found;
+ }
+ }
+
+ s->host = smtp_unavailable;
+ }
+
+found:
+
+ ngx_resolve_name_done(ctx);
+
+ ngx_mail_smtp_greeting(s, c);
+}
+
+
+static void
+ngx_mail_smtp_greeting(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_msec_t timeout;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "smtp greeting for \"%V\"", &s->host);
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ timeout = sscf->greeting_delay ? sscf->greeting_delay : cscf->timeout;
+ ngx_add_timer(c->read, timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ }
+
+ if (sscf->greeting_delay) {
+ c->read->handler = ngx_mail_smtp_invalid_pipelining;
+ return;
+ }
+
+ c->read->handler = ngx_mail_smtp_init_protocol;
+
+ s->out = sscf->greeting;
+
+ ngx_mail_send(c->write);
+}
+
+
+static void
+ngx_mail_smtp_invalid_pipelining(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ c = rev->data;
+ s = c->data;
+
+ c->log->action = "in delay pipelining state";
+
+ if (rev->timedout) {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "delay greeting");
+
+ rev->timedout = 0;
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ c->read->handler = ngx_mail_smtp_init_protocol;
+
+ ngx_add_timer(c->read, cscf->timeout);
+
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ s->out = sscf->greeting;
+
+ } else {
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "invalid pipelining");
+
+ if (s->buffer == NULL) {
+ if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+ return;
+ }
+ }
+
+ if (ngx_mail_smtp_discard_command(s, c,
+ "client was rejected before greeting: \"%V\"")
+ != NGX_OK)
+ {
+ return;
+ }
+
+ ngx_str_set(&s->out, smtp_invalid_pipelining);
+ }
+
+ ngx_mail_send(c->write);
+}
+
+
+void
+ngx_mail_smtp_init_protocol(ngx_event_t *rev)
+{
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+
+ c->log->action = "in auth state";
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ s = c->data;
+
+ if (s->buffer == NULL) {
+ if (ngx_mail_smtp_create_buffer(s, c) != NGX_OK) {
+ return;
+ }
+ }
+
+ s->mail_state = ngx_smtp_start;
+ c->read->handler = ngx_mail_smtp_auth_state;
+
+ ngx_mail_smtp_auth_state(rev);
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_create_buffer(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t)) == NGX_ERROR) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ s->buffer = ngx_create_temp_buf(c->pool, sscf->client_buffer_size);
+ if (s->buffer == NULL) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_mail_smtp_auth_state(ngx_event_t *rev)
+{
+ ngx_int_t rc;
+ ngx_connection_t *c;
+ ngx_mail_session_t *s;
+
+ c = rev->data;
+ s = c->data;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp auth state");
+
+ if (rev->timedout) {
+ ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+ c->timedout = 1;
+ ngx_mail_close_connection(c);
+ return;
+ }
+
+ if (s->out.len) {
+ ngx_log_debug0(NGX_LOG_DEBUG_MAIL, c->log, 0, "smtp send handler busy");
+ s->blocked = 1;
+ return;
+ }
+
+ s->blocked = 0;
+
+ rc = ngx_mail_read_command(s, c);
+
+ if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+ return;
+ }
+
+ ngx_str_set(&s->out, smtp_ok);
+
+ if (rc == NGX_OK) {
+ switch (s->mail_state) {
+
+ case ngx_smtp_start:
+
+ switch (s->command) {
+
+ case NGX_SMTP_HELO:
+ case NGX_SMTP_EHLO:
+ rc = ngx_mail_smtp_helo(s, c);
+ break;
+
+ case NGX_SMTP_AUTH:
+ rc = ngx_mail_smtp_auth(s, c);
+ break;
+
+ case NGX_SMTP_QUIT:
+ s->quit = 1;
+ ngx_str_set(&s->out, smtp_bye);
+ break;
+
+ case NGX_SMTP_MAIL:
+ rc = ngx_mail_smtp_mail(s, c);
+ break;
+
+ case NGX_SMTP_RCPT:
+ rc = ngx_mail_smtp_rcpt(s, c);
+ break;
+
+ case NGX_SMTP_RSET:
+ rc = ngx_mail_smtp_rset(s, c);
+ break;
+
+ case NGX_SMTP_NOOP:
+ break;
+
+ case NGX_SMTP_STARTTLS:
+ rc = ngx_mail_smtp_starttls(s, c);
+ ngx_str_set(&s->out, smtp_starttls);
+ break;
+
+ default:
+ rc = NGX_MAIL_PARSE_INVALID_COMMAND;
+ break;
+ }
+
+ break;
+
+ case ngx_smtp_auth_login_username:
+ rc = ngx_mail_auth_login_username(s, c, 0);
+
+ ngx_str_set(&s->out, smtp_password);
+ s->mail_state = ngx_smtp_auth_login_password;
+ break;
+
+ case ngx_smtp_auth_login_password:
+ rc = ngx_mail_auth_login_password(s, c);
+ break;
+
+ case ngx_smtp_auth_plain:
+ rc = ngx_mail_auth_plain(s, c, 0);
+ break;
+
+ case ngx_smtp_auth_cram_md5:
+ rc = ngx_mail_auth_cram_md5(s, c);
+ break;
+ }
+ }
+
+ switch (rc) {
+
+ case NGX_DONE:
+ ngx_mail_auth(s, c);
+ return;
+
+ case NGX_ERROR:
+ ngx_mail_session_internal_server_error(s);
+ return;
+
+ case NGX_MAIL_PARSE_INVALID_COMMAND:
+ s->mail_state = ngx_smtp_start;
+ s->state = 0;
+ ngx_str_set(&s->out, smtp_invalid_command);
+
+ /* fall through */
+
+ case NGX_OK:
+ s->args.nelts = 0;
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+
+ if (s->state) {
+ s->arg_start = s->buffer->start;
+ }
+
+ ngx_mail_send(c->write);
+ }
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_helo(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_t *arg;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ if (s->args.nelts != 1) {
+ ngx_str_set(&s->out, smtp_invalid_argument);
+ s->state = 0;
+ return NGX_OK;
+ }
+
+ arg = s->args.elts;
+
+ s->smtp_helo.len = arg[0].len;
+
+ s->smtp_helo.data = ngx_pnalloc(c->pool, arg[0].len);
+ if (s->smtp_helo.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->smtp_helo.data, arg[0].data, arg[0].len);
+
+ ngx_str_null(&s->smtp_from);
+ ngx_str_null(&s->smtp_to);
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ if (s->command == NGX_SMTP_HELO) {
+ s->out = sscf->server_name;
+
+ } else {
+ s->esmtp = 1;
+
+#if (NGX_MAIL_SSL)
+
+ if (c->ssl == NULL) {
+ ngx_mail_ssl_conf_t *sslcf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ON) {
+ s->out = sscf->starttls_capability;
+ return NGX_OK;
+ }
+
+ if (sslcf->starttls == NGX_MAIL_STARTTLS_ONLY) {
+ s->out = sscf->starttls_only_capability;
+ return NGX_OK;
+ }
+ }
+#endif
+
+ s->out = sscf->capability;
+ }
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_auth(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_int_t rc;
+ ngx_mail_core_srv_conf_t *cscf;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+#if (NGX_MAIL_SSL)
+ if (ngx_mail_starttls_only(s, c)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+#endif
+
+ if (s->args.nelts == 0) {
+ ngx_str_set(&s->out, smtp_invalid_argument);
+ s->state = 0;
+ return NGX_OK;
+ }
+
+ rc = ngx_mail_auth_parse(s, c);
+
+ switch (rc) {
+
+ case NGX_MAIL_AUTH_LOGIN:
+
+ ngx_str_set(&s->out, smtp_username);
+ s->mail_state = ngx_smtp_auth_login_username;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_LOGIN_USERNAME:
+
+ ngx_str_set(&s->out, smtp_password);
+ s->mail_state = ngx_smtp_auth_login_password;
+
+ return ngx_mail_auth_login_username(s, c, 1);
+
+ case NGX_MAIL_AUTH_PLAIN:
+
+ ngx_str_set(&s->out, smtp_next);
+ s->mail_state = ngx_smtp_auth_plain;
+
+ return NGX_OK;
+
+ case NGX_MAIL_AUTH_CRAM_MD5:
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ if (!(sscf->auth_methods & NGX_MAIL_AUTH_CRAM_MD5_ENABLED)) {
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+ }
+
+ if (s->salt.data == NULL) {
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ if (ngx_mail_salt(s, c, cscf) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ }
+
+ if (ngx_mail_auth_cram_md5_salt(s, c, "334 ", 4) == NGX_OK) {
+ s->mail_state = ngx_smtp_auth_cram_md5;
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+ }
+
+ return rc;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_mail(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char ch;
+ ngx_str_t l;
+ ngx_uint_t i;
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ sscf = ngx_mail_get_module_srv_conf(s, ngx_mail_smtp_module);
+
+ if (!(sscf->auth_methods & NGX_MAIL_AUTH_NONE_ENABLED)) {
+ ngx_mail_smtp_log_rejected_command(s, c, "client was rejected: \"%V\"");
+ ngx_str_set(&s->out, smtp_auth_required);
+ return NGX_OK;
+ }
+
+ /* auth none */
+
+ if (s->smtp_from.len) {
+ ngx_str_set(&s->out, smtp_bad_sequence);
+ return NGX_OK;
+ }
+
+ l.len = s->buffer->last - s->buffer->start;
+ l.data = s->buffer->start;
+
+ for (i = 0; i < l.len; i++) {
+ ch = l.data[i];
+
+ if (ch != CR && ch != LF) {
+ continue;
+ }
+
+ l.data[i] = ' ';
+ }
+
+ while (i) {
+ if (l.data[i - 1] != ' ') {
+ break;
+ }
+
+ i--;
+ }
+
+ l.len = i;
+
+ s->smtp_from.len = l.len;
+
+ s->smtp_from.data = ngx_pnalloc(c->pool, l.len);
+ if (s->smtp_from.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->smtp_from.data, l.data, l.len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "smtp mail from:\"%V\"", &s->smtp_from);
+
+ ngx_str_set(&s->out, smtp_ok);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_rcpt(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ u_char ch;
+ ngx_str_t l;
+ ngx_uint_t i;
+
+ if (s->smtp_from.len == 0) {
+ ngx_str_set(&s->out, smtp_bad_sequence);
+ return NGX_OK;
+ }
+
+ l.len = s->buffer->last - s->buffer->start;
+ l.data = s->buffer->start;
+
+ for (i = 0; i < l.len; i++) {
+ ch = l.data[i];
+
+ if (ch != CR && ch != LF) {
+ continue;
+ }
+
+ l.data[i] = ' ';
+ }
+
+ while (i) {
+ if (l.data[i - 1] != ' ') {
+ break;
+ }
+
+ i--;
+ }
+
+ l.len = i;
+
+ s->smtp_to.len = l.len;
+
+ s->smtp_to.data = ngx_pnalloc(c->pool, l.len);
+ if (s->smtp_to.data == NULL) {
+ return NGX_ERROR;
+ }
+
+ ngx_memcpy(s->smtp_to.data, l.data, l.len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MAIL, c->log, 0,
+ "smtp rcpt to:\"%V\"", &s->smtp_to);
+
+ s->auth_method = NGX_MAIL_AUTH_NONE;
+
+ return NGX_DONE;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_rset(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ ngx_str_null(&s->smtp_from);
+ ngx_str_null(&s->smtp_to);
+ ngx_str_set(&s->out, smtp_ok);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_starttls(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+#if (NGX_MAIL_SSL)
+ ngx_mail_ssl_conf_t *sslcf;
+
+ if (c->ssl == NULL) {
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+ if (sslcf->starttls) {
+
+ /*
+ * RFC3207 requires us to discard any knowledge
+ * obtained from client before STARTTLS.
+ */
+
+ ngx_str_null(&s->smtp_helo);
+ ngx_str_null(&s->smtp_from);
+ ngx_str_null(&s->smtp_to);
+
+ c->read->handler = ngx_mail_starttls_handler;
+ return NGX_OK;
+ }
+ }
+
+#endif
+
+ return NGX_MAIL_PARSE_INVALID_COMMAND;
+}
+
+
+static ngx_int_t
+ngx_mail_smtp_discard_command(ngx_mail_session_t *s, ngx_connection_t *c,
+ char *err)
+{
+ ssize_t n;
+
+ n = c->recv(c, s->buffer->last, s->buffer->end - s->buffer->last);
+
+ if (n == NGX_ERROR || n == 0) {
+ ngx_mail_close_connection(c);
+ return NGX_ERROR;
+ }
+
+ if (n > 0) {
+ s->buffer->last += n;
+ }
+
+ if (n == NGX_AGAIN) {
+ if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
+ ngx_mail_session_internal_server_error(s);
+ return NGX_ERROR;
+ }
+
+ return NGX_AGAIN;
+ }
+
+ ngx_mail_smtp_log_rejected_command(s, c, err);
+
+ s->buffer->pos = s->buffer->start;
+ s->buffer->last = s->buffer->start;
+
+ return NGX_OK;
+}
+
+
+static void
+ngx_mail_smtp_log_rejected_command(ngx_mail_session_t *s, ngx_connection_t *c,
+ char *err)
+{
+ u_char ch;
+ ngx_str_t cmd;
+ ngx_uint_t i;
+
+ if (c->log->log_level < NGX_LOG_INFO) {
+ return;
+ }
+
+ cmd.len = s->buffer->last - s->buffer->start;
+ cmd.data = s->buffer->start;
+
+ for (i = 0; i < cmd.len; i++) {
+ ch = cmd.data[i];
+
+ if (ch != CR && ch != LF) {
+ continue;
+ }
+
+ cmd.data[i] = '_';
+ }
+
+ cmd.len = i;
+
+ ngx_log_error(NGX_LOG_INFO, c->log, 0, err, &cmd);
+}
diff --git a/src/mail/ngx_mail_smtp_module.c b/src/mail/ngx_mail_smtp_module.c
new file mode 100644
index 0000000..cdd4e5e
--- /dev/null
+++ b/src/mail/ngx_mail_smtp_module.c
@@ -0,0 +1,308 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+#include
+#include
+
+
+static void *ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf);
+static char *ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent,
+ void *child);
+
+
+static ngx_conf_bitmask_t ngx_mail_smtp_auth_methods[] = {
+ { ngx_string("plain"), NGX_MAIL_AUTH_PLAIN_ENABLED },
+ { ngx_string("login"), NGX_MAIL_AUTH_LOGIN_ENABLED },
+ { ngx_string("cram-md5"), NGX_MAIL_AUTH_CRAM_MD5_ENABLED },
+ { ngx_string("none"), NGX_MAIL_AUTH_NONE_ENABLED },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_str_t ngx_mail_smtp_auth_methods_names[] = {
+ ngx_string("PLAIN"),
+ ngx_string("LOGIN"),
+ ngx_null_string, /* APOP */
+ ngx_string("CRAM-MD5"),
+ ngx_null_string /* NONE */
+};
+
+
+static ngx_mail_protocol_t ngx_mail_smtp_protocol = {
+ ngx_string("smtp"),
+ { 25, 465, 587, 0 },
+ NGX_MAIL_SMTP_PROTOCOL,
+
+ ngx_mail_smtp_init_session,
+ ngx_mail_smtp_init_protocol,
+ ngx_mail_smtp_parse_command,
+ ngx_mail_smtp_auth_state,
+
+ ngx_string("451 4.3.2 Internal server error" CRLF)
+};
+
+
+static ngx_command_t ngx_mail_smtp_commands[] = {
+
+ { ngx_string("smtp_client_buffer"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, client_buffer_size),
+ NULL },
+
+ { ngx_string("smtp_greeting_delay"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, greeting_delay),
+ NULL },
+
+ { ngx_string("smtp_capabilities"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_mail_capabilities,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, capabilities),
+ NULL },
+
+ { ngx_string("smtp_auth"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_smtp_srv_conf_t, auth_methods),
+ &ngx_mail_smtp_auth_methods },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_smtp_module_ctx = {
+ &ngx_mail_smtp_protocol, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_smtp_create_srv_conf, /* create server configuration */
+ ngx_mail_smtp_merge_srv_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_smtp_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_smtp_module_ctx, /* module context */
+ ngx_mail_smtp_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_mail_smtp_create_srv_conf(ngx_conf_t *cf)
+{
+ ngx_mail_smtp_srv_conf_t *sscf;
+
+ sscf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_smtp_srv_conf_t));
+ if (sscf == NULL) {
+ return NULL;
+ }
+
+ sscf->client_buffer_size = NGX_CONF_UNSET_SIZE;
+ sscf->greeting_delay = NGX_CONF_UNSET_MSEC;
+
+ if (ngx_array_init(&sscf->capabilities, cf->pool, 4, sizeof(ngx_str_t))
+ != NGX_OK)
+ {
+ return NULL;
+ }
+
+ return sscf;
+}
+
+
+static char *
+ngx_mail_smtp_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_smtp_srv_conf_t *prev = parent;
+ ngx_mail_smtp_srv_conf_t *conf = child;
+
+ u_char *p, *auth, *last;
+ size_t size;
+ ngx_str_t *c;
+ ngx_uint_t i, m, auth_enabled;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ ngx_conf_merge_size_value(conf->client_buffer_size,
+ prev->client_buffer_size,
+ (size_t) ngx_pagesize);
+
+ ngx_conf_merge_msec_value(conf->greeting_delay,
+ prev->greeting_delay, 0);
+
+ ngx_conf_merge_bitmask_value(conf->auth_methods,
+ prev->auth_methods,
+ (NGX_CONF_BITMASK_SET
+ |NGX_MAIL_AUTH_PLAIN_ENABLED
+ |NGX_MAIL_AUTH_LOGIN_ENABLED));
+
+
+ cscf = ngx_mail_conf_get_module_srv_conf(cf, ngx_mail_core_module);
+
+ size = sizeof("220 ESMTP ready" CRLF) - 1 + cscf->server_name.len;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->greeting.len = size;
+ conf->greeting.data = p;
+
+ *p++ = '2'; *p++ = '2'; *p++ = '0'; *p++ = ' ';
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ ngx_memcpy(p, " ESMTP ready" CRLF, sizeof(" ESMTP ready" CRLF) - 1);
+
+
+ size = sizeof("250 " CRLF) - 1 + cscf->server_name.len;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->server_name.len = size;
+ conf->server_name.data = p;
+
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ *p++ = CR; *p = LF;
+
+
+ if (conf->capabilities.nelts == 0) {
+ conf->capabilities = prev->capabilities;
+ }
+
+ size = sizeof("250-") - 1 + cscf->server_name.len + sizeof(CRLF) - 1;
+
+ c = conf->capabilities.elts;
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ size += sizeof("250 ") - 1 + c[i].len + sizeof(CRLF) - 1;
+ }
+
+ auth_enabled = 0;
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ size += 1 + ngx_mail_smtp_auth_methods_names[i].len;
+ auth_enabled = 1;
+ }
+ }
+
+ if (auth_enabled) {
+ size += sizeof("250 AUTH") - 1 + sizeof(CRLF) - 1;
+ }
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->capability.len = size;
+ conf->capability.data = p;
+
+ last = p;
+
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+ p = ngx_cpymem(p, cscf->server_name.data, cscf->server_name.len);
+ *p++ = CR; *p++ = LF;
+
+ for (i = 0; i < conf->capabilities.nelts; i++) {
+ last = p;
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = '-';
+ p = ngx_cpymem(p, c[i].data, c[i].len);
+ *p++ = CR; *p++ = LF;
+ }
+
+ auth = p;
+
+ if (auth_enabled) {
+ last = p;
+
+ *p++ = '2'; *p++ = '5'; *p++ = '0'; *p++ = ' ';
+ *p++ = 'A'; *p++ = 'U'; *p++ = 'T'; *p++ = 'H';
+
+ for (m = NGX_MAIL_AUTH_PLAIN_ENABLED, i = 0;
+ m <= NGX_MAIL_AUTH_CRAM_MD5_ENABLED;
+ m <<= 1, i++)
+ {
+ if (m & conf->auth_methods) {
+ *p++ = ' ';
+ p = ngx_cpymem(p, ngx_mail_smtp_auth_methods_names[i].data,
+ ngx_mail_smtp_auth_methods_names[i].len);
+ }
+ }
+
+ *p++ = CR; *p = LF;
+
+ } else {
+ last[3] = ' ';
+ }
+
+ size += sizeof("250 STARTTLS" CRLF) - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_capability.len = size;
+ conf->starttls_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data, conf->capability.len);
+
+ p = ngx_cpymem(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+ *p++ = CR; *p = LF;
+
+ p = conf->starttls_capability.data
+ + (last - conf->capability.data) + 3;
+ *p = '-';
+
+ size = (auth - conf->capability.data)
+ + sizeof("250 STARTTLS" CRLF) - 1;
+
+ p = ngx_pnalloc(cf->pool, size);
+ if (p == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ conf->starttls_only_capability.len = size;
+ conf->starttls_only_capability.data = p;
+
+ p = ngx_cpymem(p, conf->capability.data, auth - conf->capability.data);
+
+ ngx_memcpy(p, "250 STARTTLS" CRLF, sizeof("250 STARTTLS" CRLF) - 1);
+
+ if (last < auth) {
+ p = conf->starttls_only_capability.data
+ + (last - conf->capability.data) + 3;
+ *p = '-';
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/src/mail/ngx_mail_smtp_module.h b/src/mail/ngx_mail_smtp_module.h
new file mode 100644
index 0000000..04ffab6
--- /dev/null
+++ b/src/mail/ngx_mail_smtp_module.h
@@ -0,0 +1,45 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+#define _NGX_MAIL_SMTP_MODULE_H_INCLUDED_
+
+
+#include
+#include
+#include
+#include
+
+
+typedef struct {
+ ngx_msec_t greeting_delay;
+
+ size_t client_buffer_size;
+
+ ngx_str_t capability;
+ ngx_str_t starttls_capability;
+ ngx_str_t starttls_only_capability;
+
+ ngx_str_t server_name;
+ ngx_str_t greeting;
+
+ ngx_uint_t auth_methods;
+
+ ngx_array_t capabilities;
+} ngx_mail_smtp_srv_conf_t;
+
+
+void ngx_mail_smtp_init_session(ngx_mail_session_t *s, ngx_connection_t *c);
+void ngx_mail_smtp_init_protocol(ngx_event_t *rev);
+void ngx_mail_smtp_auth_state(ngx_event_t *rev);
+ngx_int_t ngx_mail_smtp_parse_command(ngx_mail_session_t *s);
+
+
+extern ngx_module_t ngx_mail_smtp_module;
+
+
+#endif /* _NGX_MAIL_SMTP_MODULE_H_INCLUDED_ */
diff --git a/src/mail/ngx_mail_ssl_module.c b/src/mail/ngx_mail_ssl_module.c
new file mode 100644
index 0000000..dd6f2ac
--- /dev/null
+++ b/src/mail/ngx_mail_ssl_module.c
@@ -0,0 +1,491 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+#define NGX_DEFAULT_CIPHERS "HIGH:!aNULL:!MD5"
+#define NGX_DEFAULT_ECDH_CURVE "prime256v1"
+
+
+static void *ngx_mail_ssl_create_conf(ngx_conf_t *cf);
+static char *ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child);
+
+static char *ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+static char *ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
+
+
+static ngx_conf_enum_t ngx_http_starttls_state[] = {
+ { ngx_string("off"), NGX_MAIL_STARTTLS_OFF },
+ { ngx_string("on"), NGX_MAIL_STARTTLS_ON },
+ { ngx_string("only"), NGX_MAIL_STARTTLS_ONLY },
+ { ngx_null_string, 0 }
+};
+
+
+
+static ngx_conf_bitmask_t ngx_mail_ssl_protocols[] = {
+ { ngx_string("SSLv2"), NGX_SSL_SSLv2 },
+ { ngx_string("SSLv3"), NGX_SSL_SSLv3 },
+ { ngx_string("TLSv1"), NGX_SSL_TLSv1 },
+ { ngx_string("TLSv1.1"), NGX_SSL_TLSv1_1 },
+ { ngx_string("TLSv1.2"), NGX_SSL_TLSv1_2 },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_command_t ngx_mail_ssl_commands[] = {
+
+ { ngx_string("ssl"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_mail_ssl_enable,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, enable),
+ NULL },
+
+ { ngx_string("starttls"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_mail_ssl_starttls,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, starttls),
+ ngx_http_starttls_state },
+
+ { ngx_string("ssl_certificate"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, certificate),
+ NULL },
+
+ { ngx_string("ssl_certificate_key"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, certificate_key),
+ NULL },
+
+ { ngx_string("ssl_dhparam"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, dhparam),
+ NULL },
+
+ { ngx_string("ssl_ecdh_curve"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, ecdh_curve),
+ NULL },
+
+ { ngx_string("ssl_protocols"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+ ngx_conf_set_bitmask_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, protocols),
+ &ngx_mail_ssl_protocols },
+
+ { ngx_string("ssl_ciphers"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, ciphers),
+ NULL },
+
+ { ngx_string("ssl_prefer_server_ciphers"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, prefer_server_ciphers),
+ NULL },
+
+ { ngx_string("ssl_session_cache"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE12,
+ ngx_mail_ssl_session_cache,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("ssl_session_timeout"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_sec_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_ssl_conf_t, session_timeout),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_mail_module_t ngx_mail_ssl_module_ctx = {
+ NULL, /* protocol */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_mail_ssl_create_conf, /* create server configuration */
+ ngx_mail_ssl_merge_conf /* merge server configuration */
+};
+
+
+ngx_module_t ngx_mail_ssl_module = {
+ NGX_MODULE_V1,
+ &ngx_mail_ssl_module_ctx, /* module context */
+ ngx_mail_ssl_commands, /* module directives */
+ NGX_MAIL_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static ngx_str_t ngx_mail_ssl_sess_id_ctx = ngx_string("MAIL");
+
+
+static void *
+ngx_mail_ssl_create_conf(ngx_conf_t *cf)
+{
+ ngx_mail_ssl_conf_t *scf;
+
+ scf = ngx_pcalloc(cf->pool, sizeof(ngx_mail_ssl_conf_t));
+ if (scf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * scf->protocols = 0;
+ * scf->certificate = { 0, NULL };
+ * scf->certificate_key = { 0, NULL };
+ * scf->dhparam = { 0, NULL };
+ * scf->ecdh_curve = { 0, NULL };
+ * scf->ciphers = { 0, NULL };
+ * scf->shm_zone = NULL;
+ */
+
+ scf->enable = NGX_CONF_UNSET;
+ scf->starttls = NGX_CONF_UNSET_UINT;
+ scf->prefer_server_ciphers = NGX_CONF_UNSET;
+ scf->builtin_session_cache = NGX_CONF_UNSET;
+ scf->session_timeout = NGX_CONF_UNSET;
+
+ return scf;
+}
+
+
+static char *
+ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_mail_ssl_conf_t *prev = parent;
+ ngx_mail_ssl_conf_t *conf = child;
+
+ char *mode;
+ ngx_pool_cleanup_t *cln;
+
+ ngx_conf_merge_value(conf->enable, prev->enable, 0);
+ ngx_conf_merge_uint_value(conf->starttls, prev->starttls,
+ NGX_MAIL_STARTTLS_OFF);
+
+ ngx_conf_merge_value(conf->session_timeout,
+ prev->session_timeout, 300);
+
+ ngx_conf_merge_value(conf->prefer_server_ciphers,
+ prev->prefer_server_ciphers, 0);
+
+ ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols,
+ (NGX_CONF_BITMASK_SET|NGX_SSL_SSLv3|NGX_SSL_TLSv1
+ |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
+
+ ngx_conf_merge_str_value(conf->certificate, prev->certificate, "");
+ ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key, "");
+
+ ngx_conf_merge_str_value(conf->dhparam, prev->dhparam, "");
+
+ ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve,
+ NGX_DEFAULT_ECDH_CURVE);
+
+ ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
+
+
+ conf->ssl.log = cf->log;
+
+ if (conf->enable) {
+ mode = "ssl";
+
+ } else if (conf->starttls != NGX_MAIL_STARTTLS_OFF) {
+ mode = "starttls";
+
+ } else {
+ mode = "";
+ }
+
+ if (*mode) {
+
+ if (conf->certificate.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate\" is defined for "
+ "the \"%s\" directive in %s:%ui",
+ mode, conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined for "
+ "the \"%s\" directive in %s:%ui",
+ mode, conf->file, conf->line);
+ return NGX_CONF_ERROR;
+ }
+
+ } else {
+
+ if (conf->certificate.len == 0) {
+ return NGX_CONF_OK;
+ }
+
+ if (conf->certificate_key.len == 0) {
+ ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+ "no \"ssl_certificate_key\" is defined "
+ "for certificate \"%V\"",
+ &conf->certificate);
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (ngx_ssl_create(&conf->ssl, conf->protocols, NULL) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ if (cln == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ cln->handler = ngx_ssl_cleanup_ctx;
+ cln->data = &conf->ssl;
+
+ if (ngx_ssl_certificate(cf, &conf->ssl, &conf->certificate,
+ &conf->certificate_key)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (conf->ciphers.len) {
+ if (SSL_CTX_set_cipher_list(conf->ssl.ctx,
+ (const char *) conf->ciphers.data)
+ == 0)
+ {
+ ngx_ssl_error(NGX_LOG_EMERG, cf->log, 0,
+ "SSL_CTX_set_cipher_list(\"%V\") failed",
+ &conf->ciphers);
+ }
+ }
+
+ if (conf->prefer_server_ciphers) {
+ SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+
+ SSL_CTX_set_tmp_rsa_callback(conf->ssl.ctx, ngx_ssl_rsa512_key_callback);
+
+ if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_conf_merge_value(conf->builtin_session_cache,
+ prev->builtin_session_cache, NGX_SSL_NONE_SCACHE);
+
+ if (conf->shm_zone == NULL) {
+ conf->shm_zone = prev->shm_zone;
+ }
+
+ if (ngx_ssl_session_cache(&conf->ssl, &ngx_mail_ssl_sess_id_ctx,
+ conf->builtin_session_cache,
+ conf->shm_zone, conf->session_timeout)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_ssl_conf_t *scf = conf;
+
+ char *rv;
+
+ rv = ngx_conf_set_flag_slot(cf, cmd, conf);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ if (scf->enable && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"starttls\" directive conflicts with \"ssl on\"");
+ return NGX_CONF_ERROR;
+ }
+
+ scf->file = cf->conf_file->file.name.data;
+ scf->line = cf->conf_file->line;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_ssl_conf_t *scf = conf;
+
+ char *rv;
+
+ rv = ngx_conf_set_enum_slot(cf, cmd, conf);
+
+ if (rv != NGX_CONF_OK) {
+ return rv;
+ }
+
+ if (scf->enable == 1 && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) {
+ ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
+ "\"ssl\" directive conflicts with \"starttls\"");
+ return NGX_CONF_ERROR;
+ }
+
+ scf->file = cf->conf_file->file.name.data;
+ scf->line = cf->conf_file->line;
+
+ return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_mail_ssl_conf_t *scf = conf;
+
+ size_t len;
+ ngx_str_t *value, name, size;
+ ngx_int_t n;
+ ngx_uint_t i, j;
+
+ value = cf->args->elts;
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strcmp(value[i].data, "off") == 0) {
+ scf->builtin_session_cache = NGX_SSL_NO_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "none") == 0) {
+ scf->builtin_session_cache = NGX_SSL_NONE_SCACHE;
+ continue;
+ }
+
+ if (ngx_strcmp(value[i].data, "builtin") == 0) {
+ scf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE;
+ continue;
+ }
+
+ if (value[i].len > sizeof("builtin:") - 1
+ && ngx_strncmp(value[i].data, "builtin:", sizeof("builtin:") - 1)
+ == 0)
+ {
+ n = ngx_atoi(value[i].data + sizeof("builtin:") - 1,
+ value[i].len - (sizeof("builtin:") - 1));
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ scf->builtin_session_cache = n;
+
+ continue;
+ }
+
+ if (value[i].len > sizeof("shared:") - 1
+ && ngx_strncmp(value[i].data, "shared:", sizeof("shared:") - 1)
+ == 0)
+ {
+ len = 0;
+
+ for (j = sizeof("shared:") - 1; j < value[i].len; j++) {
+ if (value[i].data[j] == ':') {
+ break;
+ }
+
+ len++;
+ }
+
+ if (len == 0) {
+ goto invalid;
+ }
+
+ name.len = len;
+ name.data = value[i].data + sizeof("shared:") - 1;
+
+ size.len = value[i].len - j - 1;
+ size.data = name.data + len + 1;
+
+ n = ngx_parse_size(&size);
+
+ if (n == NGX_ERROR) {
+ goto invalid;
+ }
+
+ if (n < (ngx_int_t) (8 * ngx_pagesize)) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "session cache \"%V\" is too small",
+ &value[i]);
+
+ return NGX_CONF_ERROR;
+ }
+
+ scf->shm_zone = ngx_shared_memory_add(cf, &name, n,
+ &ngx_mail_ssl_module);
+ if (scf->shm_zone == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ scf->shm_zone->init = ngx_ssl_session_cache_init;
+
+ continue;
+ }
+
+ goto invalid;
+ }
+
+ if (scf->shm_zone && scf->builtin_session_cache == NGX_CONF_UNSET) {
+ scf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE;
+ }
+
+ return NGX_CONF_OK;
+
+invalid:
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "invalid session cache \"%V\"", &value[i]);
+
+ return NGX_CONF_ERROR;
+}
diff --git a/src/mail/ngx_mail_ssl_module.h b/src/mail/ngx_mail_ssl_module.h
new file mode 100644
index 0000000..7f59b38
--- /dev/null
+++ b/src/mail/ngx_mail_ssl_module.h
@@ -0,0 +1,52 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_MAIL_SSL_H_INCLUDED_
+#define _NGX_MAIL_SSL_H_INCLUDED_
+
+
+#include
+#include
+#include
+
+
+#define NGX_MAIL_STARTTLS_OFF 0
+#define NGX_MAIL_STARTTLS_ON 1
+#define NGX_MAIL_STARTTLS_ONLY 2
+
+
+typedef struct {
+ ngx_flag_t enable;
+ ngx_flag_t prefer_server_ciphers;
+
+ ngx_ssl_t ssl;
+
+ ngx_uint_t starttls;
+ ngx_uint_t protocols;
+
+ ssize_t builtin_session_cache;
+
+ time_t session_timeout;
+
+ ngx_str_t certificate;
+ ngx_str_t certificate_key;
+ ngx_str_t dhparam;
+ ngx_str_t ecdh_curve;
+
+ ngx_str_t ciphers;
+
+ ngx_shm_zone_t *shm_zone;
+
+ u_char *file;
+ ngx_uint_t line;
+} ngx_mail_ssl_conf_t;
+
+
+extern ngx_module_t ngx_mail_ssl_module;
+
+
+#endif /* _NGX_MAIL_SSL_H_INCLUDED_ */
diff --git a/src/misc/ngx_cpp_test_module.cpp b/src/misc/ngx_cpp_test_module.cpp
new file mode 100644
index 0000000..8f87dcd
--- /dev/null
+++ b/src/misc/ngx_cpp_test_module.cpp
@@ -0,0 +1,27 @@
+
+// stub module to test header files' C++ compatibilty
+
+extern "C" {
+ #include
+ #include
+ #include
+ #include
+ #include
+
+ #include
+
+ #include
+ #include
+ #include
+ #include
+}
+
+// nginx header files should go before other, because they define 64-bit off_t
+// #include
+
+
+void
+ngx_cpp_test_handler(void *data)
+{
+ return;
+}
diff --git a/src/misc/ngx_google_perftools_module.c b/src/misc/ngx_google_perftools_module.c
new file mode 100644
index 0000000..f2f8221
--- /dev/null
+++ b/src/misc/ngx_google_perftools_module.c
@@ -0,0 +1,126 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+/*
+ * declare Profiler interface here because
+ * is C++ header file
+ */
+
+int ProfilerStart(u_char* fname);
+void ProfilerStop(void);
+void ProfilerRegisterThread(void);
+
+
+static void *ngx_google_perftools_create_conf(ngx_cycle_t *cycle);
+static ngx_int_t ngx_google_perftools_worker(ngx_cycle_t *cycle);
+
+
+typedef struct {
+ ngx_str_t profiles;
+} ngx_google_perftools_conf_t;
+
+
+static ngx_command_t ngx_google_perftools_commands[] = {
+
+ { ngx_string("google_perftools_profiles"),
+ NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ 0,
+ offsetof(ngx_google_perftools_conf_t, profiles),
+ NULL },
+
+ ngx_null_command
+};
+
+
+static ngx_core_module_t ngx_google_perftools_module_ctx = {
+ ngx_string("google_perftools"),
+ ngx_google_perftools_create_conf,
+ NULL
+};
+
+
+ngx_module_t ngx_google_perftools_module = {
+ NGX_MODULE_V1,
+ &ngx_google_perftools_module_ctx, /* module context */
+ ngx_google_perftools_commands, /* module directives */
+ NGX_CORE_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ ngx_google_perftools_worker, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+
+static void *
+ngx_google_perftools_create_conf(ngx_cycle_t *cycle)
+{
+ ngx_google_perftools_conf_t *gptcf;
+
+ gptcf = ngx_pcalloc(cycle->pool, sizeof(ngx_google_perftools_conf_t));
+ if (gptcf == NULL) {
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc()
+ *
+ * gptcf->profiles = { 0, NULL };
+ */
+
+ return gptcf;
+}
+
+
+static ngx_int_t
+ngx_google_perftools_worker(ngx_cycle_t *cycle)
+{
+ u_char *profile;
+ ngx_google_perftools_conf_t *gptcf;
+
+ gptcf = (ngx_google_perftools_conf_t *)
+ ngx_get_conf(cycle->conf_ctx, ngx_google_perftools_module);
+
+ if (gptcf->profiles.len == 0) {
+ return NGX_OK;
+ }
+
+ profile = ngx_alloc(gptcf->profiles.len + NGX_INT_T_LEN + 2, cycle->log);
+ if (profile == NULL) {
+ return NGX_OK;
+ }
+
+ if (getenv("CPUPROFILE")) {
+ /* disable inherited Profiler enabled in master process */
+ ProfilerStop();
+ }
+
+ ngx_sprintf(profile, "%V.%d%Z", &gptcf->profiles, ngx_pid);
+
+ if (ProfilerStart(profile)) {
+ /* start ITIMER_PROF timer */
+ ProfilerRegisterThread();
+
+ } else {
+ ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_errno,
+ "ProfilerStart(%s) failed", profile);
+ }
+
+ ngx_free(profile);
+
+ return NGX_OK;
+}
+
+
+/* ProfilerStop() is called on Profiler destruction */
diff --git a/src/os/unix/ngx_aio_read.c b/src/os/unix/ngx_aio_read.c
new file mode 100644
index 0000000..7849881
--- /dev/null
+++ b/src/os/unix/ngx_aio_read.c
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+extern int ngx_kqueue;
+
+
+ssize_t
+ngx_aio_read(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ int n;
+ ngx_event_t *rev;
+
+ rev = c->read;
+
+ if (!rev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0, "second aio post");
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "rev->complete: %d", rev->complete);
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio size: %d", size);
+
+ if (!rev->complete) {
+ ngx_memzero(&rev->aiocb, sizeof(struct aiocb));
+
+ rev->aiocb.aio_fildes = c->fd;
+ rev->aiocb.aio_buf = buf;
+ rev->aiocb.aio_nbytes = size;
+
+#if (NGX_HAVE_KQUEUE)
+ rev->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+ rev->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+ rev->aiocb.aio_sigevent.sigev_value.sigval_ptr = rev;
+#endif
+
+ if (aio_read(&rev->aiocb) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, rev->log, ngx_errno,
+ "aio_read() failed");
+ rev->error = 1;
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio_read: #%d OK", c->fd);
+
+ rev->active = 1;
+ rev->ready = 0;
+ }
+
+ rev->complete = 0;
+
+ n = aio_error(&rev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "aio_error() failed");
+ rev->error = 1;
+ return NGX_ERROR;
+ }
+
+ if (n != 0) {
+ if (n == NGX_EINPROGRESS) {
+ if (rev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, n,
+ "aio_read() still in progress");
+ rev->ready = 0;
+ }
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, c->log, n, "aio_read() failed");
+ rev->error = 1;
+ rev->ready = 0;
+ return NGX_ERROR;
+ }
+
+ n = aio_return(&rev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
+ "aio_return() failed");
+
+ rev->error = 1;
+ rev->ready = 0;
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rev->log, 0,
+ "aio_read: #%d %d", c->fd, n);
+
+ if (n == 0) {
+ rev->eof = 1;
+ rev->ready = 0;
+ } else {
+ rev->ready = 1;
+ }
+
+ rev->active = 0;
+
+ return n;
+}
diff --git a/src/os/unix/ngx_aio_read_chain.c b/src/os/unix/ngx_aio_read_chain.c
new file mode 100644
index 0000000..8c831b9
--- /dev/null
+++ b/src/os/unix/ngx_aio_read_chain.c
@@ -0,0 +1,78 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+ssize_t
+ngx_aio_read_chain(ngx_connection_t *c, ngx_chain_t *cl)
+{
+ int n;
+ u_char *buf, *prev;
+ size_t size;
+ ssize_t total;
+
+ if (c->read->pending_eof) {
+ c->read->ready = 0;
+ return 0;
+ }
+
+ total = 0;
+
+ while (cl) {
+
+ /* we can post the single aio operation only */
+
+ if (!c->read->ready) {
+ return total ? total : NGX_AGAIN;
+ }
+
+ buf = cl->buf->last;
+ prev = cl->buf->last;
+ size = 0;
+
+ /* coalesce the neighbouring bufs */
+
+ while (cl && prev == cl->buf->last) {
+ size += cl->buf->end - cl->buf->last;
+ prev = cl->buf->end;
+ cl = cl->next;
+ }
+
+ n = ngx_aio_read(c, buf, size);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_read: %d", n);
+
+ if (n == NGX_AGAIN) {
+ return total ? total : NGX_AGAIN;
+ }
+
+ if (n == NGX_ERROR) {
+ return NGX_ERROR;
+ }
+
+ if (n == 0) {
+ c->read->pending_eof = 1;
+ if (total) {
+ c->read->eof = 0;
+ c->read->ready = 1;
+ }
+ return total;
+ }
+
+ if (n > 0) {
+ total += n;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio_read total: %d", total);
+ }
+
+ return total ? total : NGX_AGAIN;
+}
diff --git a/src/os/unix/ngx_aio_write.c b/src/os/unix/ngx_aio_write.c
new file mode 100644
index 0000000..f0d9391
--- /dev/null
+++ b/src/os/unix/ngx_aio_write.c
@@ -0,0 +1,109 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+extern int ngx_kqueue;
+
+
+ssize_t
+ngx_aio_write(ngx_connection_t *c, u_char *buf, size_t size)
+{
+ int n;
+ ngx_event_t *wev;
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, wev->log, 0,
+ "aio: wev->complete: %d", wev->complete);
+
+ if (!wev->complete) {
+ ngx_memzero(&wev->aiocb, sizeof(struct aiocb));
+
+ wev->aiocb.aio_fildes = c->fd;
+ wev->aiocb.aio_buf = buf;
+ wev->aiocb.aio_nbytes = size;
+
+#if (NGX_HAVE_KQUEUE)
+ wev->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+ wev->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+ wev->aiocb.aio_sigevent.sigev_value.sigval_ptr = wev;
+#endif
+
+ if (aio_write(&wev->aiocb) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, wev->log, ngx_errno,
+ "aio_write() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, wev->log, 0, "aio_write: OK");
+
+ wev->active = 1;
+ wev->ready = 0;
+ }
+
+ wev->complete = 0;
+
+ n = aio_error(&wev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, wev->log, ngx_errno, "aio_error() failed");
+ wev->error = 1;
+ return NGX_ERROR;
+ }
+
+ if (n != 0) {
+ if (n == NGX_EINPROGRESS) {
+ if (wev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, wev->log, n,
+ "aio_write() still in progress");
+ wev->ready = 0;
+ }
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, wev->log, n, "aio_write() failed");
+ wev->error = 1;
+ wev->ready = 0;
+
+#if 1
+ n = aio_return(&wev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, wev->log, ngx_errno,
+ "aio_return() failed");
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, wev->log, n, "aio_return() %d", n);
+#endif
+
+ return NGX_ERROR;
+ }
+
+ n = aio_return(&wev->aiocb);
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_ALERT, wev->log, ngx_errno,
+ "aio_return() failed");
+
+ wev->error = 1;
+ wev->ready = 0;
+ return NGX_ERROR;
+ }
+
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, wev->log, 0, "aio_write: %d", n);
+
+ wev->active = 0;
+ wev->ready = 1;
+
+ return n;
+}
diff --git a/src/os/unix/ngx_aio_write_chain.c b/src/os/unix/ngx_aio_write_chain.c
new file mode 100644
index 0000000..b0c2508
--- /dev/null
+++ b/src/os/unix/ngx_aio_write_chain.c
@@ -0,0 +1,100 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+ngx_chain_t *
+ngx_aio_write_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ u_char *buf, *prev;
+ off_t send, sent;
+ size_t len;
+ ssize_t n, size;
+ ngx_chain_t *cl;
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+ send = 0;
+ sent = 0;
+ cl = in;
+
+ while (cl) {
+
+ if (cl->buf->pos == cl->buf->last) {
+ cl = cl->next;
+ continue;
+ }
+
+ /* we can post the single aio operation only */
+
+ if (!c->write->ready) {
+ return cl;
+ }
+
+ buf = cl->buf->pos;
+ prev = buf;
+ len = 0;
+
+ /* coalesce the neighbouring bufs */
+
+ while (cl && prev == cl->buf->pos && send < limit) {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ len += size;
+ prev = cl->buf->pos + size;
+ send += size;
+ cl = cl->next;
+ }
+
+ n = ngx_aio_write(c, buf, len);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "aio_write: %z", n);
+
+ if (n == NGX_ERROR) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ if (n > 0) {
+ sent += n;
+ c->sent += n;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "aio_write sent: %O", c->sent);
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (sent >= cl->buf->last - cl->buf->pos) {
+ sent -= cl->buf->last - cl->buf->pos;
+ cl->buf->pos = cl->buf->last;
+
+ continue;
+ }
+
+ cl->buf->pos += sent;
+
+ break;
+ }
+ }
+
+ return cl;
+}
diff --git a/src/os/unix/ngx_alloc.c b/src/os/unix/ngx_alloc.c
new file mode 100644
index 0000000..5c2f787
--- /dev/null
+++ b/src/os/unix/ngx_alloc.c
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+
+ngx_uint_t ngx_pagesize;
+ngx_uint_t ngx_pagesize_shift;
+ngx_uint_t ngx_cacheline_size;
+
+
+void *
+ngx_alloc(size_t size, ngx_log_t *log)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "malloc(%uz) failed", size);
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size);
+
+ return p;
+}
+
+
+void *
+ngx_calloc(size_t size, ngx_log_t *log)
+{
+ void *p;
+
+ p = ngx_alloc(size, log);
+
+ if (p) {
+ ngx_memzero(p, size);
+ }
+
+ return p;
+}
+
+
+#if (NGX_HAVE_POSIX_MEMALIGN)
+
+void *
+ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+ void *p;
+ int err;
+
+ err = posix_memalign(&p, alignment, size);
+
+ if (err) {
+ ngx_log_error(NGX_LOG_EMERG, log, err,
+ "posix_memalign(%uz, %uz) failed", alignment, size);
+ p = NULL;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
+ "posix_memalign: %p:%uz @%uz", p, size, alignment);
+
+ return p;
+}
+
+#elif (NGX_HAVE_MEMALIGN)
+
+void *
+ngx_memalign(size_t alignment, size_t size, ngx_log_t *log)
+{
+ void *p;
+
+ p = memalign(alignment, size);
+ if (p == NULL) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "memalign(%uz, %uz) failed", alignment, size);
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_ALLOC, log, 0,
+ "memalign: %p:%uz @%uz", p, size, alignment);
+
+ return p;
+}
+
+#endif
diff --git a/src/os/unix/ngx_alloc.h b/src/os/unix/ngx_alloc.h
new file mode 100644
index 0000000..c6cda09
--- /dev/null
+++ b/src/os/unix/ngx_alloc.h
@@ -0,0 +1,47 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_ALLOC_H_INCLUDED_
+#define _NGX_ALLOC_H_INCLUDED_
+
+
+#include
+#include
+
+// ʹÓÃmalloc·ÖÅäÄÚ´æ¿Õ¼ä
+void *ngx_alloc(size_t size, ngx_log_t *log);
+
+//ʹÓÃmalloc·ÖÅäÄÚ´æ¿Õ¼ä£¬²¢ÇÒ½«¿Õ¼äÄÚÈݳõʼ»¯Îª0
+void *ngx_calloc(size_t size, ngx_log_t *log);
+
+#define ngx_free free
+
+
+/*
+ * Linux has memalign() or posix_memalign()
+ * Solaris has memalign()
+ * FreeBSD 7.0 has posix_memalign(), besides, early version's malloc()
+ * aligns allocations bigger than page size at the page boundary
+ */
+
+#if (NGX_HAVE_POSIX_MEMALIGN || NGX_HAVE_MEMALIGN)
+
+void *ngx_memalign(size_t alignment, size_t size, ngx_log_t *log);
+
+#else
+
+#define ngx_memalign(alignment, size, log) ngx_alloc(size, log)
+
+#endif
+
+
+extern ngx_uint_t ngx_pagesize;
+extern ngx_uint_t ngx_pagesize_shift;
+extern ngx_uint_t ngx_cacheline_size;
+
+
+#endif /* _NGX_ALLOC_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_atomic.h b/src/os/unix/ngx_atomic.h
new file mode 100644
index 0000000..104ac37
--- /dev/null
+++ b/src/os/unix/ngx_atomic.h
@@ -0,0 +1,311 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_ATOMIC_H_INCLUDED_
+#define _NGX_ATOMIC_H_INCLUDED_
+
+
+#include
+#include
+
+
+#if (NGX_HAVE_LIBATOMIC)
+
+#define AO_REQUIRE_CAS
+#include
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+typedef long ngx_atomic_int_t;
+typedef AO_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+#if (NGX_PTR_SIZE == 8)
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+#else
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+#endif
+
+#define ngx_atomic_cmp_set(lock, old, new) \
+ AO_compare_and_swap(lock, old, new)
+#define ngx_atomic_fetch_add(value, add) \
+ AO_fetch_and_add(value, add)
+#define ngx_memory_barrier() AO_nop()
+#define ngx_cpu_pause()
+
+
+#elif (NGX_DARWIN_ATOMIC)
+
+/*
+ * use Darwin 8 atomic(3) and barrier(3) operations
+ * optimized at run-time for UP and SMP
+ */
+
+#include
+
+/* "bool" conflicts with perl's CORE/handy.h */
+#undef bool
+
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+#define ngx_atomic_cmp_set(lock, old, new) \
+ OSAtomicCompareAndSwap64Barrier(old, new, (int64_t *) lock)
+
+#define ngx_atomic_fetch_add(value, add) \
+ (OSAtomicAdd64(add, (int64_t *) value) - add)
+
+#else
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+#define ngx_atomic_cmp_set(lock, old, new) \
+ OSAtomicCompareAndSwap32Barrier(old, new, (int32_t *) lock)
+
+#define ngx_atomic_fetch_add(value, add) \
+ (OSAtomicAdd32(add, (int32_t *) value) - add)
+
+#endif
+
+#define ngx_memory_barrier() OSMemoryBarrier()
+
+#define ngx_cpu_pause()
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#elif (NGX_HAVE_GCC_ATOMIC)
+
+/* GCC 4.1 builtin atomic operations */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+typedef long ngx_atomic_int_t;
+typedef unsigned long ngx_atomic_uint_t;
+
+#if (NGX_PTR_SIZE == 8)
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+#else
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+#endif
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#define ngx_atomic_cmp_set(lock, old, set) \
+ __sync_bool_compare_and_swap(lock, old, set)
+
+#define ngx_atomic_fetch_add(value, add) \
+ __sync_fetch_and_add(value, add)
+
+#define ngx_memory_barrier() __sync_synchronize()
+
+#if ( __i386__ || __i386 || __amd64__ || __amd64 )
+#define ngx_cpu_pause() __asm__ ("pause")
+#else
+#define ngx_cpu_pause()
+#endif
+
+
+#elif ( __i386__ || __i386 )
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set);
+
+ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);
+
+/*
+ * Sun Studio 12 exits with segmentation fault on '__asm ("pause")',
+ * so ngx_cpu_pause is declared in src/os/unix/ngx_sunpro_x86.il
+ */
+
+void
+ngx_cpu_pause(void);
+
+/* the code in src/os/unix/ngx_sunpro_x86.il */
+
+#define ngx_memory_barrier() __asm (".volatile"); __asm (".nonvolatile")
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_gcc_atomic_x86.h"
+
+#endif
+
+
+#elif ( __amd64__ || __amd64 )
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set);
+
+ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add);
+
+/*
+ * Sun Studio 12 exits with segmentation fault on '__asm ("pause")',
+ * so ngx_cpu_pause is declared in src/os/unix/ngx_sunpro_amd64.il
+ */
+
+void
+ngx_cpu_pause(void);
+
+/* the code in src/os/unix/ngx_sunpro_amd64.il */
+
+#define ngx_memory_barrier() __asm (".volatile"); __asm (".nonvolatile")
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_gcc_atomic_amd64.h"
+
+#endif
+
+
+#elif ( __sparc__ || __sparc || __sparcv9 )
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+#else
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+#endif
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#if ( __SUNPRO_C )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_sunpro_atomic_sparc64.h"
+
+
+#else /* ( __GNUC__ || __INTEL_COMPILER ) */
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#include "ngx_gcc_atomic_sparc64.h"
+
+#endif
+
+
+#elif ( __powerpc__ || __POWERPC__ )
+
+#define NGX_HAVE_ATOMIC_OPS 1
+
+#if (NGX_PTR_SIZE == 8)
+
+typedef int64_t ngx_atomic_int_t;
+typedef uint64_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-9223372036854775808") - 1)
+
+#else
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+#endif
+
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+
+
+#include "ngx_gcc_atomic_ppc.h"
+
+#endif
+
+
+#if !(NGX_HAVE_ATOMIC_OPS)
+
+#define NGX_HAVE_ATOMIC_OPS 0
+
+typedef int32_t ngx_atomic_int_t;
+typedef uint32_t ngx_atomic_uint_t;
+typedef volatile ngx_atomic_uint_t ngx_atomic_t;
+#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1)
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ if (*lock == old) {
+ *lock = set;
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_int_t old;
+
+ old = *value;
+ *value += add;
+
+ return old;
+}
+
+#define ngx_memory_barrier()
+#define ngx_cpu_pause()
+
+#endif
+
+
+void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin);
+
+#define ngx_trylock(lock) (*(lock) == 0 && ngx_atomic_cmp_set(lock, 0, 1))
+#define ngx_unlock(lock) *(lock) = 0
+
+
+#endif /* _NGX_ATOMIC_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_channel.c b/src/os/unix/ngx_channel.c
new file mode 100644
index 0000000..29c69da
--- /dev/null
+++ b/src/os/unix/ngx_channel.c
@@ -0,0 +1,258 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+ngx_int_t
+ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+ ngx_log_t *log)
+{
+ ssize_t n;
+ ngx_err_t err;
+ struct iovec iov[1];
+ struct msghdr msg;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+ union {
+ struct cmsghdr cm;
+ char space[CMSG_SPACE(sizeof(int))];
+ } cmsg;
+
+ if (ch->fd == -1) {
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+
+ } else {
+ msg.msg_control = (caddr_t) &cmsg;
+ msg.msg_controllen = sizeof(cmsg);
+
+ cmsg.cm.cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg.cm.cmsg_level = SOL_SOCKET;
+ cmsg.cm.cmsg_type = SCM_RIGHTS;
+
+ /*
+ * We have to use ngx_memcpy() instead of simple
+ * *(int *) CMSG_DATA(&cmsg.cm) = ch->fd;
+ * because some gcc 4.4 with -O2/3/s optimization issues the warning:
+ * dereferencing type-punned pointer will break strict-aliasing rules
+ *
+ * Fortunately, gcc with -O1 compiles this ngx_memcpy()
+ * in the same simple assignment as in the code above
+ */
+
+ ngx_memcpy(CMSG_DATA(&cmsg.cm), &ch->fd, sizeof(int));
+ }
+
+ msg.msg_flags = 0;
+
+#else
+
+ if (ch->fd == -1) {
+ msg.msg_accrights = NULL;
+ msg.msg_accrightslen = 0;
+
+ } else {
+ msg.msg_accrights = (caddr_t) &ch->fd;
+ msg.msg_accrightslen = sizeof(int);
+ }
+
+#endif
+
+ iov[0].iov_base = (char *) ch;
+ iov[0].iov_len = size;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ n = sendmsg(s, &msg, 0);
+
+ if (n == -1) {
+ err = ngx_errno;
+ if (err == NGX_EAGAIN) {
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err, "sendmsg() failed");
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size, ngx_log_t *log)
+{
+ ssize_t n;
+ ngx_err_t err;
+ struct iovec iov[1];
+ struct msghdr msg;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+ union {
+ struct cmsghdr cm;
+ char space[CMSG_SPACE(sizeof(int))];
+ } cmsg;
+#else
+ int fd;
+#endif
+
+ iov[0].iov_base = (char *) ch;
+ iov[0].iov_len = size;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+ msg.msg_control = (caddr_t) &cmsg;
+ msg.msg_controllen = sizeof(cmsg);
+#else
+ msg.msg_accrights = (caddr_t) &fd;
+ msg.msg_accrightslen = sizeof(int);
+#endif
+
+ n = recvmsg(s, &msg, 0);
+
+ if (n == -1) {
+ err = ngx_errno;
+ if (err == NGX_EAGAIN) {
+ return NGX_AGAIN;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err, "recvmsg() failed");
+ return NGX_ERROR;
+ }
+
+ if (n == 0) {
+ ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "recvmsg() returned zero");
+ return NGX_ERROR;
+ }
+
+ if ((size_t) n < sizeof(ngx_channel_t)) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned not enough data: %uz", n);
+ return NGX_ERROR;
+ }
+
+#if (NGX_HAVE_MSGHDR_MSG_CONTROL)
+
+ if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+
+ if (cmsg.cm.cmsg_len < (socklen_t) CMSG_LEN(sizeof(int))) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned too small ancillary data");
+ return NGX_ERROR;
+ }
+
+ if (cmsg.cm.cmsg_level != SOL_SOCKET || cmsg.cm.cmsg_type != SCM_RIGHTS)
+ {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned invalid ancillary data "
+ "level %d or type %d",
+ cmsg.cm.cmsg_level, cmsg.cm.cmsg_type);
+ return NGX_ERROR;
+ }
+
+ /* ch->fd = *(int *) CMSG_DATA(&cmsg.cm); */
+
+ ngx_memcpy(&ch->fd, CMSG_DATA(&cmsg.cm), sizeof(int));
+ }
+
+ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() truncated data");
+ }
+
+#else
+
+ if (ch->command == NGX_CMD_OPEN_CHANNEL) {
+ if (msg.msg_accrightslen != sizeof(int)) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "recvmsg() returned no ancillary data");
+ return NGX_ERROR;
+ }
+
+ ch->fd = fd;
+ }
+
+#endif
+
+ return n;
+}
+
+
+ngx_int_t
+ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd, ngx_int_t event,
+ ngx_event_handler_pt handler)
+{
+ ngx_event_t *ev, *rev, *wev;
+ ngx_connection_t *c;
+
+ c = ngx_get_connection(fd, cycle->log);
+
+ if (c == NULL) {
+ return NGX_ERROR;
+ }
+
+ c->pool = cycle->pool;
+
+ rev = c->read;
+ wev = c->write;
+
+ rev->log = cycle->log;
+ wev->log = cycle->log;
+
+#if (NGX_THREADS)
+ rev->lock = &c->lock;
+ wev->lock = &c->lock;
+ rev->own_lock = &c->lock;
+ wev->own_lock = &c->lock;
+#endif
+
+ rev->channel = 1;
+ wev->channel = 1;
+
+ ev = (event == NGX_READ_EVENT) ? rev : wev;
+
+ ev->handler = handler;
+
+ if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
+ if (ngx_add_conn(c) == NGX_ERROR) {
+ ngx_free_connection(c);
+ return NGX_ERROR;
+ }
+
+ } else {
+ if (ngx_add_event(ev, event, 0) == NGX_ERROR) {
+ ngx_free_connection(c);
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
+
+
+void
+ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log)
+{
+ if (close(fd[0]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
+ }
+
+ if (close(fd[1]) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "close() channel failed");
+ }
+}
diff --git a/src/os/unix/ngx_channel.h b/src/os/unix/ngx_channel.h
new file mode 100644
index 0000000..d7a9f6b
--- /dev/null
+++ b/src/os/unix/ngx_channel.h
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_CHANNEL_H_INCLUDED_
+#define _NGX_CHANNEL_H_INCLUDED_
+
+
+#include
+#include
+#include
+
+
+typedef struct {
+ ngx_uint_t command;
+ ngx_pid_t pid;
+ ngx_int_t slot;
+ ngx_fd_t fd;
+} ngx_channel_t;
+
+
+ngx_int_t ngx_write_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+ ngx_log_t *log);
+ngx_int_t ngx_read_channel(ngx_socket_t s, ngx_channel_t *ch, size_t size,
+ ngx_log_t *log);
+ngx_int_t ngx_add_channel_event(ngx_cycle_t *cycle, ngx_fd_t fd,
+ ngx_int_t event, ngx_event_handler_pt handler);
+void ngx_close_channel(ngx_fd_t *fd, ngx_log_t *log);
+
+
+#endif /* _NGX_CHANNEL_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_daemon.c b/src/os/unix/ngx_daemon.c
new file mode 100644
index 0000000..5d47c97
--- /dev/null
+++ b/src/os/unix/ngx_daemon.c
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+
+ngx_int_t ngx_daemon(ngx_log_t *log)
+{
+ int fd;
+
+ switch (fork()) {
+ case -1:
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fork() failed");
+ return NGX_ERROR;
+
+ case 0:
+ break;
+
+ default:
+ exit(0); //¸¸½ø³ÌÍ˳ö
+ }
+
+ ngx_pid = ngx_getpid();
+
+ if (setsid() == -1) { //setsid´´½¨Ò»¸öлỰ
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "setsid() failed");
+ return NGX_ERROR;
+ }
+
+ umask(0);
+
+ //´ò¿ªÎļþ/dev/null,ʹµÃÆäÓµÓÐÊØ»¤½ø³ÌµÄ0£¬1£¬2¡£ÕâÑù·ÀÖ¹ÊØ»¤½ø³ÌÔÚÖÕ¶ËÉ豸ÉÏÏÔʾÊä³ö
+ fd = open("/dev/null", O_RDWR);
+ if (fd == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno,
+ "open(\"/dev/null\") failed");
+ return NGX_ERROR;
+ }
+
+ if (dup2(fd, STDIN_FILENO) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDIN) failed");
+ return NGX_ERROR;
+ }
+
+ if (dup2(fd, STDOUT_FILENO) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDOUT) failed");
+ return NGX_ERROR;
+ }
+
+#if 0
+ if (dup2(fd, STDERR_FILENO) == -1) {
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "dup2(STDERR) failed");
+ return NGX_ERROR;
+ }
+#endif
+
+ if (fd > STDERR_FILENO) {
+ if (close(fd) == -1) { //¹Ø±Õ²»ÐèÒªµÄÎļþÃèÊö·û
+ ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "close() failed");
+ return NGX_ERROR;
+ }
+ }
+
+ return NGX_OK;
+}
diff --git a/src/os/unix/ngx_darwin.h b/src/os/unix/ngx_darwin.h
new file mode 100644
index 0000000..b3aba90
--- /dev/null
+++ b/src/os/unix/ngx_darwin.h
@@ -0,0 +1,20 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_DARWIN_H_INCLUDED_
+#define _NGX_DARWIN_H_INCLUDED_
+
+
+ngx_chain_t *ngx_darwin_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+extern int ngx_darwin_kern_osreldate;
+extern int ngx_darwin_hw_ncpu;
+extern u_long ngx_darwin_net_inet_tcp_sendspace;
+
+
+#endif /* _NGX_DARWIN_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_darwin_config.h b/src/os/unix/ngx_darwin_config.h
new file mode 100644
index 0000000..a24e43c
--- /dev/null
+++ b/src/os/unix/ngx_darwin_config.h
@@ -0,0 +1,96 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_DARWIN_CONFIG_H_INCLUDED_
+#define _NGX_DARWIN_CONFIG_H_INCLUDED_
+
+
+#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_0
+
+
+#include
+#include
+#include
+#include
+#include
+#include /* offsetof() */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include /* statfs() */
+
+#include /* FIONBIO */
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include /* TCP_NODELAY */
+#include
+#include
+#include
+
+#include
+#include
+
+
+#ifndef IOV_MAX
+#define IOV_MAX 64
+#endif
+
+
+#include
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include
+#endif
+
+
+#define NGX_LISTEN_BACKLOG -1
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK 1
+#endif
+
+
+#ifndef NGX_HAVE_CASELESS_FILESYSTEM
+#define NGX_HAVE_CASELESS_FILESYSTEM 1
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT 1
+
+
+extern char **environ;
+
+
+#endif /* _NGX_DARWIN_CONFIG_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_darwin_init.c b/src/os/unix/ngx_darwin_init.c
new file mode 100644
index 0000000..5c542cd
--- /dev/null
+++ b/src/os/unix/ngx_darwin_init.c
@@ -0,0 +1,166 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+
+char ngx_darwin_kern_ostype[16];
+char ngx_darwin_kern_osrelease[128];
+int ngx_darwin_hw_ncpu;
+int ngx_darwin_kern_ipc_somaxconn;
+u_long ngx_darwin_net_inet_tcp_sendspace;
+
+
+static ngx_os_io_t ngx_darwin_io = {
+ ngx_unix_recv,
+ ngx_readv_chain,
+ ngx_udp_unix_recv,
+ ngx_unix_send,
+#if (NGX_HAVE_SENDFILE)
+ ngx_darwin_sendfile_chain,
+ NGX_IO_SENDFILE
+#else
+ ngx_writev_chain,
+ 0
+#endif
+};
+
+
+typedef struct {
+ char *name;
+ void *value;
+ size_t size;
+ ngx_uint_t exists;
+} sysctl_t;
+
+
+sysctl_t sysctls[] = {
+ { "hw.ncpu",
+ &ngx_darwin_hw_ncpu,
+ sizeof(ngx_darwin_hw_ncpu), 0 },
+
+ { "net.inet.tcp.sendspace",
+ &ngx_darwin_net_inet_tcp_sendspace,
+ sizeof(ngx_darwin_net_inet_tcp_sendspace), 0 },
+
+ { "kern.ipc.somaxconn",
+ &ngx_darwin_kern_ipc_somaxconn,
+ sizeof(ngx_darwin_kern_ipc_somaxconn), 0 },
+
+ { NULL, NULL, 0, 0 }
+};
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+ size_t size;
+ ngx_err_t err;
+ ngx_uint_t i;
+
+ size = sizeof(ngx_darwin_kern_ostype);
+ if (sysctlbyname("kern.ostype", ngx_darwin_kern_ostype, &size, NULL, 0)
+ == -1)
+ {
+ err = ngx_errno;
+
+ if (err != NGX_ENOENT) {
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(kern.ostype) failed");
+
+ if (err != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_darwin_kern_ostype[size - 1] = '\0';
+ }
+ }
+
+ size = sizeof(ngx_darwin_kern_osrelease);
+ if (sysctlbyname("kern.osrelease", ngx_darwin_kern_osrelease, &size,
+ NULL, 0)
+ == -1)
+ {
+ err = ngx_errno;
+
+ if (err != NGX_ENOENT) {
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(kern.osrelease) failed");
+
+ if (err != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_darwin_kern_osrelease[size - 1] = '\0';
+ }
+ }
+
+ for (i = 0; sysctls[i].name; i++) {
+ size = sysctls[i].size;
+
+ if (sysctlbyname(sysctls[i].name, sysctls[i].value, &size, NULL, 0)
+ == 0)
+ {
+ sysctls[i].exists = 1;
+ continue;
+ }
+
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT) {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(%s) failed", sysctls[i].name);
+ return NGX_ERROR;
+ }
+
+ ngx_ncpu = ngx_darwin_hw_ncpu;
+
+ if (ngx_darwin_kern_ipc_somaxconn > 32767) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "sysctl kern.ipc.somaxconn must be less than 32768");
+ return NGX_ERROR;
+ }
+
+ ngx_tcp_nodelay_and_tcp_nopush = 1;
+
+ ngx_os_io = ngx_darwin_io;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+ u_long value;
+ ngx_uint_t i;
+
+ if (ngx_darwin_kern_ostype[0]) {
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+ ngx_darwin_kern_ostype, ngx_darwin_kern_osrelease);
+ }
+
+ for (i = 0; sysctls[i].name; i++) {
+ if (sysctls[i].exists) {
+ if (sysctls[i].size == sizeof(long)) {
+ value = *(long *) sysctls[i].value;
+
+ } else {
+ value = *(int *) sysctls[i].value;
+ }
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "%s: %l",
+ sysctls[i].name, value);
+ }
+ }
+}
diff --git a/src/os/unix/ngx_darwin_sendfile_chain.c b/src/os/unix/ngx_darwin_sendfile_chain.c
new file mode 100644
index 0000000..ec247d2
--- /dev/null
+++ b/src/os/unix/ngx_darwin_sendfile_chain.c
@@ -0,0 +1,366 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+/*
+ * It seems that Darwin 9.4 (Mac OS X 1.5) sendfile() has the same
+ * old bug as early FreeBSD sendfile() syscall:
+ * http://www.freebsd.org/cgi/query-pr.cgi?pr=33771
+ *
+ * Besides sendfile() has another bug: if one calls sendfile()
+ * with both a header and a trailer, then sendfile() ignores a file part
+ * at all and sends only the header and the trailer together.
+ * For this reason we send a trailer only if there is no a header.
+ *
+ * Although sendfile() allows to pass a header or a trailer,
+ * it may send the header or the trailer and a part of the file
+ * in different packets. And FreeBSD workaround (TCP_NOPUSH option)
+ * does not help.
+ */
+
+
+#if (IOV_MAX > 64)
+#define NGX_HEADERS 64
+#define NGX_TRAILERS 64
+#else
+#define NGX_HEADERS IOV_MAX
+#define NGX_TRAILERS IOV_MAX
+#endif
+
+
+ngx_chain_t *
+ngx_darwin_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ int rc;
+ u_char *prev;
+ off_t size, send, prev_send, aligned, sent, fprev;
+ off_t header_size, file_size;
+ ngx_uint_t eintr, complete;
+ ngx_err_t err;
+ ngx_buf_t *file;
+ ngx_array_t header, trailer;
+ ngx_event_t *wev;
+ ngx_chain_t *cl;
+ struct sf_hdtr hdtr;
+ struct iovec *iov, headers[NGX_HEADERS], trailers[NGX_TRAILERS];
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return in;
+ }
+
+#if (NGX_HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+ (void) ngx_connection_error(c, wev->kq_errno,
+ "kevent() reported about an closed connection");
+ wev->error = 1;
+ return NGX_CHAIN_ERROR;
+ }
+
+#endif
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+ send = 0;
+
+ header.elts = headers;
+ header.size = sizeof(struct iovec);
+ header.nalloc = NGX_HEADERS;
+ header.pool = c->pool;
+
+ trailer.elts = trailers;
+ trailer.size = sizeof(struct iovec);
+ trailer.nalloc = NGX_TRAILERS;
+ trailer.pool = c->pool;
+
+ for ( ;; ) {
+ file = NULL;
+ file_size = 0;
+ header_size = 0;
+ eintr = 0;
+ complete = 0;
+ prev_send = send;
+
+ header.nelts = 0;
+ trailer.nelts = 0;
+
+ /* create the header iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ for (cl = in;
+ cl && header.nelts < IOV_MAX && send < limit;
+ cl = cl->next)
+ {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&header);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ header_size += size;
+ send += size;
+ }
+
+
+ if (cl && cl->buf->in_file && send < limit) {
+ file = cl->buf;
+
+ /* coalesce the neighbouring file bufs */
+
+ do {
+ size = cl->buf->file_last - cl->buf->file_pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+
+ aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+ & ~((off_t) ngx_pagesize - 1);
+
+ if (aligned <= cl->buf->file_last) {
+ size = aligned - cl->buf->file_pos;
+ }
+ }
+
+ file_size += size;
+ send += size;
+ fprev = cl->buf->file_pos + size;
+ cl = cl->next;
+
+ } while (cl
+ && cl->buf->in_file
+ && send < limit
+ && file->file->fd == cl->buf->file->fd
+ && fprev == cl->buf->file_pos);
+ }
+
+ if (file && header.nelts == 0) {
+
+ /* create the trailer iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ while (cl && header.nelts < IOV_MAX && send < limit) {
+
+ if (ngx_buf_special(cl->buf)) {
+ cl = cl->next;
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&trailer);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ send += size;
+ cl = cl->next;
+ }
+ }
+
+ if (file) {
+
+ /*
+ * sendfile() returns EINVAL if sf_hdtr's count is 0,
+ * but corresponding pointer is not NULL
+ */
+
+ hdtr.headers = header.nelts ? (struct iovec *) header.elts: NULL;
+ hdtr.hdr_cnt = header.nelts;
+ hdtr.trailers = trailer.nelts ? (struct iovec *) trailer.elts: NULL;
+ hdtr.trl_cnt = trailer.nelts;
+
+ sent = header_size + file_size;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: @%O %O h:%O",
+ file->file_pos, sent, header_size);
+
+ rc = sendfile(file->file->fd, c->fd, file->file_pos,
+ &sent, &hdtr, 0);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ (void) ngx_connection_error(c, err, "sendfile() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "sendfile() sent only %O bytes", sent);
+ }
+
+ if (rc == 0 && sent == 0) {
+
+ /*
+ * if rc and sent equal to zero, then someone
+ * has truncated the file, so the offset became beyond
+ * the end of the file
+ */
+
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "sendfile() reported that \"%s\" was truncated",
+ file->file->name.data);
+
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: %d, @%O %O:%O",
+ rc, file->file_pos, sent, file_size + header_size);
+
+ } else {
+ rc = writev(c->fd, header.elts, header.nelts);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "writev: %d of %uz", rc, send);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ ngx_connection_error(c, err, "writev() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "writev() not ready");
+ }
+
+ sent = rc > 0 ? rc : 0;
+ }
+
+ if (send - prev_send == sent) {
+ complete = 1;
+ }
+
+ c->sent += sent;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (sent == 0) {
+ break;
+ }
+
+ size = ngx_buf_size(cl->buf);
+
+ if (sent >= size) {
+ sent -= size;
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos = cl->buf->last;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos = cl->buf->file_last;
+ }
+
+ continue;
+ }
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos += (size_t) sent;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos += sent;
+ }
+
+ break;
+ }
+
+ if (eintr) {
+ continue;
+ }
+
+ if (!complete) {
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (send >= limit || cl == NULL) {
+ return cl;
+ }
+
+ in = cl;
+ }
+}
diff --git a/src/os/unix/ngx_errno.c b/src/os/unix/ngx_errno.c
new file mode 100644
index 0000000..75176c4
--- /dev/null
+++ b/src/os/unix/ngx_errno.c
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+
+/*
+ * The strerror() messages are copied because:
+ *
+ * 1) strerror() and strerror_r() functions are not Async-Signal-Safe,
+ * therefore, they cannot be used in signal handlers;
+ *
+ * 2) a direct sys_errlist[] array may be used instead of these functions,
+ * but Linux linker warns about its usage:
+ *
+ * warning: `sys_errlist' is deprecated; use `strerror' or `strerror_r' instead
+ * warning: `sys_nerr' is deprecated; use `strerror' or `strerror_r' instead
+ *
+ * causing false bug reports.
+ */
+
+
+static ngx_str_t *ngx_sys_errlist;
+static ngx_str_t ngx_unknown_error = ngx_string("Unknown error");
+
+
+u_char *
+ngx_strerror(ngx_err_t err, u_char *errstr, size_t size)
+{
+ ngx_str_t *msg;
+
+ msg = ((ngx_uint_t) err < NGX_SYS_NERR) ? &ngx_sys_errlist[err]:
+ &ngx_unknown_error;
+ size = ngx_min(size, msg->len);
+
+ return ngx_cpymem(errstr, msg->data, size);
+}
+
+
+ngx_uint_t
+ngx_strerror_init(void)
+{
+ char *msg;
+ u_char *p;
+ size_t len;
+ ngx_err_t err;
+
+ /*
+ * ngx_strerror() is not ready to work at this stage, therefore,
+ * malloc() is used and possible errors are logged using strerror().
+ */
+
+ len = NGX_SYS_NERR * sizeof(ngx_str_t);
+
+ ngx_sys_errlist = malloc(len);
+ if (ngx_sys_errlist == NULL) {
+ goto failed;
+ }
+
+ for (err = 0; err < NGX_SYS_NERR; err++) {
+ msg = strerror(err);
+ len = ngx_strlen(msg);
+
+ p = malloc(len);
+ if (p == NULL) {
+ goto failed;
+ }
+
+ ngx_memcpy(p, msg, len);
+ ngx_sys_errlist[err].len = len;
+ ngx_sys_errlist[err].data = p;
+ }
+
+ return NGX_OK;
+
+failed:
+
+ err = errno;
+ ngx_log_stderr(0, "malloc(%uz) failed (%d: %s)", len, err, strerror(err));
+
+ return NGX_ERROR;
+}
diff --git a/src/os/unix/ngx_errno.h b/src/os/unix/ngx_errno.h
new file mode 100644
index 0000000..092d05a
--- /dev/null
+++ b/src/os/unix/ngx_errno.h
@@ -0,0 +1,68 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_ERRNO_H_INCLUDED_
+#define _NGX_ERRNO_H_INCLUDED_
+
+
+#include
+#include
+
+
+typedef int ngx_err_t;
+
+#define NGX_EPERM EPERM
+#define NGX_ENOENT ENOENT
+#define NGX_ENOPATH ENOENT
+#define NGX_ESRCH ESRCH
+#define NGX_EINTR EINTR
+#define NGX_ECHILD ECHILD
+#define NGX_ENOMEM ENOMEM
+#define NGX_EACCES EACCES
+#define NGX_EBUSY EBUSY
+#define NGX_EEXIST EEXIST
+#define NGX_EXDEV EXDEV
+#define NGX_ENOTDIR ENOTDIR
+#define NGX_EISDIR EISDIR
+#define NGX_EINVAL EINVAL
+#define NGX_ENOSPC ENOSPC
+#define NGX_EPIPE EPIPE
+#define NGX_EINPROGRESS EINPROGRESS
+#define NGX_EADDRINUSE EADDRINUSE
+#define NGX_ECONNABORTED ECONNABORTED
+#define NGX_ECONNRESET ECONNRESET
+#define NGX_ENOTCONN ENOTCONN
+#define NGX_ETIMEDOUT ETIMEDOUT
+#define NGX_ECONNREFUSED ECONNREFUSED
+#define NGX_ENAMETOOLONG ENAMETOOLONG
+#define NGX_ENETDOWN ENETDOWN
+#define NGX_ENETUNREACH ENETUNREACH
+#define NGX_EHOSTDOWN EHOSTDOWN
+#define NGX_EHOSTUNREACH EHOSTUNREACH
+#define NGX_ENOSYS ENOSYS
+#define NGX_ECANCELED ECANCELED
+#define NGX_EILSEQ EILSEQ
+#define NGX_ENOMOREFILES 0
+
+#if (__hpux__)
+#define NGX_EAGAIN EWOULDBLOCK
+#else
+#define NGX_EAGAIN EAGAIN
+#endif
+
+
+#define ngx_errno errno
+#define ngx_socket_errno errno
+#define ngx_set_errno(err) errno = err
+#define ngx_set_socket_errno(err) errno = err
+
+
+u_char *ngx_strerror(ngx_err_t err, u_char *errstr, size_t size);
+ngx_uint_t ngx_strerror_init(void);
+
+
+#endif /* _NGX_ERRNO_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_file_aio_read.c b/src/os/unix/ngx_file_aio_read.c
new file mode 100644
index 0000000..0bb383d
--- /dev/null
+++ b/src/os/unix/ngx_file_aio_read.c
@@ -0,0 +1,208 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+/*
+ * FreeBSD file AIO features and quirks:
+ *
+ * if an asked data are already in VM cache, then aio_error() returns 0,
+ * and the data are already copied in buffer;
+ *
+ * aio_read() preread in VM cache as minimum 16K (probably BKVASIZE);
+ * the first AIO preload may be up to 128K;
+ *
+ * aio_read/aio_error() may return EINPROGRESS for just written data;
+ *
+ * kqueue EVFILT_AIO filter is level triggered only: an event repeats
+ * until aio_return() will be called;
+ *
+ * aio_cancel() cannot cancel file AIO: it returns AIO_NOTCANCELED always.
+ */
+
+
+extern int ngx_kqueue;
+
+
+static ssize_t ngx_file_aio_result(ngx_file_t *file, ngx_event_aio_t *aio,
+ ngx_event_t *ev);
+static void ngx_file_aio_event_handler(ngx_event_t *ev);
+
+
+ssize_t
+ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
+ ngx_pool_t *pool)
+{
+ int n;
+ ngx_event_t *ev;
+ ngx_event_aio_t *aio;
+
+ if (!ngx_file_aio) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ aio = file->aio;
+
+ if (aio == NULL) {
+ aio = ngx_pcalloc(pool, sizeof(ngx_event_aio_t));
+ if (aio == NULL) {
+ return NGX_ERROR;
+ }
+
+ aio->file = file;
+ aio->fd = file->fd;
+ aio->event.data = aio;
+ aio->event.ready = 1;
+ aio->event.log = file->log;
+#if (NGX_HAVE_AIO_SENDFILE)
+ aio->last_offset = -1;
+#endif
+ file->aio = aio;
+ }
+
+ ev = &aio->event;
+
+ if (!ev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+ "second aio post for \"%V\"", &file->name);
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio complete:%d @%O:%z %V",
+ ev->complete, offset, size, &file->name);
+
+ if (ev->complete) {
+ ev->complete = 0;
+ ngx_set_errno(aio->err);
+
+ if (aio->err == 0) {
+ return aio->nbytes;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "aio read \"%s\" failed", file->name.data);
+
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&aio->aiocb, sizeof(struct aiocb));
+
+ aio->aiocb.aio_fildes = file->fd;
+ aio->aiocb.aio_offset = offset;
+ aio->aiocb.aio_buf = buf;
+ aio->aiocb.aio_nbytes = size;
+#if (NGX_HAVE_KQUEUE)
+ aio->aiocb.aio_sigevent.sigev_notify_kqueue = ngx_kqueue;
+ aio->aiocb.aio_sigevent.sigev_notify = SIGEV_KEVENT;
+ aio->aiocb.aio_sigevent.sigev_value.sigval_ptr = ev;
+#endif
+ ev->handler = ngx_file_aio_event_handler;
+
+ n = aio_read(&aio->aiocb);
+
+ if (n == -1) {
+ n = ngx_errno;
+
+ if (n == NGX_EAGAIN) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, n,
+ "aio_read(\"%V\") failed", &file->name);
+
+ if (n == NGX_ENOSYS) {
+ ngx_file_aio = 0;
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio_read: fd:%d %d", file->fd, n);
+
+ ev->active = 1;
+ ev->ready = 0;
+ ev->complete = 0;
+
+ return ngx_file_aio_result(aio->file, aio, ev);
+}
+
+
+static ssize_t
+ngx_file_aio_result(ngx_file_t *file, ngx_event_aio_t *aio, ngx_event_t *ev)
+{
+ int n;
+ ngx_err_t err;
+
+ n = aio_error(&aio->aiocb);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio_error: fd:%d %d", file->fd, n);
+
+ if (n == -1) {
+ err = ngx_errno;
+ aio->err = err;
+
+ ngx_log_error(NGX_LOG_ALERT, file->log, err,
+ "aio_error(\"%V\") failed", &file->name);
+ return NGX_ERROR;
+ }
+
+ if (n == NGX_EINPROGRESS) {
+ if (ev->ready) {
+ ev->ready = 0;
+ ngx_log_error(NGX_LOG_ALERT, file->log, n,
+ "aio_read(\"%V\") still in progress",
+ &file->name);
+ }
+
+ return NGX_AGAIN;
+ }
+
+ n = aio_return(&aio->aiocb);
+
+ if (n == -1) {
+ err = ngx_errno;
+ aio->err = err;
+ ev->ready = 1;
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, err,
+ "aio_return(\"%V\") failed", &file->name);
+ return NGX_ERROR;
+ }
+
+ aio->err = 0;
+ aio->nbytes = n;
+ ev->ready = 1;
+ ev->active = 0;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio_return: fd:%d %d", file->fd, n);
+
+ return n;
+}
+
+
+static void
+ngx_file_aio_event_handler(ngx_event_t *ev)
+{
+ ngx_event_aio_t *aio;
+
+ aio = ev->data;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, ev->log, 0,
+ "aio event handler fd:%d %V", aio->fd, &aio->file->name);
+
+ if (ngx_file_aio_result(aio->file, aio, ev) != NGX_AGAIN) {
+ aio->handler(ev);
+ }
+}
diff --git a/src/os/unix/ngx_files.c b/src/os/unix/ngx_files.c
new file mode 100644
index 0000000..ca54581
--- /dev/null
+++ b/src/os/unix/ngx_files.c
@@ -0,0 +1,566 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+ngx_uint_t ngx_file_aio = 1;
+
+#endif
+
+
+ssize_t
+ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+ ssize_t n;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "read: %d, %p, %uz, %O", file->fd, buf, size, offset);
+
+#if (NGX_HAVE_PREAD)
+
+ n = pread(file->fd, buf, size, offset);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "pread() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+#else
+
+ if (file->sys_offset != offset) {
+ if (lseek(file->fd, offset, SEEK_SET) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "lseek() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset = offset;
+ }
+
+ n = read(file->fd, buf, size);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "read() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset += n;
+
+#endif
+
+ file->offset += n;
+
+ return n;
+}
+
+
+ssize_t
+ngx_write_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset)
+{
+ ssize_t n, written;
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "write: %d, %p, %uz, %O", file->fd, buf, size, offset);
+
+ written = 0;
+
+#if (NGX_HAVE_PWRITE)
+
+ for ( ;; ) {
+ n = pwrite(file->fd, buf + written, size, offset);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "pwrite() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->offset += n;
+ written += n;
+
+ if ((size_t) n == size) {
+ return written;
+ }
+
+ offset += n;
+ size -= n;
+ }
+
+#else
+
+ if (file->sys_offset != offset) {
+ if (lseek(file->fd, offset, SEEK_SET) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "lseek() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset = offset;
+ }
+
+ for ( ;; ) {
+ n = write(file->fd, buf + written, size);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "write() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->offset += n;
+ written += n;
+
+ if ((size_t) n == size) {
+ return written;
+ }
+
+ size -= n;
+ }
+#endif
+}
+
+
+ngx_fd_t
+ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access)
+{
+ ngx_fd_t fd;
+
+ fd = open((const char *) name, O_CREAT|O_EXCL|O_RDWR,
+ access ? access : 0600);
+
+ if (fd != -1 && !persistent) {
+ unlink((const char *) name);
+ }
+
+ return fd;
+}
+
+
+#define NGX_IOVS 8
+
+ssize_t
+ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset,
+ ngx_pool_t *pool)
+{
+ u_char *prev;
+ size_t size;
+ ssize_t total, n;
+ ngx_array_t vec;
+ struct iovec *iov, iovs[NGX_IOVS];
+
+ /* use pwrite() if there is the only buf in a chain */
+
+ if (cl->next == NULL) {
+ return ngx_write_file(file, cl->buf->pos,
+ (size_t) (cl->buf->last - cl->buf->pos),
+ offset);
+ }
+
+ total = 0;
+
+ vec.elts = iovs;
+ vec.size = sizeof(struct iovec);
+ vec.nalloc = NGX_IOVS;
+ vec.pool = pool;
+
+ do {
+ prev = NULL;
+ iov = NULL;
+ size = 0;
+
+ vec.nelts = 0;
+
+ /* create the iovec and coalesce the neighbouring bufs */
+
+ while (cl && vec.nelts < IOV_MAX) {
+ if (prev == cl->buf->pos) {
+ iov->iov_len += cl->buf->last - cl->buf->pos;
+
+ } else {
+ iov = ngx_array_push(&vec);
+ if (iov == NULL) {
+ return NGX_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = cl->buf->last - cl->buf->pos;
+ }
+
+ size += cl->buf->last - cl->buf->pos;
+ prev = cl->buf->last;
+ cl = cl->next;
+ }
+
+ /* use pwrite() if there is the only iovec buffer */
+
+ if (vec.nelts == 1) {
+ iov = vec.elts;
+
+ n = ngx_write_file(file, (u_char *) iov[0].iov_base,
+ iov[0].iov_len, offset);
+
+ if (n == NGX_ERROR) {
+ return n;
+ }
+
+ return total + n;
+ }
+
+ if (file->sys_offset != offset) {
+ if (lseek(file->fd, offset, SEEK_SET) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "lseek() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset = offset;
+ }
+
+ n = writev(file->fd, vec.elts, vec.nelts);
+
+ if (n == -1) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "writev() \"%s\" failed", file->name.data);
+ return NGX_ERROR;
+ }
+
+ if ((size_t) n != size) {
+ ngx_log_error(NGX_LOG_CRIT, file->log, 0,
+ "writev() \"%s\" has written only %z of %uz",
+ file->name.data, n, size);
+ return NGX_ERROR;
+ }
+
+ file->sys_offset += n;
+ file->offset += n;
+ total += n;
+
+ } while (cl);
+
+ return total;
+}
+
+
+ngx_int_t
+ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s)
+{
+ struct timeval tv[2];
+
+ tv[0].tv_sec = ngx_time();
+ tv[0].tv_usec = 0;
+ tv[1].tv_sec = s;
+ tv[1].tv_usec = 0;
+
+ if (utimes((char *) name, tv) != -1) {
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_create_file_mapping(ngx_file_mapping_t *fm)
+{
+ fm->fd = ngx_open_file(fm->name, NGX_FILE_RDWR, NGX_FILE_TRUNCATE,
+ NGX_FILE_DEFAULT_ACCESS);
+ if (fm->fd == NGX_INVALID_FILE) {
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ ngx_open_file_n " \"%s\" failed", fm->name);
+ return NGX_ERROR;
+ }
+
+ if (ftruncate(fm->fd, fm->size) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ "ftruncate() \"%s\" failed", fm->name);
+ goto failed;
+ }
+
+ fm->addr = mmap(NULL, fm->size, PROT_READ|PROT_WRITE, MAP_SHARED,
+ fm->fd, 0);
+ if (fm->addr != MAP_FAILED) {
+ return NGX_OK;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ "mmap(%uz) \"%s\" failed", fm->size, fm->name);
+
+failed:
+
+ if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", fm->name);
+ }
+
+ return NGX_ERROR;
+}
+
+
+void
+ngx_close_file_mapping(ngx_file_mapping_t *fm)
+{
+ if (munmap(fm->addr, fm->size) == -1) {
+ ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno,
+ "munmap(%uz) \"%s\" failed", fm->size, fm->name);
+ }
+
+ if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno,
+ ngx_close_file_n " \"%s\" failed", fm->name);
+ }
+}
+
+
+ngx_int_t
+ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir)
+{
+ dir->dir = opendir((const char *) name->data);
+
+ if (dir->dir == NULL) {
+ return NGX_ERROR;
+ }
+
+ dir->valid_info = 0;
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_read_dir(ngx_dir_t *dir)
+{
+ dir->de = readdir(dir->dir);
+
+ if (dir->de) {
+#if (NGX_HAVE_D_TYPE)
+ dir->type = dir->de->d_type;
+#else
+ dir->type = 0;
+#endif
+ return NGX_OK;
+ }
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_open_glob(ngx_glob_t *gl)
+{
+ int n;
+
+ n = glob((char *) gl->pattern, GLOB_NOSORT, NULL, &gl->pglob);
+
+ if (n == 0) {
+ return NGX_OK;
+ }
+
+#ifdef GLOB_NOMATCH
+
+ if (n == GLOB_NOMATCH && gl->test) {
+ return NGX_OK;
+ }
+
+#endif
+
+ return NGX_ERROR;
+}
+
+
+ngx_int_t
+ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name)
+{
+ size_t count;
+
+#ifdef GLOB_NOMATCH
+ count = (size_t) gl->pglob.gl_pathc;
+#else
+ count = (size_t) gl->pglob.gl_matchc;
+#endif
+
+ if (gl->n < count) {
+
+ name->len = (size_t) ngx_strlen(gl->pglob.gl_pathv[gl->n]);
+ name->data = (u_char *) gl->pglob.gl_pathv[gl->n];
+ gl->n++;
+
+ return NGX_OK;
+ }
+
+ return NGX_DONE;
+}
+
+
+void
+ngx_close_glob(ngx_glob_t *gl)
+{
+ globfree(&gl->pglob);
+}
+
+
+ngx_err_t
+ngx_trylock_fd(ngx_fd_t fd)
+{
+ struct flock fl;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_pid = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLK, &fl) == -1) {
+ return ngx_errno;
+ }
+
+ return 0;
+}
+
+
+ngx_err_t
+ngx_lock_fd(ngx_fd_t fd)
+{
+ struct flock fl;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_pid = 0;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLKW, &fl) == -1) {
+ return ngx_errno;
+ }
+
+ return 0;
+}
+
+
+ngx_err_t
+ngx_unlock_fd(ngx_fd_t fd)
+{
+ struct flock fl;
+
+ fl.l_start = 0;
+ fl.l_len = 0;
+ fl.l_pid = 0;
+ fl.l_type = F_UNLCK;
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLK, &fl) == -1) {
+ return ngx_errno;
+ }
+
+ return 0;
+}
+
+
+#if (NGX_HAVE_POSIX_FADVISE) && !(NGX_HAVE_F_READAHEAD)
+
+ngx_int_t
+ngx_read_ahead(ngx_fd_t fd, size_t n)
+{
+ int err;
+
+ err = posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
+
+ if (err == 0) {
+ return 0;
+ }
+
+ ngx_set_errno(err);
+ return NGX_FILE_ERROR;
+}
+
+#endif
+
+
+#if (NGX_HAVE_O_DIRECT)
+
+ngx_int_t
+ngx_directio_on(ngx_fd_t fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+
+ if (flags == -1) {
+ return NGX_FILE_ERROR;
+ }
+
+ return fcntl(fd, F_SETFL, flags | O_DIRECT);
+}
+
+
+ngx_int_t
+ngx_directio_off(ngx_fd_t fd)
+{
+ int flags;
+
+ flags = fcntl(fd, F_GETFL);
+
+ if (flags == -1) {
+ return NGX_FILE_ERROR;
+ }
+
+ return fcntl(fd, F_SETFL, flags & ~O_DIRECT);
+}
+
+#endif
+
+
+#if (NGX_HAVE_STATFS)
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+ struct statfs fs;
+
+ if (statfs((char *) name, &fs) == -1) {
+ return 512;
+ }
+
+ if ((fs.f_bsize % 512) != 0) {
+ return 512;
+ }
+
+ return (size_t) fs.f_bsize;
+}
+
+#elif (NGX_HAVE_STATVFS)
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+ struct statvfs fs;
+
+ if (statvfs((char *) name, &fs) == -1) {
+ return 512;
+ }
+
+ if ((fs.f_frsize % 512) != 0) {
+ return 512;
+ }
+
+ return (size_t) fs.f_frsize;
+}
+
+#else
+
+size_t
+ngx_fs_bsize(u_char *name)
+{
+ return 512;
+}
+
+#endif
diff --git a/src/os/unix/ngx_files.h b/src/os/unix/ngx_files.h
new file mode 100644
index 0000000..1298824
--- /dev/null
+++ b/src/os/unix/ngx_files.h
@@ -0,0 +1,343 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FILES_H_INCLUDED_
+#define _NGX_FILES_H_INCLUDED_
+
+
+#include
+#include
+
+
+typedef int ngx_fd_t;
+typedef struct stat ngx_file_info_t;
+typedef ino_t ngx_file_uniq_t;
+
+
+typedef struct {
+ u_char *name;
+ size_t size;
+ void *addr;
+ ngx_fd_t fd;
+ ngx_log_t *log;
+} ngx_file_mapping_t;
+
+
+typedef struct {
+ DIR *dir;
+ struct dirent *de;
+ struct stat info;
+
+ unsigned type:8;
+ unsigned valid_info:1;
+} ngx_dir_t;
+
+
+typedef struct {
+ size_t n;
+ glob_t pglob;
+ u_char *pattern;
+ ngx_log_t *log;
+ ngx_uint_t test;
+} ngx_glob_t;
+
+
+#define NGX_INVALID_FILE -1
+#define NGX_FILE_ERROR -1
+
+
+
+#ifdef __CYGWIN__
+
+#define NGX_HAVE_CASELESS_FILESYSTEM 1
+
+#define ngx_open_file(name, mode, create, access) \
+ open((const char *) name, mode|create|O_BINARY, access)
+
+#else
+
+#define ngx_open_file(name, mode, create, access) \
+ open((const char *) name, mode|create, access)
+
+#endif
+
+#define ngx_open_file_n "open()"
+
+#define NGX_FILE_RDONLY O_RDONLY
+#define NGX_FILE_WRONLY O_WRONLY
+#define NGX_FILE_RDWR O_RDWR
+#define NGX_FILE_CREATE_OR_OPEN O_CREAT
+#define NGX_FILE_OPEN 0
+#define NGX_FILE_TRUNCATE O_CREAT|O_TRUNC
+#define NGX_FILE_APPEND O_WRONLY|O_APPEND
+#define NGX_FILE_NONBLOCK O_NONBLOCK
+
+#define NGX_FILE_DEFAULT_ACCESS 0644
+#define NGX_FILE_OWNER_ACCESS 0600
+
+
+#define ngx_close_file close
+#define ngx_close_file_n "close()"
+
+
+#define ngx_delete_file(name) unlink((const char *) name)
+#define ngx_delete_file_n "unlink()"
+
+
+ngx_fd_t ngx_open_tempfile(u_char *name, ngx_uint_t persistent,
+ ngx_uint_t access);
+#define ngx_open_tempfile_n "open()"
+
+
+ssize_t ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset);
+#if (NGX_HAVE_PREAD)
+#define ngx_read_file_n "pread()"
+#else
+#define ngx_read_file_n "read()"
+#endif
+
+ssize_t ngx_write_file(ngx_file_t *file, u_char *buf, size_t size,
+ off_t offset);
+
+ssize_t ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *ce,
+ off_t offset, ngx_pool_t *pool);
+
+
+#define ngx_read_fd read
+#define ngx_read_fd_n "read()"
+
+/*
+ * we use inlined function instead of simple #define
+ * because glibc 2.3 sets warn_unused_result attribute for write()
+ * and in this case gcc 4.3 ignores (void) cast
+ */
+static ngx_inline ssize_t
+ngx_write_fd(ngx_fd_t fd, void *buf, size_t n)
+{
+ return write(fd, buf, n);
+}
+
+#define ngx_write_fd_n "write()"
+
+
+#define ngx_write_console ngx_write_fd
+
+
+#define ngx_linefeed(p) *p++ = LF;
+#define NGX_LINEFEED_SIZE 1
+#define NGX_LINEFEED "\x0a"
+
+
+#define ngx_rename_file(o, n) rename((const char *) o, (const char *) n)
+#define ngx_rename_file_n "rename()"
+
+
+#define ngx_change_file_access(n, a) chmod((const char *) n, a)
+#define ngx_change_file_access_n "chmod()"
+
+
+ngx_int_t ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s);
+#define ngx_set_file_time_n "utimes()"
+
+
+#define ngx_file_info(file, sb) stat((const char *) file, sb)
+#define ngx_file_info_n "stat()"
+
+#define ngx_fd_info(fd, sb) fstat(fd, sb)
+#define ngx_fd_info_n "fstat()"
+
+#define ngx_link_info(file, sb) lstat((const char *) file, sb)
+#define ngx_link_info_n "lstat()"
+
+#define ngx_is_dir(sb) (S_ISDIR((sb)->st_mode))
+#define ngx_is_file(sb) (S_ISREG((sb)->st_mode))
+#define ngx_is_link(sb) (S_ISLNK((sb)->st_mode))
+#define ngx_is_exec(sb) (((sb)->st_mode & S_IXUSR) == S_IXUSR)
+#define ngx_file_access(sb) ((sb)->st_mode & 0777)
+#define ngx_file_size(sb) (sb)->st_size
+#define ngx_file_fs_size(sb) ngx_max((sb)->st_size, (sb)->st_blocks * 512)
+#define ngx_file_mtime(sb) (sb)->st_mtime
+#define ngx_file_uniq(sb) (sb)->st_ino
+
+
+ngx_int_t ngx_create_file_mapping(ngx_file_mapping_t *fm);
+void ngx_close_file_mapping(ngx_file_mapping_t *fm);
+
+
+#if (NGX_HAVE_CASELESS_FILESYSTEM)
+
+#define ngx_filename_cmp(s1, s2, n) strncasecmp((char *) s1, (char *) s2, n)
+
+#else
+
+#define ngx_filename_cmp ngx_memcmp
+
+#endif
+
+
+#define ngx_realpath(p, r) realpath((char *) p, (char *) r)
+#define ngx_realpath_n "realpath()"
+#define ngx_getcwd(buf, size) (getcwd((char *) buf, size) != NULL)
+#define ngx_getcwd_n "getcwd()"
+#define ngx_path_separator(c) ((c) == '/')
+
+#define NGX_MAX_PATH PATH_MAX
+
+#define NGX_DIR_MASK_LEN 0
+
+
+ngx_int_t ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir);
+#define ngx_open_dir_n "opendir()"
+
+
+#define ngx_close_dir(d) closedir((d)->dir)
+#define ngx_close_dir_n "closedir()"
+
+
+ngx_int_t ngx_read_dir(ngx_dir_t *dir);
+#define ngx_read_dir_n "readdir()"
+
+
+#define ngx_create_dir(name, access) mkdir((const char *) name, access)
+#define ngx_create_dir_n "mkdir()"
+
+
+#define ngx_delete_dir(name) rmdir((const char *) name)
+#define ngx_delete_dir_n "rmdir()"
+
+
+#define ngx_dir_access(a) (a | (a & 0444) >> 2)
+
+
+#define ngx_de_name(dir) ((u_char *) (dir)->de->d_name)
+#if (NGX_HAVE_D_NAMLEN)
+#define ngx_de_namelen(dir) (dir)->de->d_namlen
+#else
+#define ngx_de_namelen(dir) ngx_strlen((dir)->de->d_name)
+#endif
+
+static ngx_inline ngx_int_t
+ngx_de_info(u_char *name, ngx_dir_t *dir)
+{
+ dir->type = 0;
+ return stat((const char *) name, &dir->info);
+}
+
+#define ngx_de_info_n "stat()"
+#define ngx_de_link_info(name, dir) lstat((const char *) name, &(dir)->info)
+#define ngx_de_link_info_n "lstat()"
+
+#if (NGX_HAVE_D_TYPE)
+
+/*
+ * some file systems (e.g. XFS on Linux and CD9660 on FreeBSD)
+ * do not set dirent.d_type
+ */
+
+#define ngx_de_is_dir(dir) \
+ (((dir)->type) ? ((dir)->type == DT_DIR) : (S_ISDIR((dir)->info.st_mode)))
+#define ngx_de_is_file(dir) \
+ (((dir)->type) ? ((dir)->type == DT_REG) : (S_ISREG((dir)->info.st_mode)))
+#define ngx_de_is_link(dir) \
+ (((dir)->type) ? ((dir)->type == DT_LNK) : (S_ISLNK((dir)->info.st_mode)))
+
+#else
+
+#define ngx_de_is_dir(dir) (S_ISDIR((dir)->info.st_mode))
+#define ngx_de_is_file(dir) (S_ISREG((dir)->info.st_mode))
+#define ngx_de_is_link(dir) (S_ISLNK((dir)->info.st_mode))
+
+#endif
+
+#define ngx_de_access(dir) (((dir)->info.st_mode) & 0777)
+#define ngx_de_size(dir) (dir)->info.st_size
+#define ngx_de_fs_size(dir) \
+ ngx_max((dir)->info.st_size, (dir)->info.st_blocks * 512)
+#define ngx_de_mtime(dir) (dir)->info.st_mtime
+
+
+ngx_int_t ngx_open_glob(ngx_glob_t *gl);
+#define ngx_open_glob_n "glob()"
+ngx_int_t ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name);
+void ngx_close_glob(ngx_glob_t *gl);
+
+
+ngx_err_t ngx_trylock_fd(ngx_fd_t fd);
+ngx_err_t ngx_lock_fd(ngx_fd_t fd);
+ngx_err_t ngx_unlock_fd(ngx_fd_t fd);
+
+#define ngx_trylock_fd_n "fcntl(F_SETLK, F_WRLCK)"
+#define ngx_lock_fd_n "fcntl(F_SETLKW, F_WRLCK)"
+#define ngx_unlock_fd_n "fcntl(F_SETLK, F_UNLCK)"
+
+
+#if (NGX_HAVE_F_READAHEAD)
+
+#define NGX_HAVE_READ_AHEAD 1
+
+#define ngx_read_ahead(fd, n) fcntl(fd, F_READAHEAD, (int) n)
+#define ngx_read_ahead_n "fcntl(fd, F_READAHEAD)"
+
+#elif (NGX_HAVE_POSIX_FADVISE)
+
+#define NGX_HAVE_READ_AHEAD 1
+
+ngx_int_t ngx_read_ahead(ngx_fd_t fd, size_t n);
+#define ngx_read_ahead_n "posix_fadvise(POSIX_FADV_SEQUENTIAL)"
+
+#else
+
+#define ngx_read_ahead(fd, n) 0
+#define ngx_read_ahead_n "ngx_read_ahead_n"
+
+#endif
+
+
+#if (NGX_HAVE_O_DIRECT)
+
+ngx_int_t ngx_directio_on(ngx_fd_t fd);
+#define ngx_directio_on_n "fcntl(O_DIRECT)"
+
+ngx_int_t ngx_directio_off(ngx_fd_t fd);
+#define ngx_directio_off_n "fcntl(!O_DIRECT)"
+
+#elif (NGX_HAVE_F_NOCACHE)
+
+#define ngx_directio_on(fd) fcntl(fd, F_NOCACHE, 1)
+#define ngx_directio_on_n "fcntl(F_NOCACHE, 1)"
+
+#elif (NGX_HAVE_DIRECTIO)
+
+#define ngx_directio_on(fd) directio(fd, DIRECTIO_ON)
+#define ngx_directio_on_n "directio(DIRECTIO_ON)"
+
+#else
+
+#define ngx_directio_on(fd) 0
+#define ngx_directio_on_n "ngx_directio_on_n"
+
+#endif
+
+size_t ngx_fs_bsize(u_char *name);
+
+
+#define ngx_stderr STDERR_FILENO
+#define ngx_set_stderr(fd) dup2(fd, STDERR_FILENO)
+#define ngx_set_stderr_n "dup2(STDERR_FILENO)"
+
+
+#if (NGX_HAVE_FILE_AIO)
+
+ssize_t ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size,
+ off_t offset, ngx_pool_t *pool);
+
+extern ngx_uint_t ngx_file_aio;
+
+#endif
+
+
+#endif /* _NGX_FILES_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_freebsd.h b/src/os/unix/ngx_freebsd.h
new file mode 100644
index 0000000..95bf57a
--- /dev/null
+++ b/src/os/unix/ngx_freebsd.h
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FREEBSD_H_INCLUDED_
+#define _NGX_FREEBSD_H_INCLUDED_
+
+
+ngx_chain_t *ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+extern int ngx_freebsd_kern_osreldate;
+extern int ngx_freebsd_hw_ncpu;
+extern u_long ngx_freebsd_net_inet_tcp_sendspace;
+
+extern ngx_uint_t ngx_freebsd_sendfile_nbytes_bug;
+extern ngx_uint_t ngx_freebsd_use_tcp_nopush;
+extern ngx_uint_t ngx_freebsd_debug_malloc;
+
+
+#endif /* _NGX_FREEBSD_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_freebsd_config.h b/src/os/unix/ngx_freebsd_config.h
new file mode 100644
index 0000000..f137936
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_config.h
@@ -0,0 +1,119 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FREEBSD_CONFIG_H_INCLUDED_
+#define _NGX_FREEBSD_CONFIG_H_INCLUDED_
+
+
+#include
+#include
+#include
+#include
+#include /* offsetof() */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include /* ALIGN() */
+#include /* statfs() */
+
+#include /* FIONBIO */
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include /* TCP_NODELAY, TCP_NOPUSH */
+#include
+#include
+#include
+
+#include /* setproctitle() before 4.1 */
+#include
+#include
+
+
+#if __FreeBSD_version < 400017
+
+/*
+ * FreeBSD 3.x has no CMSG_SPACE() and CMSG_LEN() and has the broken CMSG_DATA()
+ */
+
+#undef CMSG_SPACE
+#define CMSG_SPACE(l) (ALIGN(sizeof(struct cmsghdr)) + ALIGN(l))
+
+#undef CMSG_LEN
+#define CMSG_LEN(l) (ALIGN(sizeof(struct cmsghdr)) + (l))
+
+#undef CMSG_DATA
+#define CMSG_DATA(cmsg) ((u_char *)(cmsg) + ALIGN(sizeof(struct cmsghdr)))
+
+#endif
+
+
+#include
+
+
+#if (NGX_HAVE_POSIX_SEM)
+#include
+#endif
+
+
+#if (NGX_HAVE_POLL)
+#include
+#endif
+
+
+#if (NGX_HAVE_KQUEUE)
+#include
+#endif
+
+
+#if (NGX_HAVE_FILE_AIO || NGX_HAVE_AIO)
+#include
+typedef struct aiocb ngx_aiocb_t;
+#endif
+
+
+#define NGX_LISTEN_BACKLOG -1
+
+
+#if (__FreeBSD_version < 430000 || __FreeBSD_version < 500012)
+
+pid_t rfork_thread(int flags, void *stack, int (*func)(void *arg), void *arg);
+
+#endif
+
+#ifndef IOV_MAX
+#define IOV_MAX 1024
+#endif
+
+
+#ifndef NGX_HAVE_INHERITED_NONBLOCK
+#define NGX_HAVE_INHERITED_NONBLOCK 1
+#endif
+
+
+#define NGX_HAVE_OS_SPECIFIC_INIT 1
+
+
+extern char **environ;
+extern char *malloc_options;
+
+
+#endif /* _NGX_FREEBSD_CONFIG_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_freebsd_init.c b/src/os/unix/ngx_freebsd_init.c
new file mode 100644
index 0000000..02b3f2a
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_init.c
@@ -0,0 +1,259 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+
+/* FreeBSD 3.0 at least */
+char ngx_freebsd_kern_ostype[16];
+char ngx_freebsd_kern_osrelease[128];
+int ngx_freebsd_kern_osreldate;
+int ngx_freebsd_hw_ncpu;
+int ngx_freebsd_kern_ipc_somaxconn;
+u_long ngx_freebsd_net_inet_tcp_sendspace;
+
+/* FreeBSD 4.9 */
+int ngx_freebsd_machdep_hlt_logical_cpus;
+
+
+ngx_uint_t ngx_freebsd_sendfile_nbytes_bug;
+ngx_uint_t ngx_freebsd_use_tcp_nopush;
+ngx_uint_t ngx_freebsd_debug_malloc;
+
+
+static ngx_os_io_t ngx_freebsd_io = {
+ ngx_unix_recv,
+ ngx_readv_chain,
+ ngx_udp_unix_recv,
+ ngx_unix_send,
+#if (NGX_HAVE_SENDFILE)
+ ngx_freebsd_sendfile_chain,
+ NGX_IO_SENDFILE
+#else
+ ngx_writev_chain,
+ 0
+#endif
+};
+
+
+typedef struct {
+ char *name;
+ void *value;
+ size_t size;
+ ngx_uint_t exists;
+} sysctl_t;
+
+
+sysctl_t sysctls[] = {
+ { "hw.ncpu",
+ &ngx_freebsd_hw_ncpu,
+ sizeof(ngx_freebsd_hw_ncpu), 0 },
+
+ { "machdep.hlt_logical_cpus",
+ &ngx_freebsd_machdep_hlt_logical_cpus,
+ sizeof(ngx_freebsd_machdep_hlt_logical_cpus), 0 },
+
+ { "net.inet.tcp.sendspace",
+ &ngx_freebsd_net_inet_tcp_sendspace,
+ sizeof(ngx_freebsd_net_inet_tcp_sendspace), 0 },
+
+ { "kern.ipc.somaxconn",
+ &ngx_freebsd_kern_ipc_somaxconn,
+ sizeof(ngx_freebsd_kern_ipc_somaxconn), 0 },
+
+ { NULL, NULL, 0, 0 }
+};
+
+
+void
+ngx_debug_init()
+{
+#if (NGX_DEBUG_MALLOC)
+
+#if __FreeBSD_version >= 500014
+ _malloc_options = "J";
+#else
+ malloc_options = "J";
+#endif
+
+ ngx_freebsd_debug_malloc = 1;
+
+#else
+ char *mo;
+
+ mo = getenv("MALLOC_OPTIONS");
+
+ if (mo && ngx_strchr(mo, 'J')) {
+ ngx_freebsd_debug_malloc = 1;
+ }
+#endif
+}
+
+
+ngx_int_t
+ngx_os_specific_init(ngx_log_t *log)
+{
+ int version;
+ size_t size;
+ ngx_err_t err;
+ ngx_uint_t i;
+
+ size = sizeof(ngx_freebsd_kern_ostype);
+ if (sysctlbyname("kern.ostype",
+ ngx_freebsd_kern_ostype, &size, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysctlbyname(kern.ostype) failed");
+
+ if (ngx_errno != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_freebsd_kern_ostype[size - 1] = '\0';
+ }
+
+ size = sizeof(ngx_freebsd_kern_osrelease);
+ if (sysctlbyname("kern.osrelease",
+ ngx_freebsd_kern_osrelease, &size, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysctlbyname(kern.osrelease) failed");
+
+ if (ngx_errno != NGX_ENOMEM) {
+ return NGX_ERROR;
+ }
+
+ ngx_freebsd_kern_osrelease[size - 1] = '\0';
+ }
+
+
+ size = sizeof(int);
+ if (sysctlbyname("kern.osreldate",
+ &ngx_freebsd_kern_osreldate, &size, NULL, 0) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "sysctlbyname(kern.osreldate) failed");
+ return NGX_ERROR;
+ }
+
+ version = ngx_freebsd_kern_osreldate;
+
+
+#if (NGX_HAVE_SENDFILE)
+
+ /*
+ * The determination of the sendfile() "nbytes bug" is complex enough.
+ * There are two sendfile() syscalls: a new #393 has no bug while
+ * an old #336 has the bug in some versions and has not in others.
+ * Besides libc_r wrapper also emulates the bug in some versions.
+ * There is no way to say exactly if syscall #336 in FreeBSD circa 4.6
+ * has the bug. We use the algorithm that is correct at least for
+ * RELEASEs and for syscalls only (not libc_r wrapper).
+ *
+ * 4.6.1-RELEASE and below have the bug
+ * 4.6.2-RELEASE and above have the new syscall
+ *
+ * We detect the new sendfile() syscall available at the compile time
+ * to allow an old binary to run correctly on an updated FreeBSD system.
+ */
+
+#if (__FreeBSD__ == 4 && __FreeBSD_version >= 460102) \
+ || __FreeBSD_version == 460002 || __FreeBSD_version >= 500039
+
+ /* a new syscall without the bug */
+
+ ngx_freebsd_sendfile_nbytes_bug = 0;
+
+#else
+
+ /* an old syscall that may have the bug */
+
+ ngx_freebsd_sendfile_nbytes_bug = 1;
+
+#endif
+
+#endif /* NGX_HAVE_SENDFILE */
+
+
+ if ((version < 500000 && version >= 440003) || version >= 500017) {
+ ngx_freebsd_use_tcp_nopush = 1;
+ }
+
+
+ for (i = 0; sysctls[i].name; i++) {
+ size = sysctls[i].size;
+
+ if (sysctlbyname(sysctls[i].name, sysctls[i].value, &size, NULL, 0)
+ == 0)
+ {
+ sysctls[i].exists = 1;
+ continue;
+ }
+
+ err = ngx_errno;
+
+ if (err == NGX_ENOENT) {
+ continue;
+ }
+
+ ngx_log_error(NGX_LOG_ALERT, log, err,
+ "sysctlbyname(%s) failed", sysctls[i].name);
+ return NGX_ERROR;
+ }
+
+ if (ngx_freebsd_machdep_hlt_logical_cpus) {
+ ngx_ncpu = ngx_freebsd_hw_ncpu / 2;
+
+ } else {
+ ngx_ncpu = ngx_freebsd_hw_ncpu;
+ }
+
+ if (version < 600008 && ngx_freebsd_kern_ipc_somaxconn > 32767) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "sysctl kern.ipc.somaxconn must be less than 32768");
+ return NGX_ERROR;
+ }
+
+ ngx_tcp_nodelay_and_tcp_nopush = 1;
+
+ ngx_os_io = ngx_freebsd_io;
+
+ return NGX_OK;
+}
+
+
+void
+ngx_os_specific_status(ngx_log_t *log)
+{
+ u_long value;
+ ngx_uint_t i;
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "OS: %s %s",
+ ngx_freebsd_kern_ostype, ngx_freebsd_kern_osrelease);
+
+#ifdef __DragonFly_version
+ ngx_log_error(NGX_LOG_NOTICE, log, 0,
+ "kern.osreldate: %d, built on %d",
+ ngx_freebsd_kern_osreldate, __DragonFly_version);
+#else
+ ngx_log_error(NGX_LOG_NOTICE, log, 0,
+ "kern.osreldate: %d, built on %d",
+ ngx_freebsd_kern_osreldate, __FreeBSD_version);
+#endif
+
+ for (i = 0; sysctls[i].name; i++) {
+ if (sysctls[i].exists) {
+ if (sysctls[i].size == sizeof(long)) {
+ value = *(long *) sysctls[i].value;
+
+ } else {
+ value = *(int *) sysctls[i].value;
+ }
+
+ ngx_log_error(NGX_LOG_NOTICE, log, 0, "%s: %l",
+ sysctls[i].name, value);
+ }
+ }
+}
diff --git a/src/os/unix/ngx_freebsd_rfork_thread.c b/src/os/unix/ngx_freebsd_rfork_thread.c
new file mode 100644
index 0000000..9506b9d
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_rfork_thread.c
@@ -0,0 +1,756 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+
+/*
+ * The threads implementation uses the rfork(RFPROC|RFTHREAD|RFMEM) syscall
+ * to create threads. All threads use the stacks of the same size mmap()ed
+ * below the main stack. Thus the current thread id is determinated via
+ * the stack pointer value.
+ *
+ * The mutex implementation uses the ngx_atomic_cmp_set() operation
+ * to acquire a mutex and the SysV semaphore to wait on a mutex and to wake up
+ * the waiting threads. The light mutex does not use semaphore, so after
+ * spinning in the lock the thread calls sched_yield(). However the light
+ * mutecies are intended to be used with the "trylock" operation only.
+ * The SysV semop() is a cheap syscall, particularly if it has little sembuf's
+ * and does not use SEM_UNDO.
+ *
+ * The condition variable implementation uses the signal #64.
+ * The signal handler is SIG_IGN so the kill() is a cheap syscall.
+ * The thread waits a signal in kevent(). The use of the EVFILT_SIGNAL
+ * is safe since FreeBSD 4.10-STABLE.
+ *
+ * This threads implementation currently works on i386 (486+) and amd64
+ * platforms only.
+ */
+
+
+char *ngx_freebsd_kern_usrstack;
+size_t ngx_thread_stack_size;
+
+
+static size_t rz_size;
+static size_t usable_stack_size;
+static char *last_stack;
+
+static ngx_uint_t nthreads;
+static ngx_uint_t max_threads;
+
+static ngx_uint_t nkeys;
+static ngx_tid_t *tids; /* the threads tids array */
+void **ngx_tls; /* the threads tls's array */
+
+/* the thread-safe libc errno */
+
+static int errno0; /* the main thread's errno */
+static int *errnos; /* the threads errno's array */
+
+int *
+__error()
+{
+ int tid;
+
+ tid = ngx_gettid();
+
+ return tid ? &errnos[tid - 1] : &errno0;
+}
+
+
+/*
+ * __isthreaded enables the spinlocks in some libc functions, i.e. in malloc()
+ * and some other places. Nevertheless we protect our malloc()/free() calls
+ * by own mutex that is more efficient than the spinlock.
+ *
+ * _spinlock() is a weak referenced stub in src/lib/libc/gen/_spinlock_stub.c
+ * that does nothing.
+ */
+
+extern int __isthreaded;
+
+void
+_spinlock(ngx_atomic_t *lock)
+{
+ ngx_int_t tries;
+
+ tries = 0;
+
+ for ( ;; ) {
+
+ if (*lock) {
+ if (ngx_ncpu > 1 && tries++ < 1000) {
+ continue;
+ }
+
+ sched_yield();
+ tries = 0;
+
+ } else {
+ if (ngx_atomic_cmp_set(lock, 0, 1)) {
+ return;
+ }
+ }
+ }
+}
+
+
+/*
+ * Before FreeBSD 5.1 _spinunlock() is a simple #define in
+ * src/lib/libc/include/spinlock.h that zeroes lock.
+ *
+ * Since FreeBSD 5.1 _spinunlock() is a weak referenced stub in
+ * src/lib/libc/gen/_spinlock_stub.c that does nothing.
+ */
+
+#ifndef _spinunlock
+
+void
+_spinunlock(ngx_atomic_t *lock)
+{
+ *lock = 0;
+}
+
+#endif
+
+
+ngx_err_t
+ngx_create_thread(ngx_tid_t *tid, ngx_thread_value_t (*func)(void *arg),
+ void *arg, ngx_log_t *log)
+{
+ ngx_pid_t id;
+ ngx_err_t err;
+ char *stack, *stack_top;
+
+ if (nthreads >= max_threads) {
+ ngx_log_error(NGX_LOG_CRIT, log, 0,
+ "no more than %ui threads can be created", max_threads);
+ return NGX_ERROR;
+ }
+
+ last_stack -= ngx_thread_stack_size;
+
+ stack = mmap(last_stack, usable_stack_size, PROT_READ|PROT_WRITE,
+ MAP_STACK, -1, 0);
+
+ if (stack == MAP_FAILED) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "mmap(%p:%uz, MAP_STACK) thread stack failed",
+ last_stack, usable_stack_size);
+ return NGX_ERROR;
+ }
+
+ if (stack != last_stack) {
+ ngx_log_error(NGX_LOG_ALERT, log, 0,
+ "stack %p address was changed to %p", last_stack, stack);
+ return NGX_ERROR;
+ }
+
+ stack_top = stack + usable_stack_size;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, log, 0,
+ "thread stack: %p-%p", stack, stack_top);
+
+ ngx_set_errno(0);
+
+ id = rfork_thread(RFPROC|RFTHREAD|RFMEM, stack_top,
+ (ngx_rfork_thread_func_pt) func, arg);
+
+ err = ngx_errno;
+
+ if (id == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, err, "rfork() failed");
+
+ } else {
+ *tid = id;
+ nthreads = (ngx_freebsd_kern_usrstack - stack_top)
+ / ngx_thread_stack_size;
+ tids[nthreads] = id;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, "rfork()ed thread: %P", id);
+ }
+
+ return err;
+}
+
+
+ngx_int_t
+ngx_init_threads(int n, size_t size, ngx_cycle_t *cycle)
+{
+ char *red_zone, *zone;
+ size_t len;
+ ngx_int_t i;
+ struct sigaction sa;
+
+ max_threads = n + 1;
+
+ for (i = 0; i < n; i++) {
+ ngx_memzero(&sa, sizeof(struct sigaction));
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(NGX_CV_SIGNAL, &sa, NULL) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sigaction(%d, SIG_IGN) failed", NGX_CV_SIGNAL);
+ return NGX_ERROR;
+ }
+ }
+
+ len = sizeof(ngx_freebsd_kern_usrstack);
+ if (sysctlbyname("kern.usrstack", &ngx_freebsd_kern_usrstack, &len,
+ NULL, 0) == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "sysctlbyname(kern.usrstack) failed");
+ return NGX_ERROR;
+ }
+
+ /* the main thread stack red zone */
+ rz_size = ngx_pagesize;
+ red_zone = ngx_freebsd_kern_usrstack - (size + rz_size);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cycle->log, 0,
+ "usrstack: %p red zone: %p",
+ ngx_freebsd_kern_usrstack, red_zone);
+
+ zone = mmap(red_zone, rz_size, PROT_NONE, MAP_ANON, -1, 0);
+ if (zone == MAP_FAILED) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
+ "mmap(%p:%uz, PROT_NONE, MAP_ANON) red zone failed",
+ red_zone, rz_size);
+ return NGX_ERROR;
+ }
+
+ if (zone != red_zone) {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
+ "red zone %p address was changed to %p", red_zone, zone);
+ return NGX_ERROR;
+ }
+
+ /* create the thread errno' array */
+
+ errnos = ngx_calloc(n * sizeof(int), cycle->log);
+ if (errnos == NULL) {
+ return NGX_ERROR;
+ }
+
+ /* create the thread tids array */
+
+ tids = ngx_calloc((n + 1) * sizeof(ngx_tid_t), cycle->log);
+ if (tids == NULL) {
+ return NGX_ERROR;
+ }
+
+ tids[0] = ngx_pid;
+
+ /* create the thread tls' array */
+
+ ngx_tls = ngx_calloc(NGX_THREAD_KEYS_MAX * (n + 1) * sizeof(void *),
+ cycle->log);
+ if (ngx_tls == NULL) {
+ return NGX_ERROR;
+ }
+
+ nthreads = 1;
+
+ last_stack = zone + rz_size;
+ usable_stack_size = size;
+ ngx_thread_stack_size = size + rz_size;
+
+ /* allow the spinlock in libc malloc() */
+ __isthreaded = 1;
+
+ ngx_threaded = 1;
+
+ return NGX_OK;
+}
+
+
+ngx_tid_t
+ngx_thread_self()
+{
+ ngx_int_t tid;
+
+ tid = ngx_gettid();
+
+ if (tids == NULL) {
+ return ngx_pid;
+ }
+
+ return tids[tid];
+}
+
+
+ngx_err_t
+ngx_thread_key_create(ngx_tls_key_t *key)
+{
+ if (nkeys >= NGX_THREAD_KEYS_MAX) {
+ return NGX_ENOMEM;
+ }
+
+ *key = nkeys++;
+
+ return 0;
+}
+
+
+ngx_err_t
+ngx_thread_set_tls(ngx_tls_key_t key, void *value)
+{
+ if (key >= NGX_THREAD_KEYS_MAX) {
+ return NGX_EINVAL;
+ }
+
+ ngx_tls[key * NGX_THREAD_KEYS_MAX + ngx_gettid()] = value;
+ return 0;
+}
+
+
+ngx_mutex_t *
+ngx_mutex_init(ngx_log_t *log, ngx_uint_t flags)
+{
+ ngx_mutex_t *m;
+ union semun op;
+
+ m = ngx_alloc(sizeof(ngx_mutex_t), log);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ m->lock = 0;
+ m->log = log;
+
+ if (flags & NGX_MUTEX_LIGHT) {
+ m->semid = -1;
+ return m;
+ }
+
+ m->semid = semget(IPC_PRIVATE, 1, SEM_R|SEM_A);
+ if (m->semid == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "semget() failed");
+ return NULL;
+ }
+
+ op.val = 0;
+
+ if (semctl(m->semid, 0, SETVAL, op) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "semctl(SETVAL) failed");
+
+ if (semctl(m->semid, 0, IPC_RMID) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
+ "semctl(IPC_RMID) failed");
+ }
+
+ return NULL;
+ }
+
+ return m;
+}
+
+
+void
+ngx_mutex_destroy(ngx_mutex_t *m)
+{
+ if (semctl(m->semid, 0, IPC_RMID) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "semctl(IPC_RMID) failed");
+ }
+
+ ngx_free((void *) m);
+}
+
+
+ngx_int_t
+ngx_mutex_dolock(ngx_mutex_t *m, ngx_int_t try)
+{
+ uint32_t lock, old;
+ ngx_uint_t tries;
+ struct sembuf op;
+
+ if (!ngx_threaded) {
+ return NGX_OK;
+ }
+
+#if (NGX_DEBUG)
+ if (try) {
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "try lock mutex %p lock:%XD", m, m->lock);
+ } else {
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "lock mutex %p lock:%XD", m, m->lock);
+ }
+#endif
+
+ old = m->lock;
+ tries = 0;
+
+ for ( ;; ) {
+ if (old & NGX_MUTEX_LOCK_BUSY) {
+
+ if (try) {
+ return NGX_AGAIN;
+ }
+
+ if (ngx_ncpu > 1 && tries++ < 1000) {
+
+ /* the spinlock is used only on the SMP system */
+
+ old = m->lock;
+ continue;
+ }
+
+ if (m->semid == -1) {
+ sched_yield();
+
+ tries = 0;
+ old = m->lock;
+ continue;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p lock:%XD", m, m->lock);
+
+ /*
+ * The mutex is locked so we increase a number
+ * of the threads that are waiting on the mutex
+ */
+
+ lock = old + 1;
+
+ if ((lock & ~NGX_MUTEX_LOCK_BUSY) > nthreads) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "%D threads wait for mutex %p, "
+ "while only %ui threads are available",
+ lock & ~NGX_MUTEX_LOCK_BUSY, m, nthreads);
+ ngx_abort();
+ }
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "wait mutex %p lock:%XD", m, m->lock);
+
+ /*
+ * The number of the waiting threads has been increased
+ * and we would wait on the SysV semaphore.
+ * A semaphore should wake up us more efficiently than
+ * a simple sched_yield() or usleep().
+ */
+
+ op.sem_num = 0;
+ op.sem_op = -1;
+ op.sem_flg = 0;
+
+ if (semop(m->semid, &op, 1) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "semop() failed while waiting on mutex %p", m);
+ ngx_abort();
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex waked up %p lock:%XD", m, m->lock);
+
+ tries = 0;
+ old = m->lock;
+ continue;
+ }
+
+ old = m->lock;
+
+ } else {
+ lock = old | NGX_MUTEX_LOCK_BUSY;
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+ /* we locked the mutex */
+
+ break;
+ }
+
+ old = m->lock;
+ }
+
+ if (tries++ > 1000) {
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is contested", m);
+
+ /* the mutex is probably contested so we are giving up now */
+
+ sched_yield();
+
+ tries = 0;
+ old = m->lock;
+ }
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is locked, lock:%XD", m, m->lock);
+
+ return NGX_OK;
+}
+
+
+void
+ngx_mutex_unlock(ngx_mutex_t *m)
+{
+ uint32_t lock, old;
+ struct sembuf op;
+
+ if (!ngx_threaded) {
+ return;
+ }
+
+ old = m->lock;
+
+ if (!(old & NGX_MUTEX_LOCK_BUSY)) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, 0,
+ "trying to unlock the free mutex %p", m);
+ ngx_abort();
+ }
+
+ /* free the mutex */
+
+#if 0
+ ngx_log_debug2(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "unlock mutex %p lock:%XD", m, old);
+#endif
+
+ for ( ;; ) {
+ lock = old & ~NGX_MUTEX_LOCK_BUSY;
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+ break;
+ }
+
+ old = m->lock;
+ }
+
+ if (m->semid == -1) {
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is unlocked", m);
+
+ return;
+ }
+
+ /* check whether we need to wake up a waiting thread */
+
+ old = m->lock;
+
+ for ( ;; ) {
+ if (old & NGX_MUTEX_LOCK_BUSY) {
+
+ /* the mutex is just locked by another thread */
+
+ break;
+ }
+
+ if (old == 0) {
+ break;
+ }
+
+ /* there are the waiting threads */
+
+ lock = old - 1;
+
+ if (ngx_atomic_cmp_set(&m->lock, old, lock)) {
+
+ /* wake up the thread that waits on semaphore */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "wake up mutex %p", m);
+
+ op.sem_num = 0;
+ op.sem_op = 1;
+ op.sem_flg = 0;
+
+ if (semop(m->semid, &op, 1) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, m->log, ngx_errno,
+ "semop() failed while waking up on mutex %p", m);
+ ngx_abort();
+ }
+
+ break;
+ }
+
+ old = m->lock;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_MUTEX, m->log, 0,
+ "mutex %p is unlocked", m);
+
+ return;
+}
+
+
+ngx_cond_t *
+ngx_cond_init(ngx_log_t *log)
+{
+ ngx_cond_t *cv;
+
+ cv = ngx_alloc(sizeof(ngx_cond_t), log);
+ if (cv == NULL) {
+ return NULL;
+ }
+
+ cv->signo = NGX_CV_SIGNAL;
+ cv->tid = -1;
+ cv->log = log;
+ cv->kq = -1;
+
+ return cv;
+}
+
+
+void
+ngx_cond_destroy(ngx_cond_t *cv)
+{
+ if (close(cv->kq) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno,
+ "kqueue close() failed");
+ }
+
+ ngx_free(cv);
+}
+
+
+ngx_int_t
+ngx_cond_wait(ngx_cond_t *cv, ngx_mutex_t *m)
+{
+ int n;
+ ngx_err_t err;
+ struct kevent kev;
+ struct timespec ts;
+
+ if (cv->kq == -1) {
+
+ /*
+ * We have to add the EVFILT_SIGNAL filter in the rfork()ed thread.
+ * Otherwise the thread would not get a signal event.
+ *
+ * However, we have not to open the kqueue in the thread,
+ * it is simply handy do it together.
+ */
+
+ cv->kq = kqueue();
+ if (cv->kq == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno, "kqueue() failed");
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv kq:%d signo:%d", cv->kq, cv->signo);
+
+ kev.ident = cv->signo;
+ kev.filter = EVFILT_SIGNAL;
+ kev.flags = EV_ADD;
+ kev.fflags = 0;
+ kev.data = 0;
+ kev.udata = NULL;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+
+ if (kevent(cv->kq, &kev, 1, NULL, 0, &ts) == -1) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, ngx_errno, "kevent() failed");
+ return NGX_ERROR;
+ }
+
+ cv->tid = ngx_thread_self();
+ }
+
+ ngx_mutex_unlock(m);
+
+ ngx_log_debug3(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv %p wait, kq:%d, signo:%d", cv, cv->kq, cv->signo);
+
+ for ( ;; ) {
+ n = kevent(cv->kq, NULL, 0, &kev, 1, NULL);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv %p kevent: %d", cv, n);
+
+ if (n == -1) {
+ err = ngx_errno;
+ ngx_log_error((err == NGX_EINTR) ? NGX_LOG_INFO : NGX_LOG_ALERT,
+ cv->log, ngx_errno,
+ "kevent() failed while waiting condition variable %p",
+ cv);
+
+ if (err == NGX_EINTR) {
+ break;
+ }
+
+ return NGX_ERROR;
+ }
+
+ if (n == 0) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+ "kevent() returned no events "
+ "while waiting condition variable %p",
+ cv);
+ continue;
+ }
+
+ if (kev.filter != EVFILT_SIGNAL) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+ "kevent() returned unexpected events: %d "
+ "while waiting condition variable %p",
+ kev.filter, cv);
+ continue;
+ }
+
+ if (kev.ident != (uintptr_t) cv->signo) {
+ ngx_log_error(NGX_LOG_ALERT, cv->log, 0,
+ "kevent() returned unexpected signal: %d ",
+ "while waiting condition variable %p",
+ kev.ident, cv);
+ continue;
+ }
+
+ break;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p is waked up", cv);
+
+ ngx_mutex_lock(m);
+
+ return NGX_OK;
+}
+
+
+ngx_int_t
+ngx_cond_signal(ngx_cond_t *cv)
+{
+ ngx_err_t err;
+
+ ngx_log_debug3(NGX_LOG_DEBUG_CORE, cv->log, 0,
+ "cv %p to signal %P %d",
+ cv, cv->tid, cv->signo);
+
+ if (cv->tid == -1) {
+ return NGX_OK;
+ }
+
+ if (kill(cv->tid, cv->signo) == -1) {
+
+ err = ngx_errno;
+
+ ngx_log_error(NGX_LOG_ALERT, cv->log, err,
+ "kill() failed while signaling condition variable %p", cv);
+
+ if (err == NGX_ESRCH) {
+ cv->tid = -1;
+ }
+
+ return NGX_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_CORE, cv->log, 0, "cv %p is signaled", cv);
+
+ return NGX_OK;
+}
diff --git a/src/os/unix/ngx_freebsd_rfork_thread.h b/src/os/unix/ngx_freebsd_rfork_thread.h
new file mode 100644
index 0000000..2c238f7
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_rfork_thread.h
@@ -0,0 +1,122 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_
+#define _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_
+
+
+#include
+#include
+#include
+
+typedef pid_t ngx_tid_t;
+
+#define ngx_log_pid ngx_thread_self()
+#define ngx_log_tid 0
+
+#define NGX_TID_T_FMT "%P"
+
+
+#define NGX_MUTEX_LIGHT 1
+
+#define NGX_MUTEX_LOCK_BUSY 0x80000000
+
+typedef volatile struct {
+ ngx_atomic_t lock;
+ ngx_log_t *log;
+ int semid;
+} ngx_mutex_t;
+
+
+#define NGX_CV_SIGNAL 64
+
+typedef struct {
+ int signo;
+ int kq;
+ ngx_tid_t tid;
+ ngx_log_t *log;
+} ngx_cond_t;
+
+
+#define ngx_thread_sigmask(how, set, oset) \
+ (sigprocmask(how, set, oset) == -1) ? ngx_errno : 0
+
+#define ngx_thread_sigmask_n "sigprocmask()"
+
+#define ngx_thread_join(t, p)
+
+#define ngx_setthrtitle(n) setproctitle(n)
+
+
+extern char *ngx_freebsd_kern_usrstack;
+extern size_t ngx_thread_stack_size;
+
+
+static ngx_inline ngx_int_t
+ngx_gettid()
+{
+ char *sp;
+
+ if (ngx_thread_stack_size == 0) {
+ return 0;
+ }
+
+#if ( __i386__ )
+
+ __asm__ volatile ("mov %%esp, %0" : "=q" (sp));
+
+#elif ( __amd64__ )
+
+ __asm__ volatile ("mov %%rsp, %0" : "=q" (sp));
+
+#else
+
+#error "rfork()ed threads are not supported on this platform"
+
+#endif
+
+ return (ngx_freebsd_kern_usrstack - sp) / ngx_thread_stack_size;
+}
+
+
+ngx_tid_t ngx_thread_self();
+
+
+typedef ngx_uint_t ngx_tls_key_t;
+
+#define NGX_THREAD_KEYS_MAX 16
+
+extern void **ngx_tls;
+
+ngx_err_t ngx_thread_key_create(ngx_tls_key_t *key);
+#define ngx_thread_key_create_n "the tls key creation"
+
+ngx_err_t ngx_thread_set_tls(ngx_tls_key_t key, void *value);
+#define ngx_thread_set_tls_n "the tls key setting"
+
+
+static void *
+ngx_thread_get_tls(ngx_tls_key_t key)
+{
+ if (key >= NGX_THREAD_KEYS_MAX) {
+ return NULL;
+ }
+
+ return ngx_tls[key * NGX_THREAD_KEYS_MAX + ngx_gettid()];
+}
+
+
+#define ngx_mutex_trylock(m) ngx_mutex_dolock(m, 1)
+#define ngx_mutex_lock(m) (void) ngx_mutex_dolock(m, 0)
+ngx_int_t ngx_mutex_dolock(ngx_mutex_t *m, ngx_int_t try);
+void ngx_mutex_unlock(ngx_mutex_t *m);
+
+
+typedef int (*ngx_rfork_thread_func_pt)(void *arg);
+
+
+#endif /* _NGX_FREEBSD_RFORK_THREAD_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_freebsd_sendfile_chain.c b/src/os/unix/ngx_freebsd_sendfile_chain.c
new file mode 100644
index 0000000..26b3267
--- /dev/null
+++ b/src/os/unix/ngx_freebsd_sendfile_chain.c
@@ -0,0 +1,436 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+/*
+ * Although FreeBSD sendfile() allows to pass a header and a trailer,
+ * it cannot send a header with a part of the file in one packet until
+ * FreeBSD 5.3. Besides, over the fast ethernet connection sendfile()
+ * may send the partially filled packets, i.e. the 8 file pages may be sent
+ * as the 11 full 1460-bytes packets, then one incomplete 324-bytes packet,
+ * and then again the 11 full 1460-bytes packets.
+ *
+ * Threfore we use the TCP_NOPUSH option (similar to Linux's TCP_CORK)
+ * to postpone the sending - it not only sends a header and the first part of
+ * the file in one packet, but also sends the file pages in the full packets.
+ *
+ * But until FreeBSD 4.5 turning TCP_NOPUSH off does not flush a pending
+ * data that less than MSS, so that data may be sent with 5 second delay.
+ * So we do not use TCP_NOPUSH on FreeBSD prior to 4.5, although it can be used
+ * for non-keepalive HTTP connections.
+ */
+
+
+#if (IOV_MAX > 64)
+#define NGX_HEADERS 64
+#define NGX_TRAILERS 64
+#else
+#define NGX_HEADERS IOV_MAX
+#define NGX_TRAILERS IOV_MAX
+#endif
+
+
+ngx_chain_t *
+ngx_freebsd_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
+{
+ int rc, flags;
+ u_char *prev;
+ off_t size, send, prev_send, aligned, sent, fprev;
+ size_t header_size, file_size;
+ ngx_uint_t eintr, eagain, complete;
+ ngx_err_t err;
+ ngx_buf_t *file;
+ ngx_array_t header, trailer;
+ ngx_event_t *wev;
+ ngx_chain_t *cl;
+ struct sf_hdtr hdtr;
+ struct iovec *iov, headers[NGX_HEADERS], trailers[NGX_TRAILERS];
+
+ wev = c->write;
+
+ if (!wev->ready) {
+ return in;
+ }
+
+#if (NGX_HAVE_KQUEUE)
+
+ if ((ngx_event_flags & NGX_USE_KQUEUE_EVENT) && wev->pending_eof) {
+ (void) ngx_connection_error(c, wev->kq_errno,
+ "kevent() reported about an closed connection");
+ wev->error = 1;
+ return NGX_CHAIN_ERROR;
+ }
+
+#endif
+
+ /* the maximum limit size is the maximum size_t value - the page size */
+
+ if (limit == 0 || limit > (off_t) (NGX_MAX_SIZE_T_VALUE - ngx_pagesize)) {
+ limit = NGX_MAX_SIZE_T_VALUE - ngx_pagesize;
+ }
+
+ send = 0;
+ eagain = 0;
+ flags = 0;
+
+ header.elts = headers;
+ header.size = sizeof(struct iovec);
+ header.nalloc = NGX_HEADERS;
+ header.pool = c->pool;
+
+ trailer.elts = trailers;
+ trailer.size = sizeof(struct iovec);
+ trailer.nalloc = NGX_TRAILERS;
+ trailer.pool = c->pool;
+
+ for ( ;; ) {
+ file = NULL;
+ file_size = 0;
+ header_size = 0;
+ eintr = 0;
+ complete = 0;
+ prev_send = send;
+
+ header.nelts = 0;
+ trailer.nelts = 0;
+
+ /* create the header iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ for (cl = in;
+ cl && header.nelts < IOV_MAX && send < limit;
+ cl = cl->next)
+ {
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&header);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ header_size += (size_t) size;
+ send += size;
+ }
+
+
+ if (cl && cl->buf->in_file && send < limit) {
+ file = cl->buf;
+
+ /* coalesce the neighbouring file bufs */
+
+ do {
+ size = cl->buf->file_last - cl->buf->file_pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+
+ aligned = (cl->buf->file_pos + size + ngx_pagesize - 1)
+ & ~((off_t) ngx_pagesize - 1);
+
+ if (aligned <= cl->buf->file_last) {
+ size = aligned - cl->buf->file_pos;
+ }
+ }
+
+ file_size += (size_t) size;
+ send += size;
+ fprev = cl->buf->file_pos + size;
+ cl = cl->next;
+
+ } while (cl
+ && cl->buf->in_file
+ && send < limit
+ && file->file->fd == cl->buf->file->fd
+ && fprev == cl->buf->file_pos);
+ }
+
+
+ if (file) {
+
+ /* create the trailer iovec and coalesce the neighbouring bufs */
+
+ prev = NULL;
+ iov = NULL;
+
+ while (cl && header.nelts < IOV_MAX && send < limit) {
+
+ if (ngx_buf_special(cl->buf)) {
+ cl = cl->next;
+ continue;
+ }
+
+ if (!ngx_buf_in_memory_only(cl->buf)) {
+ break;
+ }
+
+ size = cl->buf->last - cl->buf->pos;
+
+ if (send + size > limit) {
+ size = limit - send;
+ }
+
+ if (prev == cl->buf->pos) {
+ iov->iov_len += (size_t) size;
+
+ } else {
+ iov = ngx_array_push(&trailer);
+ if (iov == NULL) {
+ return NGX_CHAIN_ERROR;
+ }
+
+ iov->iov_base = (void *) cl->buf->pos;
+ iov->iov_len = (size_t) size;
+ }
+
+ prev = cl->buf->pos + (size_t) size;
+ send += size;
+ cl = cl->next;
+ }
+ }
+
+ if (file) {
+
+ if (ngx_freebsd_use_tcp_nopush
+ && c->tcp_nopush == NGX_TCP_NOPUSH_UNSET)
+ {
+ if (ngx_tcp_nopush(c->fd) == NGX_ERROR) {
+ err = ngx_errno;
+
+ /*
+ * there is a tiny chance to be interrupted, however,
+ * we continue a processing without the TCP_NOPUSH
+ */
+
+ if (err != NGX_EINTR) {
+ wev->error = 1;
+ (void) ngx_connection_error(c, err,
+ ngx_tcp_nopush_n " failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ } else {
+ c->tcp_nopush = NGX_TCP_NOPUSH_SET;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "tcp_nopush");
+ }
+ }
+
+ /*
+ * sendfile() does unneeded work if sf_hdtr's count is 0,
+ * but corresponding pointer is not NULL
+ */
+
+ hdtr.headers = header.nelts ? (struct iovec *) header.elts: NULL;
+ hdtr.hdr_cnt = header.nelts;
+ hdtr.trailers = trailer.nelts ? (struct iovec *) trailer.elts: NULL;
+ hdtr.trl_cnt = trailer.nelts;
+
+ /*
+ * the "nbytes bug" of the old sendfile() syscall:
+ * http://www.freebsd.org/cgi/query-pr.cgi?pr=33771
+ */
+
+ if (!ngx_freebsd_sendfile_nbytes_bug) {
+ header_size = 0;
+ }
+
+ sent = 0;
+
+#if (NGX_HAVE_AIO_SENDFILE)
+ flags = c->aio_sendfile ? SF_NODISKIO : 0;
+#endif
+
+ rc = sendfile(file->file->fd, c->fd, file->file_pos,
+ file_size + header_size, &hdtr, &sent, flags);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ eagain = 1;
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+#if (NGX_HAVE_AIO_SENDFILE)
+ case NGX_EBUSY:
+ c->busy_sendfile = file;
+ break;
+#endif
+
+ default:
+ wev->error = 1;
+ (void) ngx_connection_error(c, err, "sendfile() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "sendfile() sent only %O bytes", sent);
+
+ /*
+ * sendfile() in FreeBSD 3.x-4.x may return value >= 0
+ * on success, although only 0 is documented
+ */
+
+ } else if (rc >= 0 && sent == 0) {
+
+ /*
+ * if rc is OK and sent equal to zero, then someone
+ * has truncated the file, so the offset became beyond
+ * the end of the file
+ */
+
+ ngx_log_error(NGX_LOG_ALERT, c->log, 0,
+ "sendfile() reported that \"%s\" was truncated at %O",
+ file->file->name.data, file->file_pos);
+
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "sendfile: %d, @%O %O:%uz",
+ rc, file->file_pos, sent, file_size + header_size);
+
+ } else {
+ rc = writev(c->fd, header.elts, header.nelts);
+
+ ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
+ "writev: %d of %uz", rc, header_size);
+
+ if (rc == -1) {
+ err = ngx_errno;
+
+ switch (err) {
+ case NGX_EAGAIN:
+ break;
+
+ case NGX_EINTR:
+ eintr = 1;
+ break;
+
+ default:
+ wev->error = 1;
+ ngx_connection_error(c, err, "writev() failed");
+ return NGX_CHAIN_ERROR;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
+ "writev() not ready");
+ }
+
+ sent = rc > 0 ? rc : 0;
+ }
+
+ if (send - prev_send == sent) {
+ complete = 1;
+ }
+
+ c->sent += sent;
+
+ for (cl = in; cl; cl = cl->next) {
+
+ if (ngx_buf_special(cl->buf)) {
+ continue;
+ }
+
+ if (sent == 0) {
+ break;
+ }
+
+ size = ngx_buf_size(cl->buf);
+
+ if (sent >= size) {
+ sent -= size;
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos = cl->buf->last;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos = cl->buf->file_last;
+ }
+
+ continue;
+ }
+
+ if (ngx_buf_in_memory(cl->buf)) {
+ cl->buf->pos += (size_t) sent;
+ }
+
+ if (cl->buf->in_file) {
+ cl->buf->file_pos += sent;
+ }
+
+ break;
+ }
+
+#if (NGX_HAVE_AIO_SENDFILE)
+ if (c->busy_sendfile) {
+ return cl;
+ }
+#endif
+
+ if (eagain) {
+
+ /*
+ * sendfile() may return EAGAIN, even if it has sent a whole file
+ * part, it indicates that the successive sendfile() call would
+ * return EAGAIN right away and would not send anything.
+ * We use it as a hint.
+ */
+
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (eintr) {
+ continue;
+ }
+
+ if (!complete) {
+ wev->ready = 0;
+ return cl;
+ }
+
+ if (send >= limit || cl == NULL) {
+ return cl;
+ }
+
+ in = cl;
+ }
+}
diff --git a/src/os/unix/ngx_gcc_atomic_amd64.h b/src/os/unix/ngx_gcc_atomic_amd64.h
new file mode 100644
index 0000000..159a297
--- /dev/null
+++ b/src/os/unix/ngx_gcc_atomic_amd64.h
@@ -0,0 +1,82 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#if (NGX_SMP)
+#define NGX_SMP_LOCK "lock;"
+#else
+#define NGX_SMP_LOCK
+#endif
+
+
+/*
+ * "cmpxchgq r, [m]":
+ *
+ * if (rax == [m]) {
+ * zf = 1;
+ * [m] = r;
+ * } else {
+ * zf = 0;
+ * rax = [m];
+ * }
+ *
+ *
+ * The "r" is any register, %rax (%r0) - %r16.
+ * The "=a" and "a" are the %rax register.
+ * Although we can return result in any register, we use "a" because it is
+ * used in cmpxchgq anyway. The result is actually in %al but not in $rax,
+ * however as the code is inlined gcc can test %al as well as %rax.
+ *
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ u_char res;
+
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " cmpxchgq %3, %1; "
+ " sete %0; "
+
+ : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
+
+ return res;
+}
+
+
+/*
+ * "xaddq r, [m]":
+ *
+ * temp = [m];
+ * [m] += r;
+ * r = temp;
+ *
+ *
+ * The "+r" is any register, %rax (%r0) - %r16.
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " xaddq %0, %1; "
+
+ : "+r" (add) : "m" (*value) : "cc", "memory");
+
+ return add;
+}
+
+
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+
+#define ngx_cpu_pause() __asm__ ("pause")
diff --git a/src/os/unix/ngx_gcc_atomic_ppc.h b/src/os/unix/ngx_gcc_atomic_ppc.h
new file mode 100644
index 0000000..45afc4b
--- /dev/null
+++ b/src/os/unix/ngx_gcc_atomic_ppc.h
@@ -0,0 +1,155 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+/*
+ * The ppc assembler treats ";" as comment, so we have to use "\n".
+ * The minus in "bne-" is a hint for the branch prediction unit that
+ * this branch is unlikely to be taken.
+ * The "1b" means the nearest backward label "1" and the "1f" means
+ * the nearest forward label "1".
+ *
+ * The "b" means that the base registers can be used only, i.e.
+ * any register except r0. The r0 register always has a zero value and
+ * could not be used in "addi r0, r0, 1".
+ * The "=&b" means that no input registers can be used.
+ *
+ * "sync" read and write barriers
+ * "isync" read barrier, is faster than "sync"
+ * "eieio" write barrier, is faster than "sync"
+ * "lwsync" write barrier, is faster than "eieio" on ppc64
+ */
+
+#if (NGX_PTR_SIZE == 8)
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " li %0, 0 \n" /* preset "0" to "res" */
+ " lwsync \n" /* write barrier */
+ "1: \n"
+ " ldarx %1, 0, %2 \n" /* load from [lock] into "temp" */
+ /* and store reservation */
+ " cmpd %1, %3 \n" /* compare "temp" and "old" */
+ " bne- 2f \n" /* not equal */
+ " stdcx. %4, 0, %2 \n" /* store "set" into [lock] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* the reservation was cleared */
+ " isync \n" /* read barrier */
+ " li %0, 1 \n" /* set "1" to "res" */
+ "2: \n"
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (lock), "b" (old), "b" (set)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " lwsync \n" /* write barrier */
+ "1: ldarx %0, 0, %2 \n" /* load from [value] into "res" */
+ /* and store reservation */
+ " add %1, %0, %3 \n" /* "res" + "add" store in "temp" */
+ " stdcx. %1, 0, %2 \n" /* store "temp" into [value] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* try again if reservation was cleared */
+ " isync \n" /* read barrier */
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (value), "b" (add)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier() \
+ __asm__ volatile ("isync \n lwsync \n" ::: "memory")
+#else
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+#endif
+
+#else
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " li %0, 0 \n" /* preset "0" to "res" */
+ " eieio \n" /* write barrier */
+ "1: \n"
+ " lwarx %1, 0, %2 \n" /* load from [lock] into "temp" */
+ /* and store reservation */
+ " cmpw %1, %3 \n" /* compare "temp" and "old" */
+ " bne- 2f \n" /* not equal */
+ " stwcx. %4, 0, %2 \n" /* store "set" into [lock] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* the reservation was cleared */
+ " isync \n" /* read barrier */
+ " li %0, 1 \n" /* set "1" to "res" */
+ "2: \n"
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (lock), "b" (old), "b" (set)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t res, temp;
+
+ __asm__ volatile (
+
+ " eieio \n" /* write barrier */
+ "1: lwarx %0, 0, %2 \n" /* load from [value] into "res" */
+ /* and store reservation */
+ " add %1, %0, %3 \n" /* "res" + "add" store in "temp" */
+ " stwcx. %1, 0, %2 \n" /* store "temp" into [value] if reservation */
+ /* is not cleared */
+ " bne- 1b \n" /* try again if reservation was cleared */
+ " isync \n" /* read barrier */
+
+ : "=&b" (res), "=&b" (temp)
+ : "b" (value), "b" (add)
+ : "cc", "memory");
+
+ return res;
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier() \
+ __asm__ volatile ("isync \n eieio \n" ::: "memory")
+#else
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+#endif
+
+#endif
+
+
+#define ngx_cpu_pause()
diff --git a/src/os/unix/ngx_gcc_atomic_sparc64.h b/src/os/unix/ngx_gcc_atomic_sparc64.h
new file mode 100644
index 0000000..d26e130
--- /dev/null
+++ b/src/os/unix/ngx_gcc_atomic_sparc64.h
@@ -0,0 +1,82 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+/*
+ * "casa [r1] 0x80, r2, r0" and
+ * "casxa [r1] 0x80, r2, r0" do the following:
+ *
+ * if ([r1] == r2) {
+ * swap(r0, [r1]);
+ * } else {
+ * r0 = [r1];
+ * }
+ *
+ * so "r0 == r2" means that the operation was successfull.
+ *
+ *
+ * The "r" means the general register.
+ * The "+r" means the general register used for both input and output.
+ */
+
+
+#if (NGX_PTR_SIZE == 4)
+#define NGX_CASA "casa"
+#else
+#define NGX_CASA "casxa"
+#endif
+
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ __asm__ volatile (
+
+ NGX_CASA " [%1] 0x80, %2, %0"
+
+ : "+r" (set) : "r" (lock), "r" (old) : "memory");
+
+ return (set == old);
+}
+
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t old, res;
+
+ old = *value;
+
+ for ( ;; ) {
+
+ res = old + add;
+
+ __asm__ volatile (
+
+ NGX_CASA " [%1] 0x80, %2, %0"
+
+ : "+r" (res) : "r" (value), "r" (old) : "memory");
+
+ if (res == old) {
+ return res;
+ }
+
+ old = res;
+ }
+}
+
+
+#if (NGX_SMP)
+#define ngx_memory_barrier() \
+ __asm__ volatile ( \
+ "membar #LoadLoad | #LoadStore | #StoreStore | #StoreLoad" \
+ ::: "memory")
+#else
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+#endif
+
+#define ngx_cpu_pause()
diff --git a/src/os/unix/ngx_gcc_atomic_x86.h b/src/os/unix/ngx_gcc_atomic_x86.h
new file mode 100644
index 0000000..54e01ae
--- /dev/null
+++ b/src/os/unix/ngx_gcc_atomic_x86.h
@@ -0,0 +1,127 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#if (NGX_SMP)
+#define NGX_SMP_LOCK "lock;"
+#else
+#define NGX_SMP_LOCK
+#endif
+
+
+/*
+ * "cmpxchgl r, [m]":
+ *
+ * if (eax == [m]) {
+ * zf = 1;
+ * [m] = r;
+ * } else {
+ * zf = 0;
+ * eax = [m];
+ * }
+ *
+ *
+ * The "r" means the general register.
+ * The "=a" and "a" are the %eax register.
+ * Although we can return result in any register, we use "a" because it is
+ * used in cmpxchgl anyway. The result is actually in %al but not in %eax,
+ * however, as the code is inlined gcc can test %al as well as %eax,
+ * and icc adds "movzbl %al, %eax" by itself.
+ *
+ * The "cc" means that flags were changed.
+ */
+
+static ngx_inline ngx_atomic_uint_t
+ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
+ ngx_atomic_uint_t set)
+{
+ u_char res;
+
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " cmpxchgl %3, %1; "
+ " sete %0; "
+
+ : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");
+
+ return res;
+}
+
+
+/*
+ * "xaddl r, [m]":
+ *
+ * temp = [m];
+ * [m] += r;
+ * r = temp;
+ *
+ *
+ * The "+r" means the general register.
+ * The "cc" means that flags were changed.
+ */
+
+
+#if !(( __GNUC__ == 2 && __GNUC_MINOR__ <= 7 ) || ( __INTEL_COMPILER >= 800 ))
+
+/*
+ * icc 8.1 and 9.0 compile broken code with -march=pentium4 option:
+ * ngx_atomic_fetch_add() always return the input "add" value,
+ * so we use the gcc 2.7 version.
+ *
+ * icc 8.1 and 9.0 with -march=pentiumpro option or icc 7.1 compile
+ * correct code.
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " xaddl %0, %1; "
+
+ : "+r" (add) : "m" (*value) : "cc", "memory");
+
+ return add;
+}
+
+
+#else
+
+/*
+ * gcc 2.7 does not support "+r", so we have to use the fixed
+ * %eax ("=a" and "a") and this adds two superfluous instructions in the end
+ * of code, something like this: "mov %eax, %edx / mov %edx, %eax".
+ */
+
+static ngx_inline ngx_atomic_int_t
+ngx_atomic_fetch_add(ngx_atomic_t *value, ngx_atomic_int_t add)
+{
+ ngx_atomic_uint_t old;
+
+ __asm__ volatile (
+
+ NGX_SMP_LOCK
+ " xaddl %2, %1; "
+
+ : "=a" (old) : "m" (*value), "a" (add) : "cc", "memory");
+
+ return old;
+}
+
+#endif
+
+
+/*
+ * on x86 the write operations go in a program order, so we need only
+ * to disable the gcc reorder optimizations
+ */
+
+#define ngx_memory_barrier() __asm__ volatile ("" ::: "memory")
+
+/* old "as" does not support "pause" opcode */
+#define ngx_cpu_pause() __asm__ (".byte 0xf3, 0x90")
diff --git a/src/os/unix/ngx_linux.h b/src/os/unix/ngx_linux.h
new file mode 100644
index 0000000..1b8bdac
--- /dev/null
+++ b/src/os/unix/ngx_linux.h
@@ -0,0 +1,18 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_LINUX_H_INCLUDED_
+#define _NGX_LINUX_H_INCLUDED_
+
+
+ngx_chain_t *ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in,
+ off_t limit);
+
+extern int ngx_linux_rtsig_max;
+
+
+#endif /* _NGX_LINUX_H_INCLUDED_ */
diff --git a/src/os/unix/ngx_linux_aio_read.c b/src/os/unix/ngx_linux_aio_read.c
new file mode 100644
index 0000000..8273c13
--- /dev/null
+++ b/src/os/unix/ngx_linux_aio_read.c
@@ -0,0 +1,137 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#include
+#include
+#include
+
+
+extern int ngx_eventfd;
+extern aio_context_t ngx_aio_ctx;
+
+
+static void ngx_file_aio_event_handler(ngx_event_t *ev);
+
+
+static int
+io_submit(aio_context_t ctx, long n, struct iocb **paiocb)
+{
+ return syscall(SYS_io_submit, ctx, n, paiocb);
+}
+
+
+ssize_t
+ngx_file_aio_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset,
+ ngx_pool_t *pool)
+{
+ ngx_err_t err;
+ struct iocb *piocb[1];
+ ngx_event_t *ev;
+ ngx_event_aio_t *aio;
+
+ if (!ngx_file_aio) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ aio = file->aio;
+
+ if (aio == NULL) {
+ aio = ngx_pcalloc(pool, sizeof(ngx_event_aio_t));
+ if (aio == NULL) {
+ return NGX_ERROR;
+ }
+
+ aio->file = file;
+ aio->fd = file->fd;
+ aio->event.data = aio;
+ aio->event.ready = 1;
+ aio->event.log = file->log;
+ file->aio = aio;
+ }
+
+ ev = &aio->event;
+
+ if (!ev->ready) {
+ ngx_log_error(NGX_LOG_ALERT, file->log, 0,
+ "second aio post for \"%V\"", &file->name);
+ return NGX_AGAIN;
+ }
+
+ ngx_log_debug4(NGX_LOG_DEBUG_CORE, file->log, 0,
+ "aio complete:%d @%O:%z %V",
+ ev->complete, offset, size, &file->name);
+
+ if (ev->complete) {
+ ev->active = 0;
+ ev->complete = 0;
+
+ if (aio->res >= 0) {
+ ngx_set_errno(0);
+ return aio->res;
+ }
+
+ ngx_set_errno(-aio->res);
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, ngx_errno,
+ "aio read \"%s\" failed", file->name.data);
+
+ return NGX_ERROR;
+ }
+
+ ngx_memzero(&aio->aiocb, sizeof(struct iocb));
+
+ aio->aiocb.aio_data = (uint64_t) (uintptr_t) ev;
+ aio->aiocb.aio_lio_opcode = IOCB_CMD_PREAD;
+ aio->aiocb.aio_fildes = file->fd;
+ aio->aiocb.aio_buf = (uint64_t) (uintptr_t) buf;
+ aio->aiocb.aio_nbytes = size;
+ aio->aiocb.aio_offset = offset;
+ aio->aiocb.aio_flags = IOCB_FLAG_RESFD;
+ aio->aiocb.aio_resfd = ngx_eventfd;
+
+ ev->handler = ngx_file_aio_event_handler;
+
+ piocb[0] = &aio->aiocb;
+
+ if (io_submit(ngx_aio_ctx, 1, piocb) == 1) {
+ ev->active = 1;
+ ev->ready = 0;
+ ev->complete = 0;
+
+ return NGX_AGAIN;
+ }
+
+ err = ngx_errno;
+
+ if (err == NGX_EAGAIN) {
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, file->log, err,
+ "io_submit(\"%V\") failed", &file->name);
+
+ if (err == NGX_ENOSYS) {
+ ngx_file_aio = 0;
+ return ngx_read_file(file, buf, size, offset);
+ }
+
+ return NGX_ERROR;
+}
+
+
+static void
+ngx_file_aio_event_handler(ngx_event_t *ev)
+{
+ ngx_event_aio_t *aio;
+
+ aio = ev->data;
+
+ ngx_log_debug2(NGX_LOG_DEBUG_CORE, ev->log, 0,
+ "aio event handler fd:%d %V", aio->fd, &aio->file->name);
+
+ aio->handler(ev);
+}
diff --git a/src/os/unix/ngx_linux_config.h b/src/os/unix/ngx_linux_config.h
new file mode 100644
index 0000000..7cf4eac
--- /dev/null
+++ b/src/os/unix/ngx_linux_config.h
@@ -0,0 +1,117 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) Nginx, Inc.
+ */
+
+
+#ifndef _NGX_LINUX_CONFIG_H_INCLUDED_
+#define _NGX_LINUX_CONFIG_H_INCLUDED_
+
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE /* pread(), pwrite(), gethostname() */
+#endif
+
+#define _FILE_OFFSET_BITS 64
+
+#include
+#include
+#include
+#include
+#include /* offsetof() */
+#include