diff options
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | db.go | 8 | ||||
| -rw-r--r-- | domain.go | 4 | ||||
| -rw-r--r-- | domain_test.go | 3 | ||||
| -rw-r--r-- | email.go | 4 | ||||
| -rw-r--r-- | handler_test.go | 445 | ||||
| -rw-r--r-- | main.go | 3 | ||||
| -rw-r--r-- | server.go | 1 | ||||
| -rw-r--r-- | server_test.go | 5 | ||||
| -rw-r--r-- | static/css/style.css | 5 | ||||
| -rw-r--r-- | statistics/mailgraph.cgi | 380 | ||||
| -rw-r--r-- | utilities_test.go | 53 | ||||
| -rw-r--r-- | views/about.html | 26 | ||||
| -rw-r--r-- | views/about_de.html | 26 | ||||
| -rw-r--r-- | views/footer.html | 6 | ||||
| -rw-r--r-- | views/header.html | 2 | ||||
| -rw-r--r-- | views/howto.html | 1 | ||||
| -rw-r--r-- | views/howto_de.html | 1 | ||||
| -rw-r--r-- | views/server.html | 1 | ||||
| -rw-r--r-- | views/server_de.html | 1 | ||||
| -rw-r--r-- | views/user.html | 1 | ||||
| -rw-r--r-- | views/user_de.html | 1 |
22 files changed, 946 insertions, 35 deletions
@@ -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 @@ -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") @@ -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.") @@ -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]) + } +} @@ -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")) @@ -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> </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> +   |   + Libremail <span class="fa fa-copyright"></span> <a class='footer-a' href='//www.iamfabulous.de' title='Maximilian Möhring'>Maximilian Mö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ö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ö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> |
