net-server 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/README.md +158 -0
- data/lib/net/server.crt +17 -0
- data/lib/net/server.key +27 -0
- data/lib/net/server.rb +237 -0
- data/lib/net/version.rb +4 -0
- metadata +50 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: befb3239d96b9cb8652bd568665eb15fe7b17d06
|
4
|
+
data.tar.gz: 9c7ae1db07ae3a5ce071ec49c346b02bb8d659ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a331a3bf1acb62e4d1eb820a642e1195376923ff724cae5abdef51f057737780573585f1f90c163ae1656f40920a488cda7c92c170aea04b1d667d96898881f4
|
7
|
+
data.tar.gz: 22c82197fa151e9aaa114b44f199d70de7c59e64ed3a45b6d8103999064524f594ba6f0ba7407e12d5bc4fe56e87002a870ddf08066420d02c3c4c57d782ab62
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,2 @@
|
|
1
|
+
# v1.0
|
2
|
+
This is the initial load of the gem. This gem was taken from an earlier work of mine, so as far as I know, it is bug-free (but never count on code being bug free). If you find a problem, report it to me at mjwelchphd@gmail.com, or FORK the library, fix the bug, then add a Pull Request.
|
data/README.md
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
# Net::Server
|
2
|
+
The Net::Server Ruby gem is a server for web apps. It's purpose is to establish one or more listeners, and create a process to handle each incoming request.
|
3
|
+
|
4
|
+
#### It has the following features:
|
5
|
+
1. It can listen on any number of ports simultaneously.
|
6
|
+
2. It starts a separate receiver process to handle each connection.
|
7
|
+
3. The server may run as root, but the processes will lose their root privileges soon after creation. This is a security feature.
|
8
|
+
4. The receiver processes can switch on full encryption (STARTTLS in a mail server, for example).
|
9
|
+
5. A log file will be used, if it exists. You create the log using the 'logger' gem.
|
10
|
+
7. Runs until terminated by a `KILL -INT <pid>` or `^C`.
|
11
|
+
9. Two example programs are included.
|
12
|
+
|
13
|
+
### TODO
|
14
|
+
* RSPEC tests.
|
15
|
+
|
16
|
+
# Gem Dependancies
|
17
|
+
This gem requires the following:
|
18
|
+
```ruby
|
19
|
+
require 'openssl'
|
20
|
+
require 'socket'
|
21
|
+
```
|
22
|
+
Both of these packages are found in the Ruby Standard Library (stdib 2.2.2 at the time of this writing). They are required in the gem itself, so you don't have to require them.
|
23
|
+
|
24
|
+
# Creating a Self-Signed Certificate
|
25
|
+
Use OpenSSL to create a self-signed certificate for testing as follows:
|
26
|
+
```bash
|
27
|
+
$ openssl req -x509 -newkey rsa:2048 -keyout example.key -out example.crt -days 9000 -nodes
|
28
|
+
$ chmod 400 example.key
|
29
|
+
$ chmod 444 example.crt
|
30
|
+
```
|
31
|
+
Put the two files anywhere you want, and specify their path in the start call (see the last line of the example programs). If you don't specify these two files, they will default to the ones that come with the gem, but since anyone can download the gem and look at the files, they are not secure.
|
32
|
+
|
33
|
+
# How to Get Net::Server Gem
|
34
|
+
|
35
|
+
To install the gem, simply use the *gem* application:
|
36
|
+
```bash
|
37
|
+
$ sudo gem install net-server
|
38
|
+
```
|
39
|
+
Alternately, you can clone the project on GitHub at:
|
40
|
+
```bash
|
41
|
+
https://github.com/mjwelchphd/net-server
|
42
|
+
```
|
43
|
+
and build it yourself.
|
44
|
+
|
45
|
+
The example programs are only found in the source on GitHub at github.com/mjwelchphd/net-server.
|
46
|
+
|
47
|
+
If you're using Builder, follow your normal routine for adding gems.
|
48
|
+
|
49
|
+
# How to Build a Basic Server
|
50
|
+
|
51
|
+
The basic server looks like this:
|
52
|
+
```ruby
|
53
|
+
require 'net/server'
|
54
|
+
|
55
|
+
class Receiver
|
56
|
+
|
57
|
+
def receive(local_port, local_hostname, remote_port, remote_hostname, remote_ip)
|
58
|
+
<send and receive data>
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
LOG = Logger::new(<log-file-path>, <log-file-name>)
|
64
|
+
|
65
|
+
Net::Server.new(<options>).start
|
66
|
+
```
|
67
|
+
There are two test applications included in the gem in the examples folder. Test1 just echos a telnet connection. Test2 implements a simple email receiver you can test with swaks.
|
68
|
+
|
69
|
+
## Options
|
70
|
+
|
71
|
+
Option | Default | Description
|
72
|
+
--- | --- | ---
|
73
|
+
:server_name | "example.com" | This name is only used in error messages.
|
74
|
+
:listening_ports | ["25","486","587"] | An array of one or more ports to listen on.
|
75
|
+
:private_key | Internal key | The key for encrypting/decrypting the data when in TLS mode.
|
76
|
+
:certificate | Internal self-signed certificate | The certificate for encrypting/decrypting the data when in TLS mode. This may be your own self-signed certificate, or one you purchase from a Certificate Authority, or you can become a Certificate Authority and sign your own.
|
77
|
+
:user_name | nil | This name is the user name to which each process will be switched after it is created. If it is nil, the ownership of the process will not be changed after creation. If you are using a port less than 1024, you must start the server as root, and the user name and group name of the process _must be_ specified.
|
78
|
+
:group_name | nil | This name is the group name to which each process will be switched after it is created.
|
79
|
+
:working_directory | the current path | The location of the program running the server.
|
80
|
+
:pid_file | "pid" | The PID of the server will be stored in this file.
|
81
|
+
:daemon | false | If this option is true, the server will be started as a daemon.
|
82
|
+
|
83
|
+
## Logging
|
84
|
+
|
85
|
+
The log file must be created by the 'logger' gem, and must be named LOG.
|
86
|
+
|
87
|
+
## Terminating the Server
|
88
|
+
|
89
|
+
### HUP and INT (^C) traps
|
90
|
+
|
91
|
+
A `kill -INT <pid>` or `<ctrl-C>` will terminate the server.
|
92
|
+
```bash
|
93
|
+
$ test1.rb
|
94
|
+
^C
|
95
|
+
test1 terminated by admin ^C
|
96
|
+
$
|
97
|
+
```
|
98
|
+
or
|
99
|
+
```bash
|
100
|
+
sudo kill -INT `cat /run/net-server/net-server.pid`
|
101
|
+
```
|
102
|
+
|
103
|
+
A `kill -HUP <pid>` will activate a restart method, if you have one defined in your code. For example, if you put this in your code:
|
104
|
+
```ruby
|
105
|
+
class Server
|
106
|
+
def restart
|
107
|
+
puts "I just got a HUP request."
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
then at another terminal enter:
|
112
|
+
```ruby
|
113
|
+
$ ps ax | grep test1
|
114
|
+
823 pts/0 Sl+ 0:00 test1.rb
|
115
|
+
829 pts/1 S+ 0:00 grep --color=auto test1
|
116
|
+
$ kill -hup 823
|
117
|
+
```
|
118
|
+
or
|
119
|
+
```bash
|
120
|
+
kill -HUP `cat pid`
|
121
|
+
```
|
122
|
+
it will result in:
|
123
|
+
```bash
|
124
|
+
net-server received a HUP request
|
125
|
+
I just got a HUP request.
|
126
|
+
```
|
127
|
+
at the terminal where `test1.rb` is running, with no other action. The first message comes from `net-server` itself, and the second comes from the `def restart` you defined.
|
128
|
+
|
129
|
+
#### Example
|
130
|
+
|
131
|
+
If you define your options in a class variable Hash object, say @options, if the values change, the new values take effect immediately, except for :daemon which requires a manual restart. For instance,
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
@options = {
|
135
|
+
:server_name=>"www.example.com",
|
136
|
+
:private_key=>"server.key",
|
137
|
+
:certificate=>"server.crt",
|
138
|
+
:listening_ports=>['2000','2001']
|
139
|
+
:user_name=>"myusername",
|
140
|
+
:group_name=>"mygroupname",
|
141
|
+
:daemon = true
|
142
|
+
}
|
143
|
+
Net::Server::new(@options).start
|
144
|
+
```
|
145
|
+
starts the server, then
|
146
|
+
```ruby
|
147
|
+
class Server
|
148
|
+
def restart
|
149
|
+
@options = {
|
150
|
+
:user_name=>"yourusername",
|
151
|
+
:group_name=>"yourgroupname"}
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
will change the user name and group name for all new processes when the HUP is received.
|
156
|
+
|
157
|
+
|
158
|
+
FIN
|
data/lib/net/server.crt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICqDCCAZACCQDi3G+g2cQKIjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtl
|
3
|
+
eGFtcGxlLmNvbTAeFw0xNjA5MTIwMzIxMTFaFw00NDAxMjgwMzIxMTFaMBYxFDAS
|
4
|
+
BgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
|
5
|
+
AQEAyjrhith3b09fjtDIFm+PO4MvyjN75Q5FyNJrB7Nwbtrs0U+cM+LqUa7IogV5
|
6
|
+
LNXNOq1NxX3b8AmrZ3PRJ0OU+SWcMQcQLWb027R2agHHH4M7haYtpZh33u7+cLo9
|
7
|
+
sCwm7d2S9ShgFPNVeIMuEjbwXKj1beHTYbljeEFULgxsqrvUJWm1A+WgPFc/9QyX
|
8
|
+
os7gv9VG0//qlCAY91QEx7LGN+XEhzSzfOHnAwYeB8qYKbf7CRK4hua2zoCZQB0d
|
9
|
+
o4IHm+aS6HSSsnTQ/JHWegjy/yyJAenseXRdmz1O4yj+nw5yFocz7TbqgQm5jqgV
|
10
|
+
acEmX2b6tiuOlwc9Hm9x6Z0BLQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCyCw/H
|
11
|
+
XPxV1P6WVZFgQ65/IKZoJY6JSZ88ILw0jzN/B+/ItfBk250n1VoDv9sNXeDg6Sym
|
12
|
+
cNlJlS5nwAzVE+drELu6xMbAOX0+y4KHhhYTrl+8CnXWXVVGsUQhHu8GljfMbM+s
|
13
|
+
VUNrhWO8XRrQ4u4wqnh1kR1pe2xUhuV3dkespWI4LRjNnsEmPw3Rg5BkY8VQRKSR
|
14
|
+
Srif5BlisnwE1WDefaIiE7Ydgs93o1FxZchvBnlHrtxnCG+bXIlO29fRdX8rUpH/
|
15
|
+
zgONQ97oJHZ4N29AEYO8dxnEt8e9eioeQU+xeU8HRw36F+JajiqhCOwgpz7Vv4rn
|
16
|
+
HmueeHzDxJaGD+xv
|
17
|
+
-----END CERTIFICATE-----
|
data/lib/net/server.key
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIIEpAIBAAKCAQEAyjrhith3b09fjtDIFm+PO4MvyjN75Q5FyNJrB7Nwbtrs0U+c
|
3
|
+
M+LqUa7IogV5LNXNOq1NxX3b8AmrZ3PRJ0OU+SWcMQcQLWb027R2agHHH4M7haYt
|
4
|
+
pZh33u7+cLo9sCwm7d2S9ShgFPNVeIMuEjbwXKj1beHTYbljeEFULgxsqrvUJWm1
|
5
|
+
A+WgPFc/9QyXos7gv9VG0//qlCAY91QEx7LGN+XEhzSzfOHnAwYeB8qYKbf7CRK4
|
6
|
+
hua2zoCZQB0do4IHm+aS6HSSsnTQ/JHWegjy/yyJAenseXRdmz1O4yj+nw5yFocz
|
7
|
+
7TbqgQm5jqgVacEmX2b6tiuOlwc9Hm9x6Z0BLQIDAQABAoIBAEfEoao+rBQqnQT3
|
8
|
+
/ypHKRO7uMgPgVaGvClK04UGH06YDxcxI4QecX3Pg5BMVPaeYZkDS+hchCMpq1Sp
|
9
|
+
e35ts36/5DBaC1mxghA+eQ+h1eLPFd6WXPi5pUDOnCmxTpco9B/SVIcAbyjAOoLs
|
10
|
+
Ovtn1+FqbL80N2ok+rIArTkyW5YzNNT84P5nMLGL5PvA40F/dGhkIUutX73F8jvU
|
11
|
+
th3OLyf2MTYKfq7J79z3CGrClZmdcD70h5oKcVH0xFISODqwW9+NUuCaZke0LLPm
|
12
|
+
TLQlFdKxfu5rVoBW6DGsK+7q4LSG41MkXTyTi2yIW70ls++KakJi4QcYetX/XPTa
|
13
|
+
Xd1YkhkCgYEA5Gqj7wLIpvdtyIbM08xF7eUhuXxx42OHAvirFu/l9WipTRBvoLg2
|
14
|
+
CCOWAr/1UyWDeIJAEOjsplKEN0NH1Yx9+jgXbzEU1rVtW8+Oyd64/FJqjICZPatp
|
15
|
+
DBTxyPy1849PcyhlFHMiPiaP8+bcuy8HVeLDd27gujjJDtNYL9mAxBcCgYEA4qay
|
16
|
+
86WqUCZqjOXrEPh4/VWqT0lvvkoBaOx3ScoapXZlKFJAf1lNd7pEvvTZ/6X5XCbr
|
17
|
+
8VA+wUB1kQAKjVBPxideIyUOKboJJfgJDWqTaiPtOdCbO9+pcEsu/1Ep+64prU1e
|
18
|
+
dX85McHKGRJZEPQOqIXT+M+m6iJ5Dr63KMtKO1sCgYEA5EJ9WFhb1B7nIsEy52T3
|
19
|
+
bOjRbt8hoK7ROPLvZpiOIVRZ+501MFNmC3QkcNMLge+3FjJze3KJKxzC68bcfldL
|
20
|
+
fUWYhZFy1a2wf1NHygw8qEpkF8xbVvl4nI5BoSyJV8AbEWWIvYrg6WL38DEA3D3/
|
21
|
+
AqD93Nh80xv0MFCbjBW7TDkCgYA5MOks89ui996hCQ8krB4Thr8/3H723EO4zxpW
|
22
|
+
o0nQAK0L3J1rxQP4NydLrAsqKB5g821L6fy71OEVopYHDIHktWBaq+oD+259hzX5
|
23
|
+
ja/+82vTz+Cr3gcqT75fLILUgCECGui/60dqV8UASJHy5jKgsYxIV1V99Q7o+pTL
|
24
|
+
FWj4ywKBgQCtJ/xOSowLSdEYGRq//iNKaBSAutaV26J5WVxxev3wUehZpvRGtqI5
|
25
|
+
CiddA8vlR0EiA/SINQ0MZtk9kwvWYVq/jkWGttK6gwU26ZDUR7xUOMs17m3ttUn7
|
26
|
+
mjkuyOITFCRxf0x0LcoFQWHlc5lC6Y/ZKF3dK6jPD74B5/UUeQ8sCA==
|
27
|
+
-----END RSA PRIVATE KEY-----
|
data/lib/net/server.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
# = net/server.rb
|
2
|
+
#
|
3
|
+
# Copyright (c) 2016 Michael J. Welch, Ph.D.
|
4
|
+
#
|
5
|
+
# Written and maintained by Michael J. Welch, Ph.D. <mjwelchphd@gmail.com>
|
6
|
+
#
|
7
|
+
# This work is not derived from any other work. It is original software.
|
8
|
+
#
|
9
|
+
# Documented by Michael J. Welch, Ph.D. <mjwelchphd@gmail.com>
|
10
|
+
#
|
11
|
+
# This program is free software. You can re-distribute and/or
|
12
|
+
# modify this program under the same terms as Ruby itself.
|
13
|
+
#
|
14
|
+
# See the README.md for documentation.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'openssl'
|
18
|
+
require 'socket'
|
19
|
+
|
20
|
+
module Net
|
21
|
+
|
22
|
+
# :stopdoc:
|
23
|
+
class ServerTerminate < Exception; end
|
24
|
+
class ServerQuit < Exception; end
|
25
|
+
# :startdoc:
|
26
|
+
|
27
|
+
# == An Internet server
|
28
|
+
class Server
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def initialize(options={})
|
33
|
+
path = __FILE__.chomp('server.rb')
|
34
|
+
@option_list = [[:server_name, "example.com"], [:listening_ports, ["25","486","587"]], \
|
35
|
+
[:private_key, "#{path}server.key"], [:certificate, "#{path}server.crt"], \
|
36
|
+
[:user_name, nil], [:group_name, nil], [:working_directory, File::realpath('.')], \
|
37
|
+
[:pid_file, "pid"], [:daemon, false]]
|
38
|
+
@options = {}
|
39
|
+
@option_list.each do |key,value|
|
40
|
+
@options[key] = if options.has_key?(key) then options[key] else value end
|
41
|
+
end
|
42
|
+
end # initialize
|
43
|
+
|
44
|
+
include Socket::Constants
|
45
|
+
|
46
|
+
#
|
47
|
+
# This is the code executed after the process has been
|
48
|
+
# forked and root privileges have been dropped.
|
49
|
+
#
|
50
|
+
def process_call(connection, local_port, remote_port, remote_ip, remote_hostname, remote_service)
|
51
|
+
begin
|
52
|
+
Signal.trap("INT") { } # ignore ^C in the child process
|
53
|
+
LOG.info("%06d"%Process::pid) {"Connection accepted on port #{local_port} from port #{remote_port} at #{remote_ip} (#{remote_hostname})"} if LOG
|
54
|
+
|
55
|
+
# a new object is created here to provide separation between server and receiver
|
56
|
+
# this call receives the email and does basic validation
|
57
|
+
Receiver::new(connection).receive(local_port, Socket::gethostname, remote_port, remote_hostname, remote_ip)
|
58
|
+
rescue ServerQuit
|
59
|
+
# nothing to do here
|
60
|
+
end
|
61
|
+
end # process_call
|
62
|
+
|
63
|
+
#
|
64
|
+
# This method drops the process's root privileges for security reasons.
|
65
|
+
#
|
66
|
+
def drop_root_privileges(user_name, group_name, working_directory)
|
67
|
+
# drop root privileges
|
68
|
+
if Process::Sys.getuid==0
|
69
|
+
user = Etc::getpwnam(user_name)
|
70
|
+
group = Etc::getgrnam(group_name)
|
71
|
+
Dir.chdir(user.dir)
|
72
|
+
Dir.chdir(working_directory) if not working_directory.nil?
|
73
|
+
Process::GID.change_privilege(group.gid)
|
74
|
+
Process::UID.change_privilege(user.uid)
|
75
|
+
end
|
76
|
+
end # drop_root_privileges
|
77
|
+
|
78
|
+
#
|
79
|
+
# both the AF_INET and AF_INET6 families use this DRY method
|
80
|
+
# to bind to the socket.
|
81
|
+
#
|
82
|
+
def bind_socket(family,port,ip)
|
83
|
+
socket = Socket.new(family, SOCK_STREAM, 0)
|
84
|
+
sockaddr = Socket.sockaddr_in(port.to_i,ip)
|
85
|
+
socket.setsockopt(:SOCKET, :REUSEADDR, true)
|
86
|
+
socket.bind(sockaddr)
|
87
|
+
socket.listen(0)
|
88
|
+
return socket
|
89
|
+
end # bind_socket
|
90
|
+
|
91
|
+
#
|
92
|
+
# The listening thread is established in this method depending on the ListenPort
|
93
|
+
# argument passed to it -- it can be '<ipv6>/<port>', '<ipv4>:<port>', or just '<port>'.
|
94
|
+
#
|
95
|
+
def listening_thread(local_port)
|
96
|
+
LOG.info("%06d"%Process::pid) {"listening on port #{local_port}..."} if LOG
|
97
|
+
|
98
|
+
# establish an SSL context
|
99
|
+
$ctx = OpenSSL::SSL::SSLContext.new
|
100
|
+
$ctx.key = $prv
|
101
|
+
$ctx.cert = $crt
|
102
|
+
|
103
|
+
# check the parameter to see if it's valid
|
104
|
+
m = /^(([0-9a-fA-F]{0,4}:{0,1}){1,8})\/([0-9]{1,5})|(([0-9]{1,3}\.{0,1}){4}):([0-9]{1,5})|([0-9]{1,5})$/.match(local_port)
|
105
|
+
#<MatchData "2001:4800:7817:104:be76:4eff:fe05:3b18/2000" 1:"2001:4800:7817:104:be76:4eff:fe05:3b18" 2:"3b18" 3:"2000" 4:nil 5:nil 6:nil 7:nil>
|
106
|
+
#<MatchData "23.253.107.107:2000" 1:nil 2:nil 3:nil 4:"23.253.107.107" 5:"107" 6:"2000" 7:nil>
|
107
|
+
#<MatchData "2000" 1:nil 2:nil 3:nil 4:nil 5:nil 6:nil 7:"2000">
|
108
|
+
case
|
109
|
+
when !m[1].nil? # it's AF_INET6
|
110
|
+
socket = bind_socket(AF_INET6,m[3],m[1])
|
111
|
+
when !m[4].nil? # it's AF_INET
|
112
|
+
socket = bind_socket(AF_INET,m[6],m[4])
|
113
|
+
when !m[7].nil?
|
114
|
+
socket = bind_socket(AF_INET6,m[7],"0:0:0:0:0:0:0:0")
|
115
|
+
else
|
116
|
+
raise ArgumentError.new(local_port)
|
117
|
+
end # case
|
118
|
+
ssl_server = OpenSSL::SSL::SSLServer.new(socket, $ctx);
|
119
|
+
|
120
|
+
# main listening loop starts in non-encrypted mode
|
121
|
+
ssl_server.start_immediately = false
|
122
|
+
loop do
|
123
|
+
# we can't use threads because if we drop root privileges on any thread,
|
124
|
+
# they will be dropped for all threads in the process--so we have to fork
|
125
|
+
# a process here in order that the reception be able to drop root privileges
|
126
|
+
# and run at a user level--this is a security precaution--the other reason
|
127
|
+
# to use processes is that they can be run on multiple processors
|
128
|
+
connection = ssl_server.accept
|
129
|
+
Process::fork do
|
130
|
+
# now we're in the child process
|
131
|
+
begin
|
132
|
+
drop_root_privileges(@options[:user_name],@options[:group_name],@options[:working_directory]) if !@options[:user_name].nil?
|
133
|
+
remote_hostname, remote_service = connection.io.remote_address.getnameinfo
|
134
|
+
remote_ip, remote_port = connection.io.remote_address.ip_unpack
|
135
|
+
process_call(connection, local_port, remote_port.to_s, remote_ip, remote_hostname, remote_service)
|
136
|
+
ensure
|
137
|
+
# here we close the child's copy of the connection --
|
138
|
+
# since the parent already closed it's copy, this
|
139
|
+
# one will send a FIN to the client, so the client
|
140
|
+
# can terminate gracefully
|
141
|
+
connection.close
|
142
|
+
LOG.info("%06d"%Process::pid) {"Connection closed on port #{local_port} by #{@options[:server_name]}"} if LOG
|
143
|
+
# and finally, close the child's link to the log
|
144
|
+
LOG.close if LOG
|
145
|
+
end
|
146
|
+
# the child process ends here
|
147
|
+
end # fork
|
148
|
+
# now we're in the parent process
|
149
|
+
# here we close the parent's copy of the connection --
|
150
|
+
# the child (created by the Process::fork above) has another copy --
|
151
|
+
# if this one is not closed, when the child closes it's copy,
|
152
|
+
# the child's copy won't send a FIN to the client -- the FIN
|
153
|
+
# is only sent when the last process holding a copy to the
|
154
|
+
# socket closes it's copy
|
155
|
+
connection.close
|
156
|
+
end # loop
|
157
|
+
end # listening_thread
|
158
|
+
|
159
|
+
public
|
160
|
+
|
161
|
+
#
|
162
|
+
# This is the main setup and loop.
|
163
|
+
#
|
164
|
+
def start
|
165
|
+
# generate the first log messages
|
166
|
+
LOG.info("%06d"%Process::pid) {"Starting RubyMTA at #{Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")}, pid=#{Process::pid}"} if LOG
|
167
|
+
LOG.info("%06d"%Process::pid) {"Options specified: #{ARGV.join(", ")}"} if LOG && ARGV.size>0
|
168
|
+
|
169
|
+
# get the certificates, if any; they're needed for STARTTLS
|
170
|
+
# we do this before daemonizing because the working folder might change
|
171
|
+
$prv = if @options[:private_key] then OpenSSL::PKey::RSA.new File.read(@options[:private_key]) else nil end
|
172
|
+
$crt = if @options[:certificate] then OpenSSL::X509::Certificate.new File.read(@options[:certificate]) else nil end
|
173
|
+
|
174
|
+
# daemonize it if the option was set--it doesn't have to be root to daemonize it
|
175
|
+
Process::daemon if @options[:daemon]
|
176
|
+
|
177
|
+
# get the process ID and the user id AFTER demonizing, if that was requested
|
178
|
+
pid = Process::pid
|
179
|
+
uid = Process::Sys.getuid
|
180
|
+
gid = Process::Sys.getgid
|
181
|
+
|
182
|
+
LOG.info("%06d"%Process::pid) {"Daemonized at #{Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")}, pid=#{pid}, uid=#{uid}, gid=#{gid}"} if LOG && @options[:daemon]
|
183
|
+
|
184
|
+
# store the pid of the server session
|
185
|
+
begin
|
186
|
+
puts "RubyMTA running as PID=>#{pid}, UID=>#{uid}, GID=>#{gid}"
|
187
|
+
File.open(@options[:pid_file],"w") { |f| f.write(pid.to_s) }
|
188
|
+
rescue Errno::EACCES => e
|
189
|
+
LOG.warn("%06d"%Process::pid) {"The pid couldn't be written. To save the pid, create a directory for '#{@options[:pid_file]}' with r/w permissions for this user."} if LOG
|
190
|
+
LOG.warn("%06d"%Process::pid) {"Proceeding without writing the pid."} if LOG
|
191
|
+
end
|
192
|
+
|
193
|
+
# if ssltransportagent was started as root, make sure UserName and
|
194
|
+
# GroupName have values because we have to drop root privileges
|
195
|
+
# after we fork a process for the receiver
|
196
|
+
if uid==0 # it's root
|
197
|
+
if @options[:user_name].nil? || @options[:group_name].nil?
|
198
|
+
LOG.error("%06d"%Process::pid) {"ssltransportagent can't be started as root unless UserName and GroupName are set."} if LOG
|
199
|
+
exit(1)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# this is the main loop which runs until admin enters ^C
|
204
|
+
Signal.trap("INT") { raise ServerTerminate.new }
|
205
|
+
Signal.trap("HUP") { restart if defined?(restart) }
|
206
|
+
Signal.trap("CHLD") do
|
207
|
+
begin
|
208
|
+
Process.wait(-1, Process::WNOHANG)
|
209
|
+
rescue Errno::ECHILD => e
|
210
|
+
# ignore the error
|
211
|
+
end
|
212
|
+
end # trap-chld
|
213
|
+
threads = []
|
214
|
+
# start the server on multiple ports (the usual case)
|
215
|
+
begin
|
216
|
+
@options[:listening_ports].each do |port|
|
217
|
+
threads << Thread.start(port) do |port|
|
218
|
+
listening_thread(port)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
# the joins are done ONLY after all threads are started
|
222
|
+
threads.each { |thread| thread.join }
|
223
|
+
rescue ServerTerminate
|
224
|
+
LOG.info("%06d"%Process::pid) {"#{@options[:server_name]} terminated by admin ^C"} if LOG
|
225
|
+
end
|
226
|
+
|
227
|
+
# attempt to remove the pid file
|
228
|
+
begin
|
229
|
+
File.delete(@options[:pid_file])
|
230
|
+
rescue Errno::ENOENT => e
|
231
|
+
LOG.warn("%06d"%Process::pid) {"No such file: #{e.inspect}"} if LOG
|
232
|
+
rescue Errno::EACCES, Errno::EPERM
|
233
|
+
LOG.warn("%06d"%Process::pid) {"Permission denied: #{e.inspect}"} if LOG
|
234
|
+
end
|
235
|
+
end # start
|
236
|
+
end
|
237
|
+
end
|
data/lib/net/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: net-server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael J. Welch, Ph.D.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Ruby Net server.
|
14
|
+
email:
|
15
|
+
- mjwelchphd@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- CHANGELOG.md
|
21
|
+
- README.md
|
22
|
+
- lib/net/server.crt
|
23
|
+
- lib/net/server.key
|
24
|
+
- lib/net/server.rb
|
25
|
+
- lib/net/version.rb
|
26
|
+
homepage: https://github.com/mjwelchphd/net-server
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 2.4.6
|
47
|
+
signing_key:
|
48
|
+
specification_version: 4
|
49
|
+
summary: Ruby Net server.
|
50
|
+
test_files: []
|