ViperServ / TC2 / Server.tcl

  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
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
# ===============================================
# =========        ====     ======   ============
# ============  ======  ===  ===   =   ==========
# ============  =====  ========   ===   =========
# ============  =====  =============   ==========
# ============  =====  ============   ===========
# == DcK =====  =====  ===========   ============
# ============  =====  ==========   =============
# ============  ======  ===  ===   ==============
# ============  =======     ===        ==========
# ===============================================
# ===============================================
# == Tau Ceti Central == Server administration ==
# ==  This is a very dangerous product to use  ==
# ==   Don't deploy it in stable environment   ==
# ==    Or say goodbye to the serv security    ==
# ==     This warning will not be repeated     ==
# ==      All your base are belong to us!      ==
# ===============================================
# ===============================================
#
#     (c) 2011 Sébastien Santoro aka Dereckson.
#     Released under BSD license.

bind bot  - tc2 bot:tc2

#
# Eggdrop events
#

#Handles tc2 requests from linked bots
proc bot:tc2 {sourcebot command text} {
	#Sourcebot: Nasqueron
	#Command:   tc2
	#Text:      requester Dereckson command phpfpm arg status
	set requester	[dict get $text requester]
	set cmd		[dict get $text command]
	set arg		[dict get $text arg]
	set bind	[dict get $text bind]
	set who		[dict get $text who]

	#Logs entry
	log tc2 "$requester@$sourcebot" "$cmd $arg"

	#Executes command
	if [proc_exists tc2:command:$cmd] {
		putcmdlog "(tc2) <$requester@$sourcebot> $cmd $arg"
		set reply [tc2:command:$cmd $requester $arg]
	} {
		set reply "0 {Unknown command: $cmd}"
	}

	#Reports result
	putbot $sourcebot "tc2 [dict create success [lindex $reply 0] reply [lindex $reply 1] bind $bind who $who]"
	return 1
}

#
# Helper procs
#

#Checks if $username begins by a letter and contains only letters, digits, -, _ or .
proc tc2:username_isvalid {username} {
	regexp {^[A-Za-z][A-Za-z0-9_\-\.]*$} $username
}

#Determines if $username exists on the system
#SECURITY: to avoid shell injection, call first tc2:username_isvalid $username
proc tc2:username_exists {username} {
    #TODO: Windows and other OSes (this line has been tested under FreeBSD)
    if {[exec -- logins -oxl $username] == ""} {
        return 0
    } {
        return 1
    }
}

#Gets server hostname
proc tc2:hostname {} {
	exec hostname -s
}

#Determines if $username is root
proc tc2:isroot {username} {
	#Validates input data
	set username [string tolower $username]
	if ![tc2:username_isvalid $username] {
		return 0
	}

	#Check 1 - User has local accreditation
	if ![sql "SELECT count(*) FROM tc2_roots WHERE account_username = '$username' AND server_name = '[sqlescape [tc2:hostname]]'"] {
		return 0
	}

	#Check 2 - User is in the group wheel on the server
	if {[lsearch [exec -- id -Gn $username] wheel] == "-1"} {
		return 0
	} {
		return 1
	}
}

#Determines if $requester is *EXPLICITELY* allowed to allowed to manage the account $user
#When you invoke this proc, you should also check if the user is root.
# e.g. if {[tc2:isroot $requester] || [tc2:userallow $requester $user]} { ... }
proc tc2:userallow {requester user} {
	set sql "SELECT count(*) FROM tc2_users_permissions WHERE server_name = '[sqlescape [tc2:hostname]]' AND account_username = '[sqlescape $user]' AND user_id = [getuserid $user]"
	putdebug $sql
	sql $sql
}

#tc2:getpermissions on $username: Gets permissions on the $username account
#tc2:getpermissions from $username: Gets permissions $username have on server accounts
proc tc2:getpermissions {keyword username} {
	switch $keyword {
		"from" {
			set sql "SELECT account_username FROM tc2_users_permissions WHERE server_name = '[sqlescape [tc2:hostname]]' AND user_id = '[getuserid $username]'"
		}
		"on" {
			set sql "SELECT u.username FROM tc2_users_permissions p, users u WHERE p.server_name = '[sqlescape [tc2:hostname]]' AND p.account_username = '$username' AND u.user_id = p.user_id"
		}
		default {
			error "from or on expected"
		}
	}
	set accounts ""
	foreach row [sql $sql] {
		lappend accounts [lindex $row 0]
	}
}

