summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--db.go8
-rw-r--r--domain.go4
-rw-r--r--domain_test.go3
-rw-r--r--email.go4
-rw-r--r--handler_test.go445
-rw-r--r--main.go3
-rw-r--r--server.go1
-rw-r--r--server_test.go5
-rw-r--r--static/css/style.css5
-rw-r--r--statistics/mailgraph.cgi380
-rw-r--r--utilities_test.go53
-rw-r--r--views/about.html26
-rw-r--r--views/about_de.html26
-rw-r--r--views/footer.html6
-rw-r--r--views/header.html2
-rw-r--r--views/howto.html1
-rw-r--r--views/howto_de.html1
-rw-r--r--views/server.html1
-rw-r--r--views/server_de.html1
-rw-r--r--views/user.html1
-rw-r--r--views/user_de.html1
22 files changed, 946 insertions, 35 deletions
diff --git a/Makefile b/Makefile
index cb4c7db..7a1be85 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@ else
VERBOSE:=
endif
-# Allow use of external networks. EXTERNAL=1
+# Use this when you run this test on a domain with a valid MX entry. EXTERNAL=1
ifdef EXTERNAL
EXTERNAL:=-external
else
@@ -91,7 +91,7 @@ pack: gen_config
(echo "Run \"make build\" first." && exit 1)
test: import
- FREEMAIL_DB_CREDENTIALS=":memory:" go test $(RACE) $(VERBOSE) $(EXTERNAL)
+ FREEMAIL_SMTP_MAILER_MX=localhost FREEMAIL_DB_CREDENTIALS=":memory:" go test $(RACE) $(VERBOSE) $(EXTERNAL)
@(rm $(IMPORT_FILE) 2>/dev/null && echo "Removing import file..." ) || true
benchmark: import
diff --git a/db.go b/db.go
index f70ef86..77ac348 100644
--- a/db.go
+++ b/db.go
@@ -21,17 +21,19 @@ func InitDB() {
}
vD := VirtualDomain{}
- Db.Debug().AutoMigrate(&vD)
+ Db.AutoMigrate(&vD)
vU := VirtualUser{}
- Db.Debug().AutoMigrate(&vU)
+ Db.AutoMigrate(&vU)
vA := VirtualAliase{}
- Db.Debug().AutoMigrate(&vA)
+ Db.AutoMigrate(&vA)
Db.Model(&vU).AddUniqueIndex("idx_virtuser_email", "email")
//Db.Model(&vU).AddForeignKey("domain_id", "virtual_domains(id)", "CASCADE", "RESTRICT")
+
/*
+ u := User{}
Db.Model(&u).AddUniqueIndex("idx_user_name", "name")
Db.Model(&u).AddUniqueIndex("idx_user_email", "email")
Db.Model(&h).AddUniqueIndex("idx_host_url", "url")
diff --git a/domain.go b/domain.go
index aa0ce40..6136f63 100644
--- a/domain.go
+++ b/domain.go
@@ -20,11 +20,11 @@ func (vD VirtualDomain) DomainExists() bool {
}
func (vD VirtualDomain) CreateDomain() bool {
- if !Db.Debug().NewRecord(vD) {
+ if !Db.NewRecord(vD) {
log.Println("Creating new record failed.", vD.Name)
return false
}
- query := Db.Debug().Create(&vD)
+ query := Db.Create(&vD)
if query.Error != nil {
log.Println(query.Error)
return false
diff --git a/domain_test.go b/domain_test.go
index 1560d55..af80d0d 100644
--- a/domain_test.go
+++ b/domain_test.go
@@ -24,9 +24,6 @@ func TestDomainExists(t *testing.T) {
}
func TestValidateDomain(t *testing.T) {
- if testing.Short() || !*testExternal {
- t.Skip("Skipping test to avoid external network.")
- }
d := VirtualDomain{Name: "blablabla"}
if d.ValidateDomain("blablabla") {
t.Fatal(d.Name + " is not a valid domain.")
diff --git a/email.go b/email.go
index 90799ee..43bde50 100644
--- a/email.go
+++ b/email.go
@@ -18,11 +18,11 @@ func (vU VirtualUser) EmailExists() bool {
}
func (vU VirtualUser) CreateEmail() bool {
- if !Db.Debug().NewRecord(vU) {
+ if !Db.NewRecord(vU) {
log.Println("Creating new record failed.", vU)
return false
}
- query := Db.Debug().Create(&vU)
+ query := Db.Create(&vU)
if query.Error != nil {
log.Println(query.Error)
return false
diff --git a/handler_test.go b/handler_test.go
new file mode 100644
index 0000000..92088bf
--- /dev/null
+++ b/handler_test.go
@@ -0,0 +1,445 @@
+package main
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+)
+
+func TestIndexHandler(t *testing.T) {
+ request, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/)", err)
+ }
+ response := httptest.NewRecorder()
+
+ IndexHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code.")
+ }
+
+ request, err = http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/)", err)
+ }
+ response = httptest.NewRecorder()
+
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request.AddCookie(&cookie)
+
+ IndexHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code. Language: de")
+ }
+}
+
+func BenchmarkIndexHandler(b *testing.B) {
+ b.StopTimer()
+ request, _ := http.NewRequest("GET", "/", nil)
+ response := httptest.NewRecorder()
+
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ IndexHandler(response, request)
+ }
+}
+
+func TestRegisterHandler(t *testing.T) {
+ request, err := http.NewRequest("GET", "/register", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/register)", err)
+ }
+ response := httptest.NewRecorder()
+
+ RegisterHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code.")
+ }
+
+ request, err = http.NewRequest("GET", "/register", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/register)", err)
+ }
+ response = httptest.NewRecorder()
+
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request.AddCookie(&cookie)
+
+ RegisterHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code. Language: de")
+ }
+}
+
+func BenchmarkRegisterHandler(b *testing.B) {
+ b.StopTimer()
+ request, _ := http.NewRequest("GET", "/register", nil)
+ response := httptest.NewRecorder()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ RegisterHandler(response, request)
+ }
+}
+
+func TestCreateNewEntryHandler(t *testing.T) {
+ form := url.Values{}
+
+ form.Set("Email", "createNewEntry@localhost")
+ form.Set("ConfirmEmail", "createNewEntry@localhost")
+ form.Set("Password", "Password")
+ form.Set("ConfirmPassword", "Password")
+
+ request, err := http.NewRequest("POST", "/create", strings.NewReader(form.Encode()))
+ if err != nil {
+ t.Log("Error creating new http request. (/create)", err)
+ }
+ request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+
+ response := httptest.NewRecorder()
+
+ CreateNewEntryHandler(response, request)
+
+ if response.Code != 302 {
+ t.Fatal("Expected 302 as status code.", response.Code)
+ }
+
+ flash := Flash{}
+ session, err := store.Get(request, "_SID")
+ if err != nil {
+ t.Log(err)
+ }
+ flash.Error = session.Flashes("error")
+ flash.Success = session.Flashes("success")
+
+ if len(flash.Error) > 0 {
+ for _, v := range flash.Error {
+ t.Fatal(v)
+ }
+ t.Fatal("Got error message.")
+ }
+
+ if len(flash.Success) == 0 {
+ t.Fatal("Expected success message.")
+ }
+
+ t.Log(flash.Success)
+}
+
+func TestAboutHandler(t *testing.T) {
+ request, err := http.NewRequest("GET", "/about", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/about)", err)
+ }
+ response := httptest.NewRecorder()
+
+ AboutHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code.")
+ }
+
+ request, err = http.NewRequest("GET", "/about", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/about)", err)
+ }
+ response = httptest.NewRecorder()
+
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request.AddCookie(&cookie)
+
+ AboutHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code. Language: de")
+ }
+}
+
+func BenchmarkAboutHandler(b *testing.B) {
+ b.StopTimer()
+ request, _ := http.NewRequest("GET", "/about", nil)
+ response := httptest.NewRecorder()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ AboutHandler(response, request)
+ }
+}
+
+func TestServerHandler(t *testing.T) {
+ request, err := http.NewRequest("GET", "/server", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/server)", err)
+ }
+ response := httptest.NewRecorder()
+
+ ServerHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code.")
+ }
+
+ request, err = http.NewRequest("GET", "/server", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/server)", err)
+ }
+ response = httptest.NewRecorder()
+
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request.AddCookie(&cookie)
+
+ ServerHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code. Language: de")
+ }
+}
+
+func BenchmarkServerHandler(b *testing.B) {
+ b.StopTimer()
+ request, _ := http.NewRequest("GET", "/server", nil)
+ response := httptest.NewRecorder()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ ServerHandler(response, request)
+ }
+}
+
+func TestHowtoHandler(t *testing.T) {
+ request, err := http.NewRequest("GET", "/howto", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/howto)", err)
+ }
+ response := httptest.NewRecorder()
+
+ HowtoHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code.")
+ }
+
+ request, err = http.NewRequest("GET", "/howto", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/howto)", err)
+ }
+ response = httptest.NewRecorder()
+
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request.AddCookie(&cookie)
+
+ HowtoHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code. Language: de")
+ }
+}
+
+func BenchmarkHowtoHandler(b *testing.B) {
+ b.StopTimer()
+ request, _ := http.NewRequest("GET", "/howto", nil)
+ response := httptest.NewRecorder()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ HowtoHandler(response, request)
+ }
+}
+
+func TestUserHandler(t *testing.T) {
+ request, err := http.NewRequest("GET", "/user", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/user)", err)
+ }
+ response := httptest.NewRecorder()
+
+ UserHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code.")
+ }
+
+ request, err = http.NewRequest("GET", "/user", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/user)", err)
+ }
+ response = httptest.NewRecorder()
+
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request.AddCookie(&cookie)
+
+ UserHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code. Language: de")
+ }
+}
+
+func BenchmarkUserHandler(b *testing.B) {
+ b.StopTimer()
+ request, _ := http.NewRequest("GET", "/user", nil)
+ response := httptest.NewRecorder()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ UserHandler(response, request)
+ }
+}
+
+func TestPasswordHandler(t *testing.T) {
+ request, err := http.NewRequest("GET", "/password", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/password)", err)
+ }
+ response := httptest.NewRecorder()
+
+ PasswordHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code.")
+ }
+
+ request, err = http.NewRequest("GET", "/password", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/password)", err)
+ }
+ response = httptest.NewRecorder()
+
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request.AddCookie(&cookie)
+
+ PasswordHandler(response, request)
+
+ if response.Code != 200 {
+ t.Log(response.Code)
+ t.Fatal("Expected 200 as status code. Language: de")
+ }
+}
+
+func BenchmarkPasswordHandler(b *testing.B) {
+ b.StopTimer()
+ request, _ := http.NewRequest("GET", "/password", nil)
+ response := httptest.NewRecorder()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ PasswordHandler(response, request)
+ }
+}
+
+func TestChangePasswordHandler(t *testing.T) {
+ form := url.Values{}
+
+ if err := CreateNewEntry("test@localhost", Md5Hash("Password")); err != nil {
+ t.Fatal(err)
+ }
+
+ form.Set("Email", "test@localhost")
+ form.Set("Password", "Password")
+ form.Set("ConfirmPassword", "password")
+ form.Set("NewPassword", "password")
+
+ request, err := http.NewRequest("POST", "/changePassword", strings.NewReader(form.Encode()))
+ if err != nil {
+ t.Log("Error creating new http request. (/changePassword)", err)
+ }
+ request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
+ response := httptest.NewRecorder()
+
+ ChangePasswordHandler(response, request)
+
+ if response.Code != 302 {
+ t.Log(response.Code)
+ t.Fatal("Expected 302 as status code.")
+ }
+
+ flash := Flash{}
+ session, err := store.Get(request, "_SID")
+ if err != nil {
+ t.Log(err)
+ }
+ flash.Error = session.Flashes("error")
+ flash.Success = session.Flashes("success")
+
+ if len(flash.Error) > 0 {
+ for _, v := range flash.Error {
+ t.Fatal(v)
+ }
+ t.Fatal("Got error message.")
+ }
+
+ if len(flash.Success) == 0 {
+ t.Fatal("Expected success message.")
+ }
+
+ t.Log(flash.Success)
+}
+
+func TestChangeLocaleHandler(t *testing.T) {
+ // Test change locale to German
+ request, err := http.NewRequest("GET", "/locale?to=de", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/locale?to=de)", err)
+ }
+ response := httptest.NewRecorder()
+
+ ChangeLocaleHandler(response, request)
+
+ if response.Code != 302 {
+ t.Log(response.Code)
+ t.Fatal("Expected 302 as status code.")
+ }
+
+ if response.HeaderMap["Set-Cookie"][0] != "lang=de" {
+ t.Fatal("Expected Cookie 'lang=de")
+ }
+
+ // Test change locale to English
+ request, err = http.NewRequest("GET", "/locale?to=en", nil)
+ if err != nil {
+ t.Log("Error creating new http request. (/locale?to=en)", err)
+ }
+ response = httptest.NewRecorder()
+
+ ChangeLocaleHandler(response, request)
+
+ if response.Code != 302 {
+ t.Log(response.Code)
+ t.Fatal("Expected 302 as status code.")
+ }
+
+ if response.HeaderMap["Set-Cookie"][0] != "lang=en" {
+ t.Fatal("Expected Cookie 'lang=en'", response.HeaderMap["Set-Cookie"][0])
+ }
+
+ // Test change locale to Garbage
+ request, err = http.NewRequest("GET", "/locale?to=foobar", nil)
+ if err != nil {
+ t.Fatal("Error creating new http request. (/locale?to=foobar)", err)
+ }
+ response = httptest.NewRecorder()
+
+ ChangeLocaleHandler(response, request)
+
+ if response.Code != 302 {
+ t.Log(response.Code)
+ t.Fatal("Expected 302 as status code.")
+ }
+
+ if response.HeaderMap["Set-Cookie"][0] != "lang=foobar" {
+ t.Fatal("Expected Cookie 'lang=foobar'", response.HeaderMap["Set-Cookie"][0])
+ }
+}
diff --git a/main.go b/main.go
index 5104d6e..984446c 100644
--- a/main.go
+++ b/main.go
@@ -12,7 +12,8 @@ import (
var decoder = schema.NewDecoder()
-var store = sessions.NewCookieStore([]byte(os.Getenv("FREEMAIL_SECRET")))
+//var store = sessions.NewCookieStore([]byte(os.Getenv("FREEMAIL_SECRET")))
+var store = sessions.NewCookieStore()
var mainTempl = template.Must(template.New("global").Funcs(template.FuncMap{"add": add}).ParseGlob("./views/*.html"))
var emailTempl = template.Must(template.New("email").Funcs(template.FuncMap{"add": add}).ParseGlob("./views/email/*.html"))
diff --git a/server.go b/server.go
index 5b29e50..0ea1851 100644
--- a/server.go
+++ b/server.go
@@ -62,6 +62,7 @@ func ExecTemplate(template string, w http.ResponseWriter, r *http.Request, flash
func ChangePassword(email, oldPassword, newPassword string) error {
if !ValidateEmail(email) {
+ log.Println("No E-Mail: ", email)
return errors.New("This doesn't look like a mail adress.")
}
diff --git a/server_test.go b/server_test.go
index 57d96c3..ba84bc7 100644
--- a/server_test.go
+++ b/server_test.go
@@ -5,10 +5,7 @@ import (
)
func TestCreateNewEntry(t *testing.T) {
- if testing.Short() || !*testExternal {
- t.Skip("Skipping test to avoid external network.")
- }
- err := CreateNewEntry("foobar@example.org", "password")
+ err := CreateNewEntry("foobar@localhost", "password")
if err != nil {
t.Fatal(err)
}
diff --git a/static/css/style.css b/static/css/style.css
index 95942a9..c7f2014 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -49,7 +49,10 @@ body {
.footer-a {
color: white;
}
-.footer-a:hover {
+.footer-a:link,
+.footer-a:hover,
+.footer-a:visited,
+.footer-a:active {
color: white;
text-decoration: underline;
}
diff --git a/statistics/mailgraph.cgi b/statistics/mailgraph.cgi
new file mode 100644
index 0000000..69d539a
--- /dev/null
+++ b/statistics/mailgraph.cgi
@@ -0,0 +1,380 @@
+#!/usr/bin/perl -w
+
+# mailgraph -- postfix mail traffic statistics
+# copyright (c) 2000-2007 ETH Zurich
+# copyright (c) 2000-2007 David Schweikert <david@schweikert.ch>
+# released under the GNU General Public License
+
+use RRDs;
+use POSIX qw(uname);
+
+my $VERSION = "1.14";
+
+my $host = (POSIX::uname())[1];
+my $scriptname = $ENV{"SCRIPT_NAME"};
+my $xpoints = 540;
+my $points_per_sample = 3;
+my $ypoints = 160;
+my $ypoints_err = 96;
+my $ypoints_grey = 96;
+my $rrd = '/var/lib/mailgraph/mailgraph.rrd'; # path to where the RRD database is
+my $rrd_virus = '/var/lib/mailgraph/mailgraph_virus.rrd'; # path to where the Virus RRD database is
+my $rrd_greylist = '/var/lib/mailgraph/mailgraph_greylist.rrd'; # path to where the Greylist RRD database is
+my $tmp_dir = '/var/lib/mailgraph'; # temporary directory where to store the images
+my @graphs = (
+ { title => 'Last Day', seconds => 3600*24, },
+ { title => 'Last Week', seconds => 3600*24*7, },
+ { title => 'Last Month', seconds => 3600*24*31, },
+ { title => 'Last Year', seconds => 3600*24*365, },
+);
+
+my %color = (
+ sent => '000099', # rrggbb in hex
+ received => '009900',
+ rejected => 'AA0000',
+ bounced => '000000',
+ virus => 'DDBB00',
+ spam => '999999',
+ greylisted => '999999',
+ delayed => '006400',
+);
+
+sub rrd_graph(@)
+{
+ my ($range, $file, $ypoints, @rrdargs) = @_;
+ my $step = $range*$points_per_sample/$xpoints;
+ # choose carefully the end otherwise rrd will maybe pick the wrong RRA:
+ my $end = time; $end -= $end % $step;
+ my $date = localtime(time);
+ $date =~ s|:|\\:|g unless $RRDs::VERSION < 1.199908;
+
+ my ($graphret,$xs,$ys) = RRDs::graph($file,
+ '--imgformat', 'PNG',
+ '--width', $xpoints,
+ '--height', $ypoints,
+ '--start', "-$range",
+ '--end', $end,
+ '--vertical-label', 'msgs/min',
+ '--lower-limit', 0,
+ '--units-exponent', 0, # don't show milli-messages/s
+ '--lazy',
+ '--color', 'SHADEA#ffffff',
+ '--color', 'SHADEB#ffffff',
+ '--color', 'BACK#ffffff',
+
+ $RRDs::VERSION < 1.2002 ? () : ( '--slope-mode'),
+
+ @rrdargs,
+
+ 'COMMENT:['.$date.']\r',
+ );
+
+ my $ERR=RRDs::error;
+ die "ERROR: $ERR\n" if $ERR;
+}
+
+sub graph($$)
+{
+ my ($range, $file) = @_;
+ my $step = $range*$points_per_sample/$xpoints;
+ rrd_graph($range, $file, $ypoints,
+ "DEF:sent=$rrd:sent:AVERAGE",
+ "DEF:msent=$rrd:sent:MAX",
+ "CDEF:rsent=sent,60,*",
+ "CDEF:rmsent=msent,60,*",
+ "CDEF:dsent=sent,UN,0,sent,IF,$step,*",
+ "CDEF:ssent=PREV,UN,dsent,PREV,IF,dsent,+",
+ "AREA:rsent#$color{sent}:Sent ",
+ 'GPRINT:ssent:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rsent:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmsent:MAX:max\: %4.0lf msgs/min\l',
+
+ "DEF:recv=$rrd:recv:AVERAGE",
+ "DEF:mrecv=$rrd:recv:MAX",
+ "CDEF:rrecv=recv,60,*",
+ "CDEF:rmrecv=mrecv,60,*",
+ "CDEF:drecv=recv,UN,0,recv,IF,$step,*",
+ "CDEF:srecv=PREV,UN,drecv,PREV,IF,drecv,+",
+ "LINE2:rrecv#$color{received}:Received",
+ 'GPRINT:srecv:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rrecv:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmrecv:MAX:max\: %4.0lf msgs/min\l',
+ );
+}
+
+sub graph_err($$)
+{
+ my ($range, $file) = @_;
+ my $step = $range*$points_per_sample/$xpoints;
+ rrd_graph($range, $file, $ypoints_err,
+ "DEF:bounced=$rrd:bounced:AVERAGE",
+ "DEF:mbounced=$rrd:bounced:MAX",
+ "CDEF:rbounced=bounced,60,*",
+ "CDEF:dbounced=bounced,UN,0,bounced,IF,$step,*",
+ "CDEF:sbounced=PREV,UN,dbounced,PREV,IF,dbounced,+",
+ "CDEF:rmbounced=mbounced,60,*",
+ "AREA:rbounced#$color{bounced}:Bounced ",
+ 'GPRINT:sbounced:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rbounced:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmbounced:MAX:max\: %4.0lf msgs/min\l',
+
+ "DEF:virus=$rrd_virus:virus:AVERAGE",
+ "DEF:mvirus=$rrd_virus:virus:MAX",
+ "CDEF:rvirus=virus,60,*",
+ "CDEF:dvirus=virus,UN,0,virus,IF,$step,*",
+ "CDEF:svirus=PREV,UN,dvirus,PREV,IF,dvirus,+",
+ "CDEF:rmvirus=mvirus,60,*",
+ "STACK:rvirus#$color{virus}:Viruses ",
+ 'GPRINT:svirus:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rvirus:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmvirus:MAX:max\: %4.0lf msgs/min\l',
+
+ "DEF:spam=$rrd_virus:spam:AVERAGE",
+ "DEF:mspam=$rrd_virus:spam:MAX",
+ "CDEF:rspam=spam,60,*",
+ "CDEF:dspam=spam,UN,0,spam,IF,$step,*",
+ "CDEF:sspam=PREV,UN,dspam,PREV,IF,dspam,+",
+ "CDEF:rmspam=mspam,60,*",
+ "STACK:rspam#$color{spam}:Spam ",
+ 'GPRINT:sspam:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rspam:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmspam:MAX:max\: %4.0lf msgs/min\l',
+
+ "DEF:rejected=$rrd:rejected:AVERAGE",
+ "DEF:mrejected=$rrd:rejected:MAX",
+ "CDEF:rrejected=rejected,60,*",
+ "CDEF:drejected=rejected,UN,0,rejected,IF,$step,*",
+ "CDEF:srejected=PREV,UN,drejected,PREV,IF,drejected,+",
+ "CDEF:rmrejected=mrejected,60,*",
+ "LINE2:rrejected#$color{rejected}:Rejected",
+ 'GPRINT:srejected:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rrejected:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmrejected:MAX:max\: %4.0lf msgs/min\l',
+
+ );
+}
+
+sub graph_grey($$)
+{
+ my ($range, $file) = @_;
+ my $step = $range*$points_per_sample/$xpoints;
+ rrd_graph($range, $file, $ypoints_grey,
+ "DEF:greylisted=$rrd_greylist:greylisted:AVERAGE",
+ "DEF:mgreylisted=$rrd_greylist:greylisted:MAX",
+ "CDEF:rgreylisted=greylisted,60,*",
+ "CDEF:dgreylisted=greylisted,UN,0,greylisted,IF,$step,*",
+ "CDEF:sgreylisted=PREV,UN,dgreylisted,PREV,IF,dgreylisted,+",
+ "CDEF:rmgreylisted=mgreylisted,60,*",
+ "AREA:rgreylisted#$color{greylisted}:Greylisted",
+ 'GPRINT:sgreylisted:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rgreylisted:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmgreylisted:MAX:max\: %4.0lf msgs/min\l',
+
+ "DEF:delayed=$rrd_greylist:delayed:AVERAGE",
+ "DEF:mdelayed=$rrd_greylist:delayed:MAX",
+ "CDEF:rdelayed=delayed,60,*",
+ "CDEF:ddelayed=delayed,UN,0,delayed,IF,$step,*",
+ "CDEF:sdelayed=PREV,UN,ddelayed,PREV,IF,ddelayed,+",
+ "CDEF:rmdelayed=mdelayed,60,*",
+ "LINE2:rdelayed#$color{delayed}:Delayed ",
+ 'GPRINT:sdelayed:MAX:total\: %8.0lf msgs',
+ 'GPRINT:rdelayed:AVERAGE:avg\: %5.2lf msgs/min',
+ 'GPRINT:rmdelayed:MAX:max\: %4.0lf msgs/min\l',
+ );
+}
+
+
+sub print_html()
+{
+ print "Content-Type: text/html\n\n";
+
+ print <<HEADER;
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Mail statistics for Libremail</title>
+<meta http-equiv="Refresh" content="300" />
+<meta http-equiv="Pragma" content="no-cache" />
+<meta http-equiv="X-UA-Compatible" content="IE=edge">
+<meta name='viewport' content='width=device-width, initial-scale=1.0'>
+ <link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css'>
+ <link rel='stylesheet' type='text/css' href='/static/css/material-wfont.min.css'>
+ <link rel='stylesheet' type='text/css' href='/static/css/material.min.css'>
+ <link rel='stylesheet' type='text/css' href='/static/css/ripples.min.css'>
+ <link rel='stylesheet' type='text/css' href='/static/css/style.css'>
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
+ <link rel='shortcut icon' type='image/png' href='/static/img/favicon.ico'>
+<style type="text/css">
+* { margin: 0; padding: 0 }
+/*
+body { width: 630px; background-color: white;
+ font-family: sans-serif;
+ font-size: 12pt;
+ margin: 5px }
+*/
+h1 { margin-top: 20px; margin-bottom: 30px;
+ text-align: center }
+h2 { background-color: #ddd;
+ padding: 2px 0 2px 4px }
+hr { height: 1px;
+ border: 0;
+ border-top: 1px solid #aaa }
+table { border: 0px; width: 100% }
+img { border: 0 }
+a { text-decoration: none; color: #00e }
+a:hover { text-decoration: underline; }
+#jump { margin: 0 0 10px 4px }
+#jump li { list-style: none; display: inline;
+ font-size: 90%; }
+#jump li:after { content: "|"; }
+#jump li:last-child:after { content: ""; }
+</style>
+</head>
+<body>
+<nav class='navbar navbar-default navbar-custom shadow-z-2' role='navigation'>
+<div class='container'>
+<div class='navbar-header'>
+<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbarCollapse" aria-expanded="false" aria-controls="navbar">
+<span class="sr-only">Toggle navigation</span>
+<span class="icon-bar"></span>
+<span class="icon-bar"></span>
+<span class="icon-bar"></span>
+</button>
+<a class='navbar-brand' href='/'><span class='glyphicon glyphicon-home'></span> Home</a>
+</div>
+<div class="collapse navbar-collapse" id="navbarCollapse">
+<ul class="nav navbar-nav navbar-left">
+<li>
+<a class="" href="/about" title="About us"><span class="glyphicon glyphicon-file"></span> About</a>
+</li>
+<li>
+<a class="" href="/howto" title="How to"><span class="glyphicon glyphicon-star"></span> How to</a>
+</li>
+<li>
+<a class="" href="/server" title="Server"><span class="glyphicon glyphicon-cog"></span> Server</a>
+</li>
+<li>
+<a class="" href="https://iamfabulous.de/contact/" title="Contact" target="_blank"><span class="glyphicon glyphicon-phone-alt"></span> Contact</a>
+</li>
+</ul>
+<ul class="nav navbar-nav navbar-right">
+<li>
+<a class="" href="https://iamfabulous.de/webmail" title="Webmail"><span class="glyphicon glyphicon-envelope"></span> Webmail</a>
+</li>
+<li class="dropdown">
+<a class="" href="/register" title="Sign Up" data-target="#" data-toggle="dropdown"><span class="glyphicon glyphicon-user"></span> User <span class="caret"></span></a>
+<ul class="dropdown-menu" role="menu">
+<li><a class="" href="/register" title="Sign Up"><span class="fa fa-user-plus"></span> Create New</a></li>
+<li><a class="" href="/password" title="Change Password"><span class="fa fa-lock"></span> Change Password</a></li>
+</ul>
+</li>
+</ul>
+</div>
+</div>
+</nav>
+<div class="container">
+ <div class="row">
+ <div class="cold-md-12">
+ <h1>Mail statistics for Libremail</h1>
+ <div class="well">
+HEADER
+
+ #print "<h1>Mail statistics for Libremail</h1>\n";
+
+ print "<ul id=\"jump\">\n";
+ for my $n (0..$#graphs) {
+ print " <li><a href=\"#G$n\">$graphs[$n]{title}</a>&nbsp;</li>\n";
+ }
+ print "</ul>\n";
+
+ for my $n (0..$#graphs) {
+ print "<h2 id=\"G$n\">$graphs[$n]{title}</h2>\n";
+ print "<p><img class=\"img-responsive\" src=\"$scriptname?${n}-n\" alt=\"mailgraph\"/><br/>\n";
+ print "<img class=\"img-responsive\" src=\"$scriptname?${n}-e\" alt=\"mailgraph\"/>\n";
+ print "<img class=\"img-responsive\" src=\"$scriptname?${n}-g\" alt=\"mailgraph\"/></p>\n";
+ }
+
+ print <<FOOTER;
+<hr/>
+ </div>
+ </div>
+ </div>
+</div>
+<footer class="footer shadow-y-2">
+ <div class="container">
+ <div class="col-md-12">
+ <p class="sticky-footer">
+ Provided by <a class="footer-a" href="http://mailgraph.schweikert.ch/">Mailgraph <span class="fa fa-external-link"></span></a>
+ &#160; | &#160;
+ Libremail <span class="fa fa-copyright"></span> <a class='footer-a' href='//www.iamfabulous.de' title='Maximilian Möhring'>Maximilian M&ouml;hring <span class="fa fa-external-link"></span></a>
+ </p>
+ </div>
+ </div>
+</footer>
+<script src='//code.jquery.com/jquery-1.11.2.min.js'></script>
+<script src='//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js'></script>
+<script src='/static/js/material.min.js'></script>
+<script src='/static/js/ripples.min.js'></script>
+<script>
+ \$(document).ready(function(){
+ \$('.dropdown-toggle').dropdown();
+ })
+</script>
+</body></html>
+FOOTER
+}
+
+sub send_image($)
+{
+ my ($file)= @_;
+
+ -r $file or do {
+ print "Content-type: text/plain\n\nERROR: can't find $file\n";
+ exit 1;
+ };
+
+ print "Content-type: image/png\n";
+ print "Content-length: ".((stat($file))[7])."\n";
+ print "\n";
+ open(IMG, $file) or die;
+ my $data;
+ print $data while read(IMG, $data, 16384)>0;
+}
+
+sub main()
+{
+ my $uri = $ENV{REQUEST_URI} || '';
+ $uri =~ s/\/[^\/]+$//;
+ $uri =~ s/\//,/g;
+ $uri =~ s/(\~|\%7E)/tilde,/g;
+ mkdir $tmp_dir, 0777 unless -d $tmp_dir;
+ mkdir "$tmp_dir/$uri", 0777 unless -d "$tmp_dir/$uri";
+
+ my $img = $ENV{QUERY_STRING};
+ if(defined $img and $img =~ /\S/) {
+ if($img =~ /^(\d+)-n$/) {
+ my $file = "$tmp_dir/$uri/mailgraph_$1.png";
+ graph($graphs[$1]{seconds}, $file);
+ send_image($file);
+ }
+ elsif($img =~ /^(\d+)-e$/) {
+ my $file = "$tmp_dir/$uri/mailgraph_$1_err.png";
+ graph_err($graphs[$1]{seconds}, $file);
+ send_image($file);
+ }
+ elsif($img =~ /^(\d+)-g$/) {
+ my $file = "$tmp_dir/$uri/mailgraph_$1_grey.png";
+ graph_grey($graphs[$1]{seconds}, $file);
+ send_image($file);
+ }
+ else {
+ die "ERROR: invalid argument\n";
+ }
+ }
+ else {
+ print_html;
+ }
+}
+
+main;
diff --git a/utilities_test.go b/utilities_test.go
index 4776323..5f095dc 100644
--- a/utilities_test.go
+++ b/utilities_test.go
@@ -1,6 +1,7 @@
package main
import (
+ "net/http"
"testing"
)
@@ -21,3 +22,55 @@ func TestCompareStrings(t *testing.T) {
t.Fatal("Strings are not equal. Exptected false")
}
}
+
+func TestGetLanguage(t *testing.T) {
+ // Test language with neither cookie nor Accept-Language header
+ request, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatal("Error creating new http request. (/)", err)
+ }
+ lang := GetLanguage(request)
+ if lang != "" {
+ t.Log(lang)
+ t.Fatal("Expected empty string as lang.")
+ }
+
+ // Test language with lang=de
+ cookie := http.Cookie{Name: "lang", Value: "de"}
+ request, err = http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatal("Error creating new http request. (/) 2nd time.", err)
+ }
+ request.AddCookie(&cookie)
+ lang = GetLanguage(request)
+ if lang != "_de" {
+ t.Log(lang)
+ t.Fatal("Expected '_de' as lang.")
+ }
+
+ // Test language with lang=en
+ cookie = http.Cookie{Name: "lang", Value: "en"}
+ request, err = http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatal("Error creating new http request. (/) 3rd time.", err)
+ }
+ request.AddCookie(&cookie)
+ lang = GetLanguage(request)
+ if lang != "" {
+ t.Log(lang)
+ t.Fatal("Expected empty string as lang.")
+ }
+
+ // Test language with garbage
+ cookie = http.Cookie{Name: "lang", Value: "foobar"}
+ request, err = http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatal("Error creating new http request. (/) 4th time.", err)
+ }
+ request.AddCookie(&cookie)
+ lang = GetLanguage(request)
+ if lang != "" {
+ t.Log(lang)
+ t.Fatal("Expected empty string as lang.")
+ }
+}
diff --git a/views/about.html b/views/about.html
index ad1cbad..44c837d 100644
--- a/views/about.html
+++ b/views/about.html
@@ -2,13 +2,6 @@
{{template "navbar.html"}}
{{template "alert.html" .}}
-<!--div class="jumbotron">
- <div class="container">
- <h1>About</h1>
- <p>Everything about Libremail</p>
- </div>
-</div-->
-
<div class="container">
<div class="row">
<div class="col-md-12">
@@ -40,6 +33,25 @@
<br>
<h2 id="sign_up">How to sign up?</h2>
<p>Please look <a href="/howto#create_new" title="How to sign up">here</a>.
+ <!--br>
+ <h2 id="software">Used Software</h2>
+ <p>
+ Postfix - SMTP
+ <br>
+ Dovecot - IMAP, POP3
+ <br>
+ Roundcube - Webmali
+ <br>
+ MariaDB - Database
+ <br>
+ TumgreySPF - Greylisting
+ <br>
+ Libremail - Golang
+ <br>
+ Mailgraph - Statistics
+ <br>
+ Debian - Operating System
+ </p-->
<br>
<h2 id="warranty">Warranty</h2>
<p>
diff --git a/views/about_de.html b/views/about_de.html
index d56ef4f..0f46fed 100644
--- a/views/about_de.html
+++ b/views/about_de.html
@@ -2,13 +2,6 @@
{{template "navbar_de.html"}}
{{template "alert.html" .}}
-<!--div class="jumbotron">
- <div class="container">
- <h1>About</h1>
- <p>Everything about Libremail</p>
- </div>
-</div-->
-
<div class="container">
<div class="row">
<div class="col-md-12">
@@ -42,6 +35,25 @@
<br>
<h2 id="sign_up">Wie erstelle ich eine neue Adresse?</h2>
<p>Schauen Sie <a href="/howto#create_new" title="How to sign up">hier</a> nach.</p>
+ <!--br>
+ <h2 id="software">Used Software</h2>
+ <p>
+ Postfix - SMTP
+ <br>
+ Dovecot - IMAP, POP3
+ <br>
+ Roundcube - Webmali
+ <br>
+ MariaDB - Database
+ <br>
+ TumgreySPF - Greylisting
+ <br>
+ Libremail - Golang
+ <br>
+ Mailgraph - Statistics
+ <br>
+ Debian - Operating System
+ </p-->
<br>
<h2 id="warranty">Garantie</h2>
<p>
diff --git a/views/footer.html b/views/footer.html
index 8f2c02e..19c589b 100644
--- a/views/footer.html
+++ b/views/footer.html
@@ -1,7 +1,11 @@
<footer class="footer shadow-y-2">
<div class="container">
<div class="col-md-12">
- <p class="sticky-footer"> <span class="fa fa-copyright"></span> <a class='footer-a' href='//www.iamfabulous.de' title='Maximilian Möhring'>Maximilian M&ouml;hring <span class="fa fa-external-link"></span></a></p>
+ <p class="sticky-footer">
+ <a class="footer-a" href="/statistics" title="Statistics"><span class="fa fa-bar-chart"></span> Statistics</a>
+ |
+ <span class="fa fa-copyright"></span> <a class='footer-a' href='//www.iamfabulous.de' title='Maximilian Möhring'>Maximilian M&ouml;hring <span class="fa fa-external-link"></span></a>
+ </p>
</div>
</div>
</footer>
diff --git a/views/header.html b/views/header.html
index c41b483..8269f3d 100644
--- a/views/header.html
+++ b/views/header.html
@@ -5,10 +5,8 @@
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
- <!--title>Libremail @ mail.iamfabulous.de</title-->
<title>{{.}}</title>
<link rel='stylesheet' href='//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css'>
- <link rel='stylesheet' type='text/css' href='/static/css/bootstrap.min.css'>
<link rel='stylesheet' type='text/css' href='/static/css/material-wfont.min.css'>
<link rel='stylesheet' type='text/css' href='/static/css/material.min.css'>
<link rel='stylesheet' type='text/css' href='/static/css/ripples.min.css'>
diff --git a/views/howto.html b/views/howto.html
index a25d734..ae8d9fe 100644
--- a/views/howto.html
+++ b/views/howto.html
@@ -6,6 +6,7 @@
<div class="row">
<div class="col-md-12">
<h1>How to</h1>
+ <br>
<div class="well well-lg">
<h2 id="create_new">Create new adress</h2>
<p>To create a new mail adress follow and fill out <a href="/register" title="Sign up">this</a> link.
diff --git a/views/howto_de.html b/views/howto_de.html
index 2f46ffa..5b0f9a5 100644
--- a/views/howto_de.html
+++ b/views/howto_de.html
@@ -6,6 +6,7 @@
<div class="row">
<div class="col-md-12">
<h1>How to</h1>
+ <br>
<div class="well well-lg">
<h2 id="create_new">Neue Adresse</h2>
<p>Um eine neue E-Mail Adresse auszufüllen folgen Sie <a href="/register" title="Sign up">diesem</a> link.
diff --git a/views/server.html b/views/server.html
index 5b88502..9326b5c 100644
--- a/views/server.html
+++ b/views/server.html
@@ -6,6 +6,7 @@
<div class="row">
<div class="col-md-12">
<h1>Server details</h1>
+ <br>
<div class="well well-lg">
<p>Everything you need to know to use Libremail.</p>
<ul class="list-unstyled">
diff --git a/views/server_de.html b/views/server_de.html
index 770278e..c4d2f6f 100644
--- a/views/server_de.html
+++ b/views/server_de.html
@@ -6,6 +6,7 @@
<div class="row">
<div class="col-md-12">
<h1>Server Details</h1>
+ <br>
<div class="well well-lg">
<p>Alles was Sie wissen müssen um Libremail zu benutzen.</p>
<ul class="list-unstyled">
diff --git a/views/user.html b/views/user.html
index 0d4f7c6..d8a5175 100644
--- a/views/user.html
+++ b/views/user.html
@@ -6,6 +6,7 @@
<div class="row">
<div class="col-md-12">
<h1>User</h1>
+ <br>
<div class="well well-lg">
<h2 id="change_password">Change Password</h2>
<p>
diff --git a/views/user_de.html b/views/user_de.html
index df1b5c4..fe5e1a1 100644
--- a/views/user_de.html
+++ b/views/user_de.html
@@ -6,6 +6,7 @@
<div class="row">
<div class="col-md-12">
<h1>Nutzer Übersicht</h1>
+ <br>
<div class="well well-lg">
<h2 id="change_password">Ändere das Passwort</h2>
<p>