1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
|
#!/usr/bin/ruby
#
# Copyright (c) 2004,2013 Peter Palfrader <peter@palfrader.org>
#
# All rights reserved.
#
require "ldap"
require "getoptlong"
require "myldap"
require "yaml"
require "etc"
require 'ipaddr'
@config = YAML::load( File.open( '/etc/noreply/config' ) )
ldap = MyLDAP.new(@config, "ldap2apache")
clients = ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, 'objectclass=tnClient')
# webstat
@rmagicdir = @config['module']['analog']['rmagic']
@webstat_location = @config['module']['analog']['webstat']
@defaultbind = @config['module']['apache']['defaultbind']
@defaultbindhttpport = @config['module']['apache']['defaultbindhttpport']
@defaultbindhttpsport = @config['module']['apache']['defaultbindhttpsport']
@configdir = @config['module']['apache']['configdir']
@phpinipreamble = @config['module']['apache']['phpinipreamble']
@phpwrapperfilesdir = @config['module']['apache']['phpwrapperfilesdir']
@phpinifilesdir = @config['module']['apache']['phpinifilesdir']
@postgres_uid = @config['module']['apache']['postgres_uid']
@www_gid = @config['module']['apache']['www_gid']
@configtest = @config['module']['apache']['configtest']
@reload = @config['module']['apache']['reload']
@restart = @config['module']['apache']['restart']
@statsbind = @config['module']['apache']['statsbind']
@statsvhostaddto = @config['module']['apache']['statsvhostaddto']
files = {}
phpinifiles = {}
phpwrapperfiles = {}
def mkdir(dir, mode, uid, gid)
Dir.mkdir(dir, mode) unless File.exists?(dir)
# unless file is a symlink
unless FileTest.symlink?(dir)
File.chown(uid, gid, dir) == 1 or throw "chown #{dir} failed"
chmodded = File.chmod(mode, dir)
throw "Could not chmod #{dir} to #{mode}" unless chmodded == 1
end
end
def make_vhostline(addresses, port)
line = []
line << "<VirtualHost"
addresses.each do |addr|
ipaddr = IPAddr.new addr
if ipaddr.ipv4?
line << "#{ipaddr}:#{port}"
elsif ipaddr.ipv6?
line << "[#{ipaddr}]:#{port}"
else
throw "Unknown ip version/class"
end
end
line << ">"
return line.join(' ')
end
clients.each{ |c|
c['vhosts'] = ldap.conn.search2(c['dn'][0], LDAP::LDAP_SCOPE_SUBTREE, '(&(objectclass=tnWebVHost)(tnHost='+@config['thishost']+'))')
client_home = c['homeDirectory'][0] or throw "No home for #{d['dn'][0]}"
uid = c['uidNumber'][0].to_i
gid = c['gidNumber'][0].to_i
mkdir(client_home , 02755, 0, 0)
#mkdir(client_home+"/logs" , 02755, 0, gid)
#mkdir(client_home+"/logs-archive" , 02755, 0, gid)
#mkdir(client_home+"/logs-for-stat", 02750, 0, gid)
mkdir(client_home+"/pg" , 02750, @postgres_uid, gid)
c['vhosts'].each{ |vhost|
bind = vhost['tnWebVHostBind'] ? vhost['tnWebVHostBind'] : @defaultbind
bind = [ bind ] unless bind.kind_of?(Array)
bindhttpport = vhost['tnWebVHostBindHTTPPort'] ? vhost['tnWebVHostBindHTTPPort'][0] : @defaultbindhttpport
bindhttpsport = vhost['tnWebVHostBindHTTPSPort'] ? vhost['tnWebVHostBindHTTPSPort'][0] : @defaultbindhttpsport
server_name = vhost['tnWebVHostServerName'][0]
server_admin = vhost['tnWebVHostWebmaster'][0]
server_aliases = (vhost['tnWebVHostServerAlias'] or []).join(" ")
home = client_home +"/"+ vhost['tnWebVHostHomeDirectory'][0]
property = { # list multi-value keys here
'phpiniaddon' => []
}
if vhost['tnWebVHostProperties']
vhost['tnWebVHostProperties'].each{ |prop|
(key, val) = prop.split('=', 2)
if property.has_key? key
if property[key].kind_of?(Array)
property[key] << val
else
throw "Found tnWebVHostProperties entry '#{prop}' but #{key} is already #{property[key]}"
end
else
property[key] = val
end
}
end
property['php'] = "no" unless %w(yes no cgi5).include?property['php']
dirindex = vhost['tnWebVHostDirectoryIndex'][0] if vhost['tnWebVHostDirectoryIndex']
docdiroptions = (vhost['tnWebVHostDocDirOptions'] or []).join(" ")
cgidiroptions = (vhost['tnWebVHostCgiDirOptions'] or []).join(" ")
docdiroptions += " Includes" if property['ssi'] == "yes"
addto = vhost['tnWebVHostAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostAddto']
docdiraddto = vhost['tnWebVHostDocDirAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostDocDirAddto']
cgidiraddto = vhost['tnWebVHostCgiDirAddto'][0].gsub("\n", "\n\t") if vhost['tnWebVHostCgiDirAddto']
if property['https'] == "only"
ssl = true
http_ssl_upgrade = true unless property['http_ssl_upgrade'] == "no"
end
vhostuid = uid
vhostgid = gid
if property['vhostUser']
u = Etc.getpwnam(property['vhostUser'])
vhostuid = u['uid']
vhostgid = u['gid']
end
if property['vhostGroup']
g = Etc.getgrnam(property['vhostGroup'])
vhostgid = g['gid']
end
vhostuidname = Etc.getpwuid(vhostuid)['name']
vhostgidname = Etc.getgrgid(vhostgid)['name']
umask=File.umask
File.umask(0000)
if property['permissionstyle'] == "new"
mkdir(home, 00750, vhostuid, @www_gid)
else
mkdir(home, 02755, vhostuid, vhostgid)
end
if vhostgid != gid
mkdir(home+"/htdocs", 02775, vhostuid, vhostgid)
mkdir(home+"/bin", 02775, 0, vhostgid)
else
mkdir(home+"/htdocs", 02755, vhostuid, vhostgid)
mkdir(home+"/bin", 02755, 0, vhostgid)
end
mkdir(home+"/cgi-bin", 02755, vhostuid, vhostgid) if property['cgi-bin'] == "yes"
if property['php'] == "cgi5"
mkdir(home+"/tmp", 00770, vhostuid, vhostgid)
elsif property['php']!="no"
mkdir(home+"/tmp", 03775, vhostuid, @www_gid)
else
Dir.rmdir( home+"/tmp" ) if File.exists?(home+"/tmp")
end
File.umask(umask)
config = []
basedir = nil
unless property['php'] == "cgi5"
basedir = "#{home}"
if property['open_basedir']
if property['open_basedir'] != "none"
basedir += ":" + property['open_basedir']
else
basedir = nil
end
end
if property['safemode_include']
safemode_include = property['safemode_include']
end
end
phpincludepath = [
'.',
"#{home}/include",
"/usr/share/php",
"/usr/share/pear",
"/usr/share/php/smarty/libs" ].map { |d| File.exists?(d) ? d : nil }.compact.join ':'
if ssl
crtfiles = [
"/etc/ssl/certs/apache-#{server_name}.pem",
"/etc/ssl/certs/apache-#{server_name}.crt",
"/etc/ssl/certs/#{server_name}.pem",
"/etc/ssl/certs/#{server_name}.crt",
].select{ |i| FileTest.exists?(i) }
chainfiles = [
"/etc/ssl/certs/apache-#{server_name}-chain.pem",
"/etc/ssl/certs/apache-#{server_name}-chain.crt",
"/etc/ssl/certs/#{server_name}-chain.pem",
"/etc/ssl/certs/#{server_name}-chain.crt",
"/etc/ssl/certs/#{server_name}.chain",
].select{ |i| FileTest.exists?(i) }
keyfiles = [
"/etc/ssl/private/apache-#{server_name}.key",
"/etc/ssl/private/#{server_name}.key",
].select{ |i| FileTest.exists?(i) }
STDERR.puts "Warning: No crtfiles exist" unless crtfiles.length() > 0
STDERR.puts "Warning: No keyfiles exist" unless keyfiles.length() > 0
config << make_vhostline(bind, bindhttpsport)
config << " SSLEngine on"
config << " SSLCertificateFile #{crtfiles.first}" if crtfiles.length() > 0
config << " SSLCertificateKeyFile #{keyfiles.first}" if keyfiles.length() > 0
config << " SSLCertificateChainFile #{chainfiles.first}" if chainfiles.length() > 0
config << ' <Files ~ "\.php$">'
config << ' SSLOptions +StdEnvVars'
config << ' </Files>'
config << ' SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown'
config << ''
else
config << make_vhostline(bind, bindhttpport)
end
config << " ServerName #{server_name}"
config << " ServerAlias #{server_aliases}" if server_aliases != ""
config << " ServerAdmin #{server_admin}"
config << ""
if property['canonical'] == "yes"
config << " RewriteEngine on"
config << " RewriteCond %{HTTP_HOST} !^#{server_name}$"
config << " RewriteRule ^/(.*) %s://#{server_name}/$1 [R]"%[ ( ssl ? 'https' : 'http' ) ]
end
config << ""
unless property['userdir'] == "yes"
config << " <IfModule mod_userdir.c>"
config << " UserDir disabled"
config << " </IfModule>"
end
config << " SuexecUserGroup #{vhostuidname} #{vhostgidname}" unless property['setusergroup'] == "no"
config << " LogLevel warn"
config << " ErrorLog /var/log/apache2/#{server_name}-error.log"
config << " CustomLog /var/log/apache2/#{server_name}-access.log privacy"
config << " ServerSignature On"
config << ""
config << ""
if property['php'] == "yes"
config << " <IfModule mod_php4.c>"
config << " php_admin_value engine 1"
unless property['safe_mode'] == "no"
config << " php_admin_value safe_mode 1"
config << " php_admin_value safe_mode_gid 1"
config << " php_admin_value safe_mode_exec_dir \"#{home}/bin\""
end
config << " php_admin_value upload_tmp_dir \"#{home}/tmp\""
config << " php_admin_value sendmail_path \"/usr/sbin/sendmail -t -i -f #{server_admin}\""
config << " php_admin_value allow_url_fopen 0"
config << " </IfModule>"
elsif property['php'] == "no"
config << " <IfModule mod_php4.c>"
config << " php_admin_value engine off"
config << " </IfModule>"
config << " AddType text/plain .php3"
config << " AddType text/plain .php3s"
config << " AddType text/plain .php"
config << " AddType text/plain .php4"
config << " AddType text/plain .phps"
end
unless property['document_root'] == "manual"
config << " DocumentRoot #{home}/htdocs"
end
config << " <Location />"
config << " #AllowOverride FileInfo AuthConfig Limit Indexes Options"
config << " IndexOptions FancyIndexing NameWidth=*"
if property['php'] == "yes"
config << " <IfModule mod_php4.c>"
config << " php_value magic_quotes_gpc 0"
config << " php_admin_value open_basedir \"#{basedir}\"" if basedir
config << " php_admin_value safe_mode_include_dir \"#{safemode_include}\"" if safemode_include
config << " php_value include_path \"#{phpincludepath}\""
config << " </IfModule>"
end
config << " Options #{docdiroptions}" if docdiroptions != ""
config << " #{docdiraddto}" if docdiraddto
config << " DirectoryIndex #{dirindex}" if dirindex
config << " AddHandler server-parsed .html" if property['ssi'] == 'yes'
config << " AddType text/html .shtml" if property['ssi'] == 'yes'
config << " AddHandler server-parsed .shtml" if property['ssi'] == 'yes'
config << ""
config << " Order allow,deny"
if property['allowfrom']
property['allowfrom'].split(/ */).each do |l|
config << " Allow from #{l}"
end
else
config << " Allow from all"
end
config << " </Location>"
if property['php'] == "cgi5"
throw "@config['module']['apache']['phpwrapperfilesdir'] is not defined, yet we use php-cgi" unless @phpwrapperfilesdir
if property['open_basedir'] or property['safemode_include']
STDERR.puts "Warning: open_basedir and safemode_include not supported with php-cgi"
end
phpini = []
phpini.concat IO.readlines(@phpinipreamble).collect{|a| a.chop} if @phpinipreamble and FileTest.exists?(@phpinipreamble)
phpini << "[PHP]"
phpini << "upload_tmp_dir = \"#{home}/tmp\""
phpini << "sendmail_path = \"/usr/sbin/sendmail -t -i -f #{server_admin}\""
phpini << "allow_url_fopen = 0"
phpini << "magic_quotes_gpc = 0"
phpini << "include_path = \"#{phpincludepath}\""
phpini << property['phpiniaddon'] if property['phpiniaddon']
inilp = c['o'][0] +"-"+ server_name
throw "Clash on #{server_name} of client#{c['o'][0]} for phpinifiles" if phpinifiles[ inilp ]
phpinifiles[ inilp ] = phpini
wrap = []
wrap << "#!/bin/bash"
wrap << ""
wrap << "exec /usr/bin/php5-cgi --php-ini #{@phpinifilesdir}/#{inilp} \"$@\""
wraplp1 = "#{vhostuid}=#{vhostgid}"
wraplp2 = "php-#{server_name}"
phpwrapperfiles[wraplp1] = {} unless phpwrapperfiles[wraplp1]
throw "Clash on #{server_name} of client#{c['o'][0]} for phpwrapperfiles" if phpwrapperfiles[wraplp1][wraplp2]
phpwrapperfiles[wraplp1][wraplp2] = wrap
phppath = "#{@phpwrapperfilesdir}/#{wraplp1}/#{wraplp2}"
config << " <Directory #{home}/htdocs>"
#config << " AddHandler fcgid-script .php"
#config << " FCGIWrapper #{phppath} .php"
config << " <Files *.php>"
#config << " Options ExecCGI"
config << " SetHandler fcgid-script"
config << " FCGIWrapper #{phppath}"
config << " </Files>"
config << " </Directory>"
end
if property['cgi-bin'] == "yes"
cgihome = home.gsub(/^\/srv\/www\/vhosts/, '/var/www/vhosts')
config << " ScriptAlias /cgi-bin #{cgihome}/cgi-bin"
config << " <Directory #{cgihome}/cgi-bin>"
config << " AllowOverride FileInfo AuthConfig Limit Indexes"
config << " Options ExecCGI #{cgidiroptions}"
config << " #{cgidiraddto}" if cgidiraddto
config << " </Directory>"
end
config << " #{addto}" if addto
#
# webstats, part I - proxy to our second webserver
if property['stats'] == "yes"
config << ' Alias /awstats-classes/ "/usr/share/awstats/classes/"'
config << ' Alias /awstats-css/ "/usr/share/awstats/css/"'
config << ' Alias /awstats-icon/ "/usr/share/awstats/icon/"'
config << " <IfModule mod_proxy.c>"
config << " ProxyPass /webstats/ http://#{@statsbind}/"
config << " ProxyPassReverse /webstats/ http://#{@statsbind}/"
config << " ProxyPreserveHost on"
config << " </IfModule>"
end
config << "</VirtualHost>"
config << '# vim:ft=apache:'
throw "Clash on #{server_name} of client#{c['o'][0]}" if files[ c['o'][0] +"-"+ server_name ]
files[ c['o'][0] +"-"+ server_name ] = config
if ssl and http_ssl_upgrade
config = ['']
config << make_vhostline(bind, bindhttpport)
config << " ServerName #{server_name}"
config << " ServerAlias #{server_aliases}" if server_aliases != ""
config << " ServerAdmin #{server_admin}"
config << " <IfModule mod_userdir.c>"
config << " UserDir disabled"
config << " </IfModule>"
config << " LogLevel warn"
config << " ErrorLog /var/log/apache2/#{server_name}-error.log"
config << " CustomLog /var/log/apache2/#{server_name}-access.log privacy"
config << " ServerSignature On"
config << " RewriteEngine on"
config << " RewriteRule ^/(.*)$ https://#{server_name}"+(bindhttpsport == 443 ? '' : ":#{bindhttpsport}")+"/$1 [L,R]"
config << "</VirtualHost>"
config << '# vim:ft=apache:'
files[ c['o'][0] +"-"+ server_name ] += config
end
#
# webstats, part II - we need our own vhost since we can't have suexec enabled
if property['stats'] == "yes"
config = ['']
config << "<VirtualHost #{@statsbind}>"
config << " ServerName #{server_name}"
config << " ServerAdmin #{server_admin}"
config << " <IfModule mod_userdir.c>"
config << " UserDir disabled"
config << " </IfModule>"
config << "# ErrorLog #{client_home}/logs/stats.#{server_name}-error.log"
config << "# LogLevel warn"
config << "# CustomLog #{client_home}/logs/stats.#{server_name}-access.log combined"
config << " ServerSignature On"
config << ""
config << ' Alias /awstats-classes/ "/usr/share/awstats/classes/"'
config << ' Alias /awstats-css/ "/usr/share/awstats/css/"'
config << ' Alias /awstats-icon/ "/usr/share/awstats/icon/"'
config << " ScriptAlias /awstats.pl /usr/lib/cgi-bin/awstats.pl"
config << " ScriptAlias / /usr/lib/cgi-bin/awstats.pl"
config << ""
config << " #{@statsvhostaddto}" if @statsvhostaddto
config << "</VirtualHost>"
config << '# vim:ft=apache:'
files[ c['o'][0] +"-"+ server_name ] += config
end
}
}
def write_files(basedir, f, mode=0644, uid=nil, gid=nil)
need_reload = false
Dir.entries( basedir ).each{ |e|
next if ((e =~ /^\./) != nil)
next if f.has_key?( e )
File.unlink( basedir + '/' + e )
need_reload = true
}
f.each_pair{ |name, config|
filename = basedir + '/' + name
conf = config.join("\n") + "\n"
on_disk = FileTest.exists?(filename) ? File.read(filename) : nil
next if on_disk == conf
f = File.new( filename, "w" )
f.chmod(mode)
f.write(conf)
f.close
File.chown(uid, gid, filename) if uid or gid
need_reload = true
}
return need_reload
end
if phpinifiles.size > 0
throw "@config['module']['apache']['phpinifilesdir'] is not defined, yet we use php-cgi" unless @phpinifilesdir
throw "@config['module']['apache']['restart'] is not defined, yet we use php-cgi" unless @restart
end
need_reload = write_files(@configdir, files)
need_restart = write_files(@phpinifilesdir, phpinifiles) if @phpinifilesdir
phpwrapperfilesdirectories = {}
phpwrapperfiles.each_pair do |dirname, wrappers|
m = /^(.*)=(.*)$/.match dirname
throw "Could not parse phpwrapperfilesdir dirname #{dirname}" unless m
dir = @phpwrapperfilesdir + '/' + dirname
mkdir(dir, 0755, m[1].to_i, m[2].to_i) unless File.exists?(dir)
write_files(dir, wrappers, 0755, m[1].to_i, m[2].to_i)
phpwrapperfilesdirectories[dirname] = true
end
if @phpwrapperfilesdir
Dir.entries( @phpwrapperfilesdir ).each do |e|
next if ((e =~ /^\./) != nil)
next if phpwrapperfilesdirectories.has_key?( e )
Dir.entries( @phpwrapperfilesdir+'/'+e ).each do |e2|
next if ((e2 =~ /^\./) != nil) and File.directory?(@phpwrapperfilesdir+'/'+e+'/'+e2)
File.unlink( @phpwrapperfilesdir+'/'+e+'/'+e2 )
end
Dir.rmdir( @phpwrapperfilesdir+'/'+e )
need_reload = true
end
end
if need_restart
system(@configtest) or throw "Configtest returned errors."
system(@restart) or throw "Restart returned errors."
elsif need_reload
system(@configtest) or throw "Configtest returned errors."
system(@reload) or throw "Reload returned errors."
end
|