#Creates an account $username froÃm the $specified group
proc tc2:createaccount {username group} {
	if {$group == "web"} {
		set key "tc2.[tc2:hostname].wwwroot"
		if {[set wwwroot [registry get $key]] == ""} {
			error "You must define the registry key $key"
		}
		set homedir $wwwroot/$username
		if [catch {
			set reply [exec -- pw user add $username -g $group -b $wwwroot -w random]
			exec -- mkdir -p -m 0711 $homedir
			exec -- chown -R $username:web $homedir
		} err] {
			append reply " / "
			append reply $err
		}
		return $reply
	} {
		exec -- pw user add $username -g $group -m -w random
	}
}

#Checks if $username begins by a letter and contains only letters, digits, -, _ or .
proc tc2:isdomain {domain} {
	regexp "^\[a-z0-9A-Z\]\[a-z0-9A-Z\\-.\]*\[a-z0-9A-Z\]$" $domain
}

proc tc2:cutdomain {domain} {
	#a.b.hostname	a.b	hostname
	#a.tld			a.tld
	#a.b.tld	a	b.tld
	set items [split $domain .]
	if {[llength $items] < 3} {
		list "" $domain
	} elseif {[llength $items] == 3} {
		list [lindex $items 0] [join [lrange $items 1 end] .]
	} {
		set hostname [exec hostname -f]
		set k [expr [llength $hostname] + 1]
		if {[lrange $items end-$k end] == [split $hostname .]} {
			list [join [lrange $items 0 $k] .] $hostname
		} {
			list [join [lrange $items 0 end-2] .] [join [lrange $items end-1 end] .]
		}
	}
}

#Determines if $username is a valid MySQL user
proc tc2:mysql_user_exists {username} {
	sql7 "SELECT count(*) FROM mysql.user WHERE user = '[sqlescape $username]'"
}

#Gets the host matching the first $username MySQL user
proc tc2:mysql_get_host {username} {
	sql7 "SELECT host FROM mysql.user WHERE user = '[sqlescape $username]' LIMIT 1"
}

#Gets a temporary password
proc tc2:randpass {} {
	encrypt [rand 99999999] [rand 99999999]
}

#Adds the SSH key $key to the $username account
proc tc2:sshaddkey {username key} {
	set sshdir "/home/$username/.ssh"
	set keysfile "$sshdir/authorized_keys"
	if ![file exists $sshdir] {
		exec -- mkdir -p -m 0700 $sshdir
		exec chown $username $sshdir
	}
	if ![file isdirectory $sshdir] {
		return 0
	}
	set fd [open $keysfile a]
	puts $fd $key
	close $fd
	exec chmod 600 $keysfile
	exec chown $username $keysfile
	return 1
}

#Guesses web user from requester or domain
proc tc2:guesswebuser {requester domain} {
	set alphanumdomain [regsub -all {([^[:alnum:]])} [string range $domain 0 [string last . $domain]-1] ""]
	foreach candidate [list $domain [string tolower $domain] $alphanumdomain $requester [string tolower $requester]] {
		if {[tc2:username_isvalid $candidate] && [tc2:username_exists $candidate]} {
			return $candidate
		}
	}
	registry get tc2.[tc2:hostname].nginx.defaultuser
}

#
# tc2 commands
#

#account permission
#account isroot
#account exists
#account create <username> <group> [SSH public key url]
proc tc2:command:account {requester arg} {
	set command [lindex $arg 0]
	switch -- $command {
		"exists" {
			set username [lindex $arg 1]
			if ![tc2:username_isvalid $username] {
				return {0 "this is not a valid username"}
			}
			if [tc2:username_exists $username] {
				list 1 "$username is a valid account on [tc2:hostname]."
			} {
				list 1 "$username isn't a valid account on [tc2:hostname]."
			}
		}

		"isroot" {
			set username [lindex $arg 1]
			if ![tc2:username_isvalid $username] {
				return {0 "this is not a valid username"}
			}
			if [tc2:isroot $username] {
				list 1 "$username has got root accreditation on [tc2:hostname]."
			} {
				list 1 "$username doesn't seem to have any root accreditation [tc2:hostname]."
			}
		}

		"permission" {
			set username [lindex $arg 1]
			if ![tc2:username_isvalid $username] {
				return {0 "this is not a valid username"}
			}

			switch -- [lindex $arg 2] {
				"" {
					set sentences {}
					set accounts_from [tc2:getpermissions from $username]
					set accounts_on [tc2:getpermissions on $username]
					if {$accounts_on != ""} {
						lappend sentences "has authority upon [join $accounts_on ", "]"
					}
					if {$accounts_from != ""} {
						lappend sentences "account can be managed from IRC by [join $accounts_from ", "]"
					}
					if {[tc2:isroot $username]} {
						lappend sentences "has root access"
					}
					if {$sentences == ""} {
						list 1 nada
					} {
						list 1 "$username [join $sentences " / "]."
					}
				}

				"add" {
					#e.g. .account permission espacewin add dereckson
					#      will give access to the espacewin account to dereckson
					if {![tc2:isroot $requester] && ![tc2:userallow $requester $username]} {
						return "0 {you don't have the authority to give access to $username account.}"
					}

					#Asserts mandataire has an account
					set mandataire [lindex $arg 3]
					if {[set mandataire_user_id [getuserid $mandataire]] == ""} {
						return "0 {please create first a bot account for $mandataire.}"
					}

					#Adds the permission
					sqlreplace tc2_users_permissions "server_name account_username user_id" [list [tc2:hostname] $username $mandataire_user_id]

					return "1 {$mandataire has now access to $username account.}"
				}

				"del" {
					#e.g. .account permission espacewin del dereckson
					#      will remove access to the espacewin account to dereckson
					if {![tc2:isroot $requester] && ![tc2:userallow $requester $username]} {
						return "0 {you don't have the authority to manage the $username account.}"
					}

					#Asserts mandataire is a valid bot account
					set mandataire [lindex $arg 3]
					if {[set mandataire_user_id [getuserid $mandataire]] == ""} {
						return "0 {$mandataire doesn't have a bot account, and so, no such permission.}"
					}

					#Checks if the permission exists
					if ![tc2:userallow $requester $mandataire] {
						return "0 {$mandataire haven't had an access to $username account.}"
					}

					#Removess the permission
					sql "DELETE FROM tc2_users_permissions WHERE server_name = '[sqlescape [tc2:hostname]]' AND account_username = '$username' AND user_id = '$mandataire_user_id'"

					return "1 {$mandataire doesn't have access to $username account anymore.}"
				}

				"+root" {
					#Checks right and need
					if ![tc2:isroot $requester] {
						return "0 {you don't have root authority yourself.}"
					}
					if [tc2:isroot $username] {
						return "0 {$username have already root authority.}"
					}

					#Declares him as root
					sqlreplace tc2_roots "server_name account_username user_id" [list [tc2:hostname] $username [getuserid $username]]

					#Checks if our intervention is enough
					if [tc2:isroot $username] {
						list 1 "$username have now root authority."
					} {
						list 1 "$username have been added as root and will have root authority once in the wheel group."
					}
				}

				"-root" {
					if ![tc2:isroot $requester] {
						return {0 "you don't have root authority yourself."}
					}
					if ![tc2:isroot $username] {
						list 0 "$username doesn't have root authority."
					} {
						#Removes entry from db
						sql "DELETE FROM tc2_roots WHERE server_name = '[sqlescape [tc2:hostname]]' AND account_username = '[sqlescape $username]'"

						#Checks if our intervention is enough
						list 1 "$username doesn't have root authority on IRC anymore. Check also the wheel group."
					}
				}

				default {
					list 0 "expected: add <username>, del <username>, +root, -root, or nothing"
				}
			}
		}

		"groups" {
			set username [lindex $arg 1]
			if ![tc2:username_isvalid $username] {
				return {0 "this is not a valid username"}
			}
			if [tc2:username_exists $username] {
				list 1 [exec -- id -Gn $username]
			} {
				list 0 "$username isn't a valid account on [tc2:hostname]."
			}
		}

		"create" {
			#Checks access and need
			set username [lindex $arg 1]
			if ![tc2:username_isvalid $username] {
				return {0 "this is not a valid username"}
			}
			if [tc2:username_exists $username] {
				return "0 {there is already a $username account}"
			}
			if ![tc2:isroot $requester] {
				return "0 {you don't have root authority, which is required to create an account.}"
			}

			#Checks group
			set group [lindex $arg 2]
			set validgroups [registry get tc2.[tc2:hostname].usergroups]
			if {$group == ""} {
				return "0 {In which group? Must be amongst $validgroups.}"
			}
			if {[lsearch $validgroups $group] == -1} {
				return "0 {$group isn't a valid group, must be among $validgroups}"
			}

			#Checks public key URL. If so, creates user with SSH key and random password.
			if {[set url [geturls [lindex $arg 3]]] != ""} {
				set password [tc2:createaccount $username $group]
				set keyAdded 0
				if {[catch {
					set key [geturltext $url]
					if {$key != ""} {
						set keyAdded [tc2:sshaddkey $username $key]
					}
				}]} {
					putdebug "An error occured adding the SSH key."
					set keyAdded 0
				}
				if {$keyAdded} {
					return [list 1 "account created"]
				} {
					return [list 1 "account created but can't install SSH key ; you can use the password $password"]
				}
			}

			#Creates user without SSH key.
			list 1 [tc2:createaccount $username $group]
		}

		"" {
			return {0 "permission, isroot, exists or groups expected"}
		}

		default {
			set reply 0
			lappend reply "unknown command: $command"
		}
	}
}

#.mysql create database [username]
proc tc2:command:mysql {requester arg} {
	switch -- [set command [lindex $arg 0]] {
		"create" {
			set database [lindex $arg 1]
			set username [lindex $arg 2]
			if ![tc2:username_isvalid $database] {
				list 0 "Invalid database name: $database"
			} elseif [file exists [registry get tc2.[tc2:hostname].mysql.datadir]/$database] {
				list 1 "database $database already exists"
			} elseif {$username == ""} {
				if {[tc2:mysql_user_exists $database]} {
					tc2:command:mysql $requester [list create $database $database]
				} {
					#Ok, create the database and a new user with same login than db and random password
					set password [tc2:randpass]
					if [catch {
						sql7 "CREATE DATABASE $database"
						sql7 "GRANT ALL PRIVILEGES ON $database.* TO '$database'@'localhost' IDENTIFIED BY '$password'"
					} err] {
						list 0 $err
					} {
						list 1 "database created, with rights granted to user $database, with $password as temporary password"
					}
				}
			} {
				if {![tc2:username_isvalid $username]} {
					list 0 "Invalid username: $username"
				}
				if {[tc2:isroot $requester] || [tc2:userallow $requester $username]} {
					if [catch {
						set host [tc2:mysql_get_host $username]
						sql7 "CREATE DATABASE $database"
						sql7 "GRANT ALL PRIVILEGES ON $database.* TO '$username'@'$host'"
					} err] {
						list 0 $err
					} {
						list 1 "database $database created, with rights granted to $username@$host"
					}
				} {
					[list 0 "You aren't root nor have authority on $username"
				}
			}
		}

		default {
			list 0 "try .mysql create <database> \[username\]"
		}
	}
}

#.nginx reload
#.nginx status
#.nginx server add <domain> [directory] [+php | +upstream <keyword> <url>] [+ssl]
#TODO .nginx server edit <domain> <new directory>
#TODO .nginx server edit <domain> <-php|+php>
#TODO .nginx server edit <domain> <-ssl|+ssl>
proc tc2:command:nginx {requester arg} {
	switch -- [set command [lindex $arg 0]] {
		"reload" {
			if [catch {exec /usr/local/etc/rc.d/nginx reload} output] {
				if {[string first "is successful" $output] == -1} {
					return [list 0 $output]
				} {
					return {1 "ok, nginx reloaded"}
				}
			} {
				return {1 "ok, nginx reloaded"}
			}
		}

		"status" {
			set conn [exec sockstat | grep nginx | grep -c tcp]
			if {$conn == 0} {
				return {1 "nginx not running"}
			} {
				return "1 {$conn connection[s $conn]}"
			}
			return $reply
		}

		"create" {
			tc2:command:nginx $requester [list server add {*}[lrange $arg 1 end]]
		}

		"server" {		
			#.nginx server add <domain> [directory] [+php] [+upstream <url>] [+ssl]
			#.nginx server edit <domain> <+php|-php>
			set subcommand [lindex $arg 1]
			set domain [lindex $arg 2]
			
			if {$subcommand != "" && $domain != "" && [tc2:isdomain $domain]} {
				set fulldomain $domain
				foreach "subdomain domain" [tc2:cutdomain $fulldomain] {}
				set tpldir [registry get tc2.[tc2:hostname].nginx.tpldir]
				set config [registry get tc2.[tc2:hostname].nginx.etcdir]/$domain.conf
				switch $subcommand {
					add {
						#Default options
						global username
						set wwwdir [registry get tc2.[tc2:hostname].wwwroot]/$domain/$subdomain
						set logdir [registry get tc2.[tc2:hostname].nginx.logdir]/$domain
						set ssldir [registry get tc2.[tc2:hostname].nginx.ssldir]/$domain
						set user [tc2:guesswebuser $requester $domain]
						set tpl vhost.tpl
						set php 0
						set ssl 0
						set upstream 0
						set upstream_keyword ""
						set upstream_url ""
						set index "index.html index.htm default.html default.htm"
						set phpfpmport ""

						#Parses options
						for {set i 3} {$i < [llength $arg]} {incr i} {
							set option [lindex $arg $i]
							if {$option == "+php"} {
								set php 1
								set index "index.html index.php index.htm"

								#Determines php-fpm port
								set phpfpmport [sqlscalar "SELECT pool_port FROM tc2_phpfpm_pools WHERE pool_user = '[sqlescape $user]'"]
								if {$phpfpmport == ""} {
									#Fallbacks to default www pool
									set port [registry get tc2.[tc2:hostname].phpfpm.defaultport]
									if {$phpfpmport == ""} {
										return "0 {no pool for $user, and tc2.[tc2:hostname].phpfpm.defaultport registry key isn't defined to fallback to www pool}"
									}
								}
							} elseif {$option == "+upstream"} {
								set upstream 1
								set upstream_keyword [lindex $arg [incr i]]
								set upstream_url [lindex $arg [incr i]]
							} elseif {$option == "+ssl"} {
								set ssl 1
							} elseif {[string index $option 0] == "/"} {
								set wwwdir $option
							} else {
								return [list 0 "Unknown option: $option"]
							}
						}

						#TODO: check if $user is legitimate
						if {$user != "www" && ![tc2:isroot $requester] && ![tc2:userallow $requester $user]} {
							return "0 {you don't have the authority to create a website linked to $user account.}"
						}

						#Creates needed directories
						if ![file exists $wwwdir] {
							exec -- mkdir -m 0711 -p $wwwdir
							exec -- chown $user $wwwdir
						}
						if ![file exists $logdir] {
							exec -- mkdir -m 0711 -p $wwwdir
							exec -- chown $user $wwwdir
						}

						#Prepares new config block
						set fd [open $tpldir/$tpl r]
						set template [read $fd]
						close $fd
						set xtra ""
						foreach option "ssl php upstream" {
							if $$option {
								set xtrafile $tpldir/extra-$option.tpl
								if ![file exists $xtrafile] {
									return [list 0 "Template file not found: $xtrafile"]
								}
								set fd [open $xtrafile]
								append xtra "\n\n"
								append xtra [read $fd]
								close $fd
							}
						}
						set configblock [string map [list %EXTRACONFIG% $xtra] $template]
						set configblock [string map [list %REQUESTER% $requester %TIME% [unixtime] %COMMENT% "Autogenerated by $username" %FULLDOMAIN% $fulldomain %LOGDIR% $logdir %SSLDIR% $ssldir %SUBDOMAIN% $subdomain %WWWDIR% $wwwdir %PHPFPMPORT% $phpfpmport %CUSTOM-PREPHP% "" %CUSTOM-PHP% "" %CUSTOM% "" %UPSTREAMKEYWORD% $upstream_keyword %UPSTREAMURL% $upstream_url %INDEX% $index %EXTRACONFIG% $xtra] $configblock]

						#Opens or creates domain config file
						if [file exists $config] {
							set fd [open $config a]
						} {
							#We use a also template for ou config file header
							set fd [open $tpldir/vhost-header.tpl r]
							set template [read $fd]
							close $fd
							set fd [open $config w]
							puts $fd [string map "%DOMAIN% $domain" $template]
							flush $fd
						}

						#Writes new config block
						puts $fd ""
						puts $fd $configblock
						close $fd
						return [list 1 "done, $fulldomain server block added to $config ; use .nginx reload to save"]
					}

					edit {
						return [list 1 "not yet implemented, edit the file $config"]
					}
				}
			}
			return {0 "usage: .nginx server add/edit domain \[options\]"}
		}

		"" {
			return {0 "server add, server edit, status or reload expected"}
		}

		default {
			set reply 0
			lappend reply "unknown command: $command"
		}
	}

}

#phpfpm reload
#phpfpm status
#phpfpm create <user>
proc tc2:command:phpfpm {requester arg} {
	set command [lindex $arg 0]

	switch $command {
		"reload" {
			if [catch {exec /usr/local/etc/rc.d/php-fpm reload} output] {
				list 0 [string map {"\n" " "} $output]
			} {
				return {1 "ok, php-fpm reloaded"}
			}
		}

		"restart" {
			if [catch {exec /usr/local/etc/rc.d/php-fpm restart} output] {
				list 0 [string map {"\n" " "} $output]
			} {
				return {1 "ok, php-fpm reloaded"}
			}
		}

		"status" {
			catch {exec /usr/local/etc/rc.d/php-fpm status} output
			list 1 [string map {"\n" " "} $output]
		}

		"create" {
			set user [lindex $arg 1]
			if {$user == ""} {
				return {0 "syntax: phpfpm create <user>"}
			}
			if ![tc2:username_isvalid $user] {
				return {0 "not a valid username"}
			}
			if ![tc2:username_exists $user] {
				return "0 {$user isn't a valid [tc2:hostname] user}"
			}
			if [file exists [set file "/usr/local/etc/php-fpm/pool-prod/$user.conf"]] {
				return "0 {there is already a $user pool}"
			}
			if {![tc2:isroot $requester] && ![tc2:userallow $requester $user]} {
				return "0 {you don't have the authority to create a pool under $user user}"
			}
			set port [sql "SELECT MAX(pool_port) FROM tc2_phpfpm_pools"]
			if {$port == ""} {
				set port 9000
			} {
				incr port
			}

			#Adds it in MySQL table
			set time [unixtime]
			sqladd tc2_phpfpm_pools {pool_user pool_port pool_requester pool_time} [list $user $port $requester $time]

			#Write config gile
			global username
			set fd [open /usr/local/etc/php-fpm/pool.tpl r]
			set template [read $fd]
			close $fd
			set fd [open $file w]
			puts $fd [string map "%REQUESTER% $requester %TIME% $time %PORT% $port %USER% $user %GROUP% [exec -- id -gn $user] %COMMENT% {Autogenerated by $username}" $template]
			close $fd
			exec -- chown root:config $file
			exec -- chmod 644 $file
			return {1 "pool created, use '.phpfpm reload' to enable it"}
		}

		"" {
			return {0 "create, status or reload expected"}
		}

		default {
			set reply 0
			lappend reply "unknown command: $command"
		}
	}
}

#.df
#.df pull [extension]
proc tc2:command:df {requester arg} {
	set command [lindex $arg 0]

	switch $command {
		"pull" {
			set what [lindex $arg 1]
			if {$what == ""} {
				set what core
			} {
				if {![file exists [registry get df.paths.extensions]/$what]} {
					return [list 0 "Invalid extension: $what"]
				}
			}

			catch {exec -- su -m [registry get df.who] -c "[registry get df.paths.bin]/dfpull $what"} status
			if { $status == "Already up-to-date." } {
				return { 1 "Repository already up-to-date." }
			} elseif { [string first "Can't currently pull code" $status] > -1 }  {
				list 0 $status
			} else {
				putdebug $status
				return { 1 "repository updated" }
			}
		}

		"" {
			return {0 "pull expected"}
		}

		default {
			set reply 0
			lappend reply "unknown command: $command"
		}

	}
}

#ci status
#ci stop
proc tc2:command:ci {requester arg} {
	set command [lindex $arg 0]

	switch $command {
		"start" {
			list 0 [string range "use /usr/local/etc/rc.d/jenkins onestart" 0 end]
		}

		"status" {
			catch {exec /usr/local/etc/rc.d/jenkins onestatus} status
			list 1 [string range $status 0 [string first . $status]]
		}

		"stop" {
			#Jenkins doesn't reply to stop signal on the server, so we kill it.
			if [catch {exec -- kill -9 [exec cat /var/run/jenkins/jenkins.pid]} output] {
				list 0 [string map {"\n" " "} $output]
			} {
				return {1 "ok, Jenkins stopped"}
			}
		}

		"" {
			return {0 "status or stop expected"}
		}

		default {
			set reply 0
			lappend reply "unknown command: $command"
		}
	}
}