From 472958ea03523307726dc5ae28b83805895ef77e Mon Sep 17 00:00:00 2001
From: Arjan H
Date: Sun, 18 Nov 2018 10:28:29 +0100
Subject: [PATCH 1/3] gofmt the .go files
#2
---
gui/acme.go | 1141 +++++++-------
gui/certificate.go | 796 +++++-----
gui/dashboard.go | 567 ++++---
gui/main.go | 3655 ++++++++++++++++++++++----------------------
4 files changed, 3078 insertions(+), 3081 deletions(-)
diff --git a/gui/acme.go b/gui/acme.go
index eee6ac9..3bacc2d 100644
--- a/gui/acme.go
+++ b/gui/acme.go
@@ -1,733 +1,732 @@
package main
import (
- "database/sql"
- "html/template"
- "net"
- "net/http"
- "strconv"
+ "database/sql"
+ "html/template"
+ "net"
+ "net/http"
+ "strconv"
)
type BaseList struct {
- Title string
- TableClass string
- Header []template.HTML
+ Title string
+ TableClass string
+ Header []template.HTML
}
type Account struct {
- Id int
- Status string
- Contact string
- Agreement string
- InitialIp net.IP
- CreatedAt string
+ Id int
+ Status string
+ Contact string
+ Agreement string
+ InitialIp net.IP
+ CreatedAt string
}
type AccountList struct {
- BaseList
- Rows []Account
+ BaseList
+ Rows []Account
}
-func GetAccounts(w http.ResponseWriter, r *http.Request) (AccountList, error){
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountList{}, err
- }
+func GetAccounts(w http.ResponseWriter, r *http.Request) (AccountList, error) {
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountList{}, err
+ }
- defer db.Close()
+ defer db.Close()
- rows, err := db.Query("SELECT id, status, contact, agreement, initialIP, createdAt FROM registrations")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountList{}, err
- }
+ rows, err := db.Query("SELECT id, status, contact, agreement, initialIP, createdAt FROM registrations")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountList{}, err
+ }
- Accounts := AccountList{
- BaseList: BaseList{
- Title: "Accounts",
- TableClass: "accounts_list",
- Header: []template.HTML{"ID", "Status", "Contact", "Agreement", "Initial IP", "Created"},
- },
- Rows: []Account{},
- }
+ Accounts := AccountList{
+ BaseList: BaseList{
+ Title: "Accounts",
+ TableClass: "accounts_list",
+ Header: []template.HTML{"ID", "Status", "Contact", "Agreement", "Initial IP", "Created"},
+ },
+ Rows: []Account{},
+ }
- for rows.Next() {
- row := Account{}
- err = rows.Scan(&row.Id, &row.Status, &row.Contact, &row.Agreement, &row.InitialIp, &row.CreatedAt)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountList{}, err
- }
- Accounts.Rows = append(Accounts.Rows, row)
- }
+ for rows.Next() {
+ row := Account{}
+ err = rows.Scan(&row.Id, &row.Status, &row.Contact, &row.Agreement, &row.InitialIp, &row.CreatedAt)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountList{}, err
+ }
+ Accounts.Rows = append(Accounts.Rows, row)
+ }
- return Accounts, nil
+ return Accounts, nil
}
type Order struct {
- Id int
- RegistrationId int
- Expires string
- CertSerial string
- BeganProc bool
- Created string
+ Id int
+ RegistrationId int
+ Expires string
+ CertSerial string
+ BeganProc bool
+ Created string
}
type OrderList struct {
- BaseList
- Rows []Order
+ BaseList
+ Rows []Order
}
type NameValue struct {
- Name string
- Value string
+ Name string
+ Value string
}
type BaseShow struct {
- Title string
- TableClass string
- Rows []NameValue
- Links []NameValHtml
- Extra []template.HTML
+ Title string
+ TableClass string
+ Rows []NameValue
+ Links []NameValHtml
+ Extra []template.HTML
}
type AccountShow struct {
- BaseShow
- Related []CertificateList
- Related2 []OrderList
+ BaseShow
+ Related []CertificateList
+ Related2 []OrderList
}
func GetAccount(w http.ResponseWriter, r *http.Request, id int) (AccountShow, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountShow{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountShow{}, err
+ }
- defer db.Close()
+ defer db.Close()
- rows, err := db.Query("SELECT c.id, c.registrationID, c.serial, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, c.issued, c.expires FROM certificates c JOIN certificateStatus cs ON cs.id = c.id WHERE registrationID=?", strconv.Itoa(id))
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountShow{}, err
- }
+ rows, err := db.Query("SELECT c.id, c.registrationID, c.serial, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, c.issued, c.expires FROM certificates c JOIN certificateStatus cs ON cs.id = c.id WHERE registrationID=?", strconv.Itoa(id))
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountShow{}, err
+ }
- Certificates := CertificateList{
- BaseList: BaseList{
- Title: "Certificates",
- TableClass: "rel_certificates_list",
- Header: []template.HTML{"ID", "Account ID", "Serial", "Status", "Issued", "Expires"},
- },
- Rows: []Certificate{},
- }
+ Certificates := CertificateList{
+ BaseList: BaseList{
+ Title: "Certificates",
+ TableClass: "rel_certificates_list",
+ Header: []template.HTML{"ID", "Account ID", "Serial", "Status", "Issued", "Expires"},
+ },
+ Rows: []Certificate{},
+ }
- for rows.Next() {
- row := Certificate{}
- err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Status, &row.Issued, &row.Expires)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountShow{}, err
- }
- Certificates.Rows = append(Certificates.Rows, row)
- }
+ for rows.Next() {
+ row := Certificate{}
+ err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Status, &row.Issued, &row.Expires)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountShow{}, err
+ }
+ Certificates.Rows = append(Certificates.Rows, row)
+ }
- rows, err = db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders WHERE registrationID=?", strconv.Itoa(id))
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountShow{}, err
- }
+ rows, err = db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders WHERE registrationID=?", strconv.Itoa(id))
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountShow{}, err
+ }
- Orders := OrderList{
- BaseList: BaseList{
- Title: "Orders",
- TableClass: "rel_orders_list",
- Header: []template.HTML{"ID", "Account ID", "Expires", "Certificate Serial", "Began Processing?", "Created"},
- },
- Rows: []Order{},
- }
+ Orders := OrderList{
+ BaseList: BaseList{
+ Title: "Orders",
+ TableClass: "rel_orders_list",
+ Header: []template.HTML{"ID", "Account ID", "Expires", "Certificate Serial", "Began Processing?", "Created"},
+ },
+ Rows: []Order{},
+ }
- for rows.Next() {
- row := Order{}
- err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountShow{}, err
- }
- Orders.Rows = append(Orders.Rows, row)
- }
+ for rows.Next() {
+ row := Order{}
+ err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountShow{}, err
+ }
+ Orders.Rows = append(Orders.Rows, row)
+ }
- rows, err = db.Query("SELECT id, status, contact, agreement, initialIP, createdAt FROM registrations WHERE id=?", strconv.Itoa(id))
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountShow{}, err
- }
+ rows, err = db.Query("SELECT id, status, contact, agreement, initialIP, createdAt FROM registrations WHERE id=?", strconv.Itoa(id))
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountShow{}, err
+ }
- AccountDetails := AccountShow{
- BaseShow: BaseShow{
- Title: "Account",
- TableClass: "account_show",
- Rows: []NameValue{},
- },
- Related: []CertificateList{Certificates},
- Related2: []OrderList{Orders},
- }
+ AccountDetails := AccountShow{
+ BaseShow: BaseShow{
+ Title: "Account",
+ TableClass: "account_show",
+ Rows: []NameValue{},
+ },
+ Related: []CertificateList{Certificates},
+ Related2: []OrderList{Orders},
+ }
- for rows.Next() {
- row := Account{}
- err = rows.Scan(&row.Id, &row.Status, &row.Contact, &row.Agreement, &row.InitialIp, &row.CreatedAt)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AccountShow{}, err
- }
- AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
- AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Status", row.Status})
- AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Contact", row.Contact})
- AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Agreement", row.Agreement})
- AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Initial IP", row.InitialIp.String()})
- AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Created At", row.CreatedAt})
- }
+ for rows.Next() {
+ row := Account{}
+ err = rows.Scan(&row.Id, &row.Status, &row.Contact, &row.Agreement, &row.InitialIp, &row.CreatedAt)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AccountShow{}, err
+ }
+ AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
+ AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Status", row.Status})
+ AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Contact", row.Contact})
+ AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Agreement", row.Agreement})
+ AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Initial IP", row.InitialIp.String()})
+ AccountDetails.Rows = append(AccountDetails.Rows, NameValue{"Created At", row.CreatedAt})
+ }
- return AccountDetails, nil
+ return AccountDetails, nil
}
func GetOrders(w http.ResponseWriter, r *http.Request) (OrderList, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderList{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderList{}, err
+ }
- defer db.Close()
+ defer db.Close()
- rows, err := db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderList{}, err
- }
+ rows, err := db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderList{}, err
+ }
- Orders := OrderList{
- BaseList: BaseList{
- Title: "Orders",
- TableClass: "orders_list",
- Header: []template.HTML{"ID", "Account ID", "Expires", "Certificate Serial", "Began Processing?", "Created"},
- },
- Rows: []Order{},
- }
+ Orders := OrderList{
+ BaseList: BaseList{
+ Title: "Orders",
+ TableClass: "orders_list",
+ Header: []template.HTML{"ID", "Account ID", "Expires", "Certificate Serial", "Began Processing?", "Created"},
+ },
+ Rows: []Order{},
+ }
- for rows.Next() {
- row := Order{}
- err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderList{}, err
- }
- Orders.Rows = append(Orders.Rows, row)
- }
+ for rows.Next() {
+ row := Order{}
+ err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderList{}, err
+ }
+ Orders.Rows = append(Orders.Rows, row)
+ }
- return Orders, nil
+ return Orders, nil
}
type Auth struct {
- Id string
- Identifier string
- RegistrationId int
- Status string
- Expires string
- Combinations string
+ Id string
+ Identifier string
+ RegistrationId int
+ Status string
+ Expires string
+ Combinations string
}
type AuthList struct {
- BaseList
- Rows []Auth
+ BaseList
+ Rows []Auth
}
type OrderShow struct {
- BaseShow
- Related []AuthList
- Related2 []BaseList
+ BaseShow
+ Related []AuthList
+ Related2 []BaseList
}
func GetOrder(w http.ResponseWriter, r *http.Request, id int) (OrderShow, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderShow{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderShow{}, err
+ }
- defer db.Close()
+ defer db.Close()
- partial := "SELECT id, identifier, registrationID, status, expires, combinations FROM "
- where := " WHERE id IN (SELECT authzID FROM orderToAuthz WHERE orderID=?)"
- rows, err := db.Query(partial+"authz"+where+" UNION "+partial+"pendingAuthorizations"+where, strconv.Itoa(id), strconv.Itoa(id))
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderShow{}, err
- }
+ partial := "SELECT id, identifier, registrationID, status, expires, combinations FROM "
+ where := " WHERE id IN (SELECT authzID FROM orderToAuthz WHERE orderID=?)"
+ rows, err := db.Query(partial+"authz"+where+" UNION "+partial+"pendingAuthorizations"+where, strconv.Itoa(id), strconv.Itoa(id))
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderShow{}, err
+ }
- Authz := AuthList{
- BaseList: BaseList{
- Title: "Authorizations",
- TableClass: "rel_authz_list",
- Header: []template.HTML{"ID", "Identifier", "Account ID", "Status", "Expires", "Combinations"},
- },
- Rows: []Auth{},
- }
+ Authz := AuthList{
+ BaseList: BaseList{
+ Title: "Authorizations",
+ TableClass: "rel_authz_list",
+ Header: []template.HTML{"ID", "Identifier", "Account ID", "Status", "Expires", "Combinations"},
+ },
+ Rows: []Auth{},
+ }
- for rows.Next() {
- row := Auth{}
- err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderShow{}, err
- }
- Authz.Rows = append(Authz.Rows, row)
- }
+ for rows.Next() {
+ row := Auth{}
+ err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderShow{}, err
+ }
+ Authz.Rows = append(Authz.Rows, row)
+ }
- rows, err = db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders WHERE id=?", strconv.Itoa(id))
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderShow{}, err
- }
+ rows, err = db.Query("SELECT id, registrationID, expires, certificateSerial, beganProcessing, created FROM orders WHERE id=?", strconv.Itoa(id))
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderShow{}, err
+ }
- OrderDetails := OrderShow{
- BaseShow: BaseShow{
- Title: "Order",
- TableClass: "order_show",
- Rows: []NameValue{},
- Links: []NameValHtml{},
- },
- Related: []AuthList{Authz},
- }
+ OrderDetails := OrderShow{
+ BaseShow: BaseShow{
+ Title: "Order",
+ TableClass: "order_show",
+ Rows: []NameValue{},
+ Links: []NameValHtml{},
+ },
+ Related: []AuthList{Authz},
+ }
- for rows.Next() {
- row := Order{}
- err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return OrderShow{}, err
- }
- OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
- OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Expires", row.Expires})
- v := "false"
- if row.BeganProc {
- v = "true"
- }
- OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Began Processing?", v})
- OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Created", row.Created})
+ for rows.Next() {
+ row := Order{}
+ err = rows.Scan(&row.Id, &row.RegistrationId, &row.Expires, &row.CertSerial, &row.BeganProc, &row.Created)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return OrderShow{}, err
+ }
+ OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
+ OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Expires", row.Expires})
+ v := "false"
+ if row.BeganProc {
+ v = "true"
+ }
+ OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Began Processing?", v})
+ OrderDetails.Rows = append(OrderDetails.Rows, NameValue{"Created", row.Created})
- OrderDetails.Links = append(OrderDetails.Links, NameValHtml{"Certificate", template.HTML("" + row.CertSerial + " ")})
- OrderDetails.Links = append(OrderDetails.Links, NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + " ")})
- }
+ OrderDetails.Links = append(OrderDetails.Links, NameValHtml{"Certificate", template.HTML("" + row.CertSerial + " ")})
+ OrderDetails.Links = append(OrderDetails.Links, NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + " ")})
+ }
- return OrderDetails, nil
+ return OrderDetails, nil
}
func GetAuthz(w http.ResponseWriter, r *http.Request) (AuthList, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthList{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthList{}, err
+ }
- defer db.Close()
+ defer db.Close()
- rows, err := db.Query("SELECT id, identifier, registrationID, status, expires, combinations FROM authz UNION SELECT id, identifier, registrationID, status, expires, combinations FROM pendingAuthorizations")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthList{}, err
- }
+ rows, err := db.Query("SELECT id, identifier, registrationID, status, expires, combinations FROM authz UNION SELECT id, identifier, registrationID, status, expires, combinations FROM pendingAuthorizations")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthList{}, err
+ }
- Authz := AuthList{
- BaseList: BaseList{
- Title: "Authorizations",
- TableClass: "authz_list",
- Header: []template.HTML{"ID", "Identifier", "Account ID", "Status", "Expires", "Combinations"},
- },
- Rows: []Auth{},
- }
+ Authz := AuthList{
+ BaseList: BaseList{
+ Title: "Authorizations",
+ TableClass: "authz_list",
+ Header: []template.HTML{"ID", "Identifier", "Account ID", "Status", "Expires", "Combinations"},
+ },
+ Rows: []Auth{},
+ }
- for rows.Next() {
- row := Auth{}
- err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthList{}, err
- }
- Authz.Rows = append(Authz.Rows, row)
- }
+ for rows.Next() {
+ row := Auth{}
+ err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthList{}, err
+ }
+ Authz.Rows = append(Authz.Rows, row)
+ }
- return Authz, nil
+ return Authz, nil
}
type Challenge struct {
- Id int
- AuthId string
- Type string
- Status string
- Validated string
- Token string
- KeyAuth string
+ Id int
+ AuthId string
+ Type string
+ Status string
+ Validated string
+ Token string
+ KeyAuth string
}
type ChallengeList struct {
- BaseList
- Rows []Challenge
+ BaseList
+ Rows []Challenge
}
type NameValHtml struct {
- Name string
- Value template.HTML
+ Name string
+ Value template.HTML
}
type AuthShow struct {
- BaseShow
- Related []ChallengeList
- Related2 []BaseList
+ BaseShow
+ Related []ChallengeList
+ Related2 []BaseList
}
func GetAuth(w http.ResponseWriter, r *http.Request, id string) (AuthShow, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthShow{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthShow{}, err
+ }
- defer db.Close()
+ defer db.Close()
- rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges WHERE authorizationID=?", id)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthShow{}, err
- }
+ rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges WHERE authorizationID=?", id)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthShow{}, err
+ }
- Challenges := ChallengeList{
- BaseList: BaseList{
- Title: "Challenges",
- TableClass: "rel_challenges_list",
- Header: []template.HTML{"ID", "Authorization ID", "Type", "Status", "Validated", "Token", "Key Authorization"},
- },
- Rows: []Challenge{},
- }
+ Challenges := ChallengeList{
+ BaseList: BaseList{
+ Title: "Challenges",
+ TableClass: "rel_challenges_list",
+ Header: []template.HTML{"ID", "Authorization ID", "Type", "Status", "Validated", "Token", "Key Authorization"},
+ },
+ Rows: []Challenge{},
+ }
- for rows.Next() {
- row := Challenge{}
- err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthShow{}, err
- }
- Challenges.Rows = append(Challenges.Rows, row)
- }
+ for rows.Next() {
+ row := Challenge{}
+ err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthShow{}, err
+ }
+ Challenges.Rows = append(Challenges.Rows, row)
+ }
- partial := "SELECT id, identifier, registrationID, status, expires, combinations FROM "
- where := " WHERE id IN (SELECT authzID FROM orderToAuthz WHERE id=?)"
- rows, err = db.Query(partial+"authz"+where+" UNION "+partial+"pendingAuthorizations"+where, id, id)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthShow{}, err
- }
+ partial := "SELECT id, identifier, registrationID, status, expires, combinations FROM "
+ where := " WHERE id IN (SELECT authzID FROM orderToAuthz WHERE id=?)"
+ rows, err = db.Query(partial+"authz"+where+" UNION "+partial+"pendingAuthorizations"+where, id, id)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthShow{}, err
+ }
- AuthDetails := AuthShow{
- BaseShow: BaseShow{
- Title: "Authorization",
- TableClass: "auth_show",
- Rows: []NameValue{},
- Links: []NameValHtml{},
- },
- Related: []ChallengeList{Challenges},
- }
+ AuthDetails := AuthShow{
+ BaseShow: BaseShow{
+ Title: "Authorization",
+ TableClass: "auth_show",
+ Rows: []NameValue{},
+ Links: []NameValHtml{},
+ },
+ Related: []ChallengeList{Challenges},
+ }
- for rows.Next() {
- row := Auth{}
- err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return AuthShow{}, err
- }
- AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"ID", row.Id})
- AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Identifier", row.Identifier})
- AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Status", row.Status})
- AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Expires", row.Expires})
- AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Combinations", row.Combinations})
+ for rows.Next() {
+ row := Auth{}
+ err = rows.Scan(&row.Id, &row.Identifier, &row.RegistrationId, &row.Status, &row.Expires, &row.Combinations)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return AuthShow{}, err
+ }
+ AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"ID", row.Id})
+ AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Identifier", row.Identifier})
+ AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Status", row.Status})
+ AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Expires", row.Expires})
+ AuthDetails.Rows = append(AuthDetails.Rows, NameValue{"Combinations", row.Combinations})
- Link := NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + " ")}
- AuthDetails.Links = append(AuthDetails.Links, Link)
- }
+ Link := NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + " ")}
+ AuthDetails.Links = append(AuthDetails.Links, Link)
+ }
- return AuthDetails, nil
+ return AuthDetails, nil
}
func GetChallenges(w http.ResponseWriter, r *http.Request) (ChallengeList, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ChallengeList{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ChallengeList{}, err
+ }
- defer db.Close()
+ defer db.Close()
- rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ChallengeList{}, err
- }
+ rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ChallengeList{}, err
+ }
- Challenges := ChallengeList{
- BaseList: BaseList{
- Title: "Challenges",
- TableClass: "challenges_list",
- Header: []template.HTML{"ID", "Authorization ID", "Type", "Status", "Validated", "Token", "Key Authorization"},
- },
- Rows: []Challenge{},
- }
+ Challenges := ChallengeList{
+ BaseList: BaseList{
+ Title: "Challenges",
+ TableClass: "challenges_list",
+ Header: []template.HTML{"ID", "Authorization ID", "Type", "Status", "Validated", "Token", "Key Authorization"},
+ },
+ Rows: []Challenge{},
+ }
- for rows.Next() {
- row := Challenge{}
- err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ChallengeList{}, err
- }
- Challenges.Rows = append(Challenges.Rows, row)
- }
+ for rows.Next() {
+ row := Challenge{}
+ err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ChallengeList{}, err
+ }
+ Challenges.Rows = append(Challenges.Rows, row)
+ }
- return Challenges, nil
+ return Challenges, nil
}
type ChallengeShow struct {
- BaseShow
- Related []ChallengeList
- Related2 []BaseList
+ BaseShow
+ Related []ChallengeList
+ Related2 []BaseList
}
func GetChallenge(w http.ResponseWriter, r *http.Request, id int) (ChallengeShow, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ChallengeShow{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ChallengeShow{}, err
+ }
- defer db.Close()
+ defer db.Close()
- rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges WHERE id=?", id)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ChallengeShow{}, err
- }
+ rows, err := db.Query("SELECT id, authorizationID, type, status, validated, token, keyAuthorization FROM challenges WHERE id=?", id)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ChallengeShow{}, err
+ }
- ChallengeDetails := ChallengeShow{
- BaseShow: BaseShow{
- Title: "Challenge",
- TableClass: "challenge_show",
- Rows: []NameValue{},
- Links: []NameValHtml{},
- },
- Related: []ChallengeList{},
- }
+ ChallengeDetails := ChallengeShow{
+ BaseShow: BaseShow{
+ Title: "Challenge",
+ TableClass: "challenge_show",
+ Rows: []NameValue{},
+ Links: []NameValHtml{},
+ },
+ Related: []ChallengeList{},
+ }
- for rows.Next() {
- row := Challenge{}
- err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ChallengeShow{}, err
- }
- ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
- ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Type", row.Type})
- ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Status", row.Status})
- ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Validated", row.Validated})
- ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Token", row.Token})
- ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"KeyAuth", row.KeyAuth})
+ for rows.Next() {
+ row := Challenge{}
+ err = rows.Scan(&row.Id, &row.AuthId, &row.Type, &row.Status, &row.Validated, &row.Token, &row.KeyAuth)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ChallengeShow{}, err
+ }
+ ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
+ ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Type", row.Type})
+ ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Status", row.Status})
+ ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Validated", row.Validated})
+ ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"Token", row.Token})
+ ChallengeDetails.Rows = append(ChallengeDetails.Rows, NameValue{"KeyAuth", row.KeyAuth})
- Link := NameValHtml{"Authorization", template.HTML("" + row.AuthId + " ")}
- ChallengeDetails.Links = append(ChallengeDetails.Links, Link)
- }
+ Link := NameValHtml{"Authorization", template.HTML("" + row.AuthId + " ")}
+ ChallengeDetails.Links = append(ChallengeDetails.Links, Link)
+ }
- return ChallengeDetails, nil
+ return ChallengeDetails, nil
}
type Certificate struct {
- Id int
- RegistrationId int
- Serial string
- Status string
- Issued string
- Expires string
+ Id int
+ RegistrationId int
+ Serial string
+ Status string
+ Issued string
+ Expires string
}
type CertificateList struct {
- BaseList
- Rows []Certificate
+ BaseList
+ Rows []Certificate
}
func GetCertificates(w http.ResponseWriter, r *http.Request) (CertificateList, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return CertificateList{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return CertificateList{}, err
+ }
- defer db.Close()
+ defer db.Close()
- where := ""
- if r.URL.Query().Get("active") != "" {
- where = " WHERE cs.revokedDate='0000-00-00 00:00:00' AND cs.notAfter >= NOW()";
- } else if r.URL.Query().Get("expired") != "" {
- where = " WHERE cs.revokedDate='0000-00-00 00:00:00' AND cs.notAfter < NOW()";
- } else if r.URL.Query().Get("revoked") != "" {
- where = " WHERE cs.revokedDate<>'0000-00-00 00:00:00'";
- }
+ where := ""
+ if r.URL.Query().Get("active") != "" {
+ where = " WHERE cs.revokedDate='0000-00-00 00:00:00' AND cs.notAfter >= NOW()"
+ } else if r.URL.Query().Get("expired") != "" {
+ where = " WHERE cs.revokedDate='0000-00-00 00:00:00' AND cs.notAfter < NOW()"
+ } else if r.URL.Query().Get("revoked") != "" {
+ where = " WHERE cs.revokedDate<>'0000-00-00 00:00:00'"
+ }
- rows, err := db.Query("SELECT c.id, c.registrationID, c.serial, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, c.issued, c.expires FROM certificates c JOIN certificateStatus cs ON cs.id = c.id" + where)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return CertificateList{}, err
- }
+ rows, err := db.Query("SELECT c.id, c.registrationID, c.serial, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, c.issued, c.expires FROM certificates c JOIN certificateStatus cs ON cs.id = c.id" + where)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return CertificateList{}, err
+ }
- Certificates := CertificateList{
- BaseList: BaseList{
- Title: "Certificates",
- TableClass: "certificates_list",
- Header: []template.HTML{"ID", "Account ID", "Serial", "Status", "Issued", "Expires"},
- },
- Rows: []Certificate{},
- }
+ Certificates := CertificateList{
+ BaseList: BaseList{
+ Title: "Certificates",
+ TableClass: "certificates_list",
+ Header: []template.HTML{"ID", "Account ID", "Serial", "Status", "Issued", "Expires"},
+ },
+ Rows: []Certificate{},
+ }
- for rows.Next() {
- row := Certificate{}
- err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Status, &row.Issued, &row.Expires)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return CertificateList{}, err
- }
- Certificates.Rows = append(Certificates.Rows, row)
- }
+ for rows.Next() {
+ row := Certificate{}
+ err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Status, &row.Issued, &row.Expires)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return CertificateList{}, err
+ }
+ Certificates.Rows = append(Certificates.Rows, row)
+ }
- return Certificates, nil
+ return Certificates, nil
}
type CertificateShow struct {
- BaseShow
- Related []BaseList
- Related2 []BaseList
+ BaseShow
+ Related []BaseList
+ Related2 []BaseList
}
type CertificateExtra struct {
- Id int
- RegistrationId int
- Serial string
- Digest string
- Issued string
- Expires string
- SubscriberApproved bool
- Status string
- OCSPLastUpdate string
- Revoked string
- RevokedReason int
- LastNagSent string
- NotAfter string
- IsExpired bool
+ Id int
+ RegistrationId int
+ Serial string
+ Digest string
+ Issued string
+ Expires string
+ SubscriberApproved bool
+ Status string
+ OCSPLastUpdate string
+ Revoked string
+ RevokedReason int
+ LastNagSent string
+ NotAfter string
+ IsExpired bool
}
func GetCertificate(w http.ResponseWriter, r *http.Request, id int, serial string) (CertificateShow, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return CertificateShow{}, err
- }
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return CertificateShow{}, err
+ }
- defer db.Close()
+ defer db.Close()
- var rows *sql.Rows
- selectWhere := "SELECT c.id, c.registrationID, c.serial, c.digest, c.issued, c.expires, cs.subscriberApproved, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, cs.ocspLastUpdated, cs.revokedDate, cs.revokedReason, cs.lastExpirationNagSent, cs.notAfter, cs.isExpired FROM certificates c JOIN certificateStatus cs ON cs.id = c.id WHERE "
+ var rows *sql.Rows
+ selectWhere := "SELECT c.id, c.registrationID, c.serial, c.digest, c.issued, c.expires, cs.subscriberApproved, CASE WHEN cs.notAfter < NOW() THEN 'expired' ELSE cs.status END AS status, cs.ocspLastUpdated, cs.revokedDate, cs.revokedReason, cs.lastExpirationNagSent, cs.notAfter, cs.isExpired FROM certificates c JOIN certificateStatus cs ON cs.id = c.id WHERE "
- if serial != "" {
- rows, err = db.Query(selectWhere+"c.serial=?", serial)
- } else {
- rows, err = db.Query(selectWhere+"c.id=?", id)
- }
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return CertificateShow{}, err
- }
+ if serial != "" {
+ rows, err = db.Query(selectWhere+"c.serial=?", serial)
+ } else {
+ rows, err = db.Query(selectWhere+"c.id=?", id)
+ }
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return CertificateShow{}, err
+ }
- CertificateDetails := CertificateShow{
- BaseShow: BaseShow{
- Title: "Certificate",
- TableClass: "certificate_show",
- Rows: []NameValue{},
- Links: []NameValHtml{},
- },
- }
+ CertificateDetails := CertificateShow{
+ BaseShow: BaseShow{
+ Title: "Certificate",
+ TableClass: "certificate_show",
+ Rows: []NameValue{},
+ Links: []NameValHtml{},
+ },
+ }
- for rows.Next() {
- row := CertificateExtra{}
- err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Digest, &row.Issued, &row.Expires, &row.SubscriberApproved, &row.Status, &row.OCSPLastUpdate, &row.Revoked, &row.RevokedReason, &row.LastNagSent, &row.NotAfter, &row.IsExpired)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return CertificateShow{}, err
- }
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Serial", row.Serial})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Digest", row.Digest})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Issued", row.Issued})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Expires", row.Expires})
- v := "false"
- if row.SubscriberApproved {
- v = "true"
- }
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Subscriber Approved", v})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Status", row.Status})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"OCSP Last Update", row.OCSPLastUpdate})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked", row.Revoked})
- reasonText := ""
- switch row.RevokedReason {
- case 0:
- if row.Revoked != "0000-00-00 00:00:00" {
- reasonText = " - Unspecified"
- }
- case 1:
- reasonText = " - Key Compromise"
- case 2:
- reasonText = " - CA Compromise"
- case 3:
- reasonText = " - Affiliation Changed"
- case 4:
- reasonText = " - Superseded"
- case 5:
- reasonText = " - Cessation Of Operation"
- case 6:
- reasonText = " - Certificate Hold"
- case 8:
- reasonText = " - Remove From CRL"
- case 9:
- reasonText = " - Privilege Withdrawn"
- case 10:
- reasonText = " - AA Compromise"
- }
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked Reason", strconv.Itoa(row.RevokedReason) + reasonText})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Last Expiration Nag Sent", row.LastNagSent})
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Not After", row.NotAfter})
- v = "false"
- if row.IsExpired {
- v = "true"
- }
- CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Is Expired", v})
+ for rows.Next() {
+ row := CertificateExtra{}
+ err = rows.Scan(&row.Id, &row.RegistrationId, &row.Serial, &row.Digest, &row.Issued, &row.Expires, &row.SubscriberApproved, &row.Status, &row.OCSPLastUpdate, &row.Revoked, &row.RevokedReason, &row.LastNagSent, &row.NotAfter, &row.IsExpired)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return CertificateShow{}, err
+ }
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"ID", strconv.Itoa(row.Id)})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Serial", row.Serial})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Digest", row.Digest})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Issued", row.Issued})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Expires", row.Expires})
+ v := "false"
+ if row.SubscriberApproved {
+ v = "true"
+ }
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Subscriber Approved", v})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Status", row.Status})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"OCSP Last Update", row.OCSPLastUpdate})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked", row.Revoked})
+ reasonText := ""
+ switch row.RevokedReason {
+ case 0:
+ if row.Revoked != "0000-00-00 00:00:00" {
+ reasonText = " - Unspecified"
+ }
+ case 1:
+ reasonText = " - Key Compromise"
+ case 2:
+ reasonText = " - CA Compromise"
+ case 3:
+ reasonText = " - Affiliation Changed"
+ case 4:
+ reasonText = " - Superseded"
+ case 5:
+ reasonText = " - Cessation Of Operation"
+ case 6:
+ reasonText = " - Certificate Hold"
+ case 8:
+ reasonText = " - Remove From CRL"
+ case 9:
+ reasonText = " - Privilege Withdrawn"
+ case 10:
+ reasonText = " - AA Compromise"
+ }
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked Reason", strconv.Itoa(row.RevokedReason) + reasonText})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Last Expiration Nag Sent", row.LastNagSent})
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Not After", row.NotAfter})
+ v = "false"
+ if row.IsExpired {
+ v = "true"
+ }
+ CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Is Expired", v})
- Link := NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + " ")}
- CertificateDetails.Links = append(CertificateDetails.Links, Link)
+ Link := NameValHtml{"Account", template.HTML("" + strconv.Itoa(row.RegistrationId) + " ")}
+ CertificateDetails.Links = append(CertificateDetails.Links, Link)
- if row.Revoked == "0000-00-00 00:00:00" {
- revokeHtml, err := tmpls.RenderSingle("views/revoke-partial.tmpl", struct{ Serial string }{Serial: row.Serial})
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return CertificateShow{}, err
- }
- CertificateDetails.Extra = append(CertificateDetails.Extra, template.HTML(revokeHtml))
- }
- }
+ if row.Revoked == "0000-00-00 00:00:00" {
+ revokeHtml, err := tmpls.RenderSingle("views/revoke-partial.tmpl", struct{ Serial string }{Serial: row.Serial})
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return CertificateShow{}, err
+ }
+ CertificateDetails.Extra = append(CertificateDetails.Extra, template.HTML(revokeHtml))
+ }
+ }
- return CertificateDetails, nil
+ return CertificateDetails, nil
}
-
diff --git a/gui/certificate.go b/gui/certificate.go
index e92b4e4..94bd441 100644
--- a/gui/certificate.go
+++ b/gui/certificate.go
@@ -1,485 +1,485 @@
package main
import (
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "mime/multipart"
- "os"
- "os/exec"
- "path/filepath"
- "runtime/debug"
- "strings"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime/debug"
+ "strings"
)
type CertificateInfo struct {
- IsRoot bool
- KeyTypes map[string]string
- KeyType string
- CreateType string
+ IsRoot bool
+ KeyTypes map[string]string
+ KeyType string
+ CreateType string
- Country string
- Organization string
- OrgUnit string
- CommonName string
+ Country string
+ Organization string
+ OrgUnit string
+ CommonName string
- ImportFile multipart.File
- ImportHandler *multipart.FileHeader
- ImportPwd string
+ ImportFile multipart.File
+ ImportHandler *multipart.FileHeader
+ ImportPwd string
- Key string
- Passphrase string
- Certificate string
+ Key string
+ Passphrase string
+ Certificate string
- RequestBase string
- Errors map[string]string
+ RequestBase string
+ Errors map[string]string
}
func (ci *CertificateInfo) Initialize() {
- ci.Errors = make(map[string]string)
+ ci.Errors = make(map[string]string)
- ci.KeyTypes = make(map[string]string)
- ci.KeyTypes["rsa4096"] = "RSA-4096"
- ci.KeyTypes["rsa3072"] = "RSA-3072"
- ci.KeyTypes["rsa2048"] = "RSA-2048"
- ci.KeyTypes["ecdsa384"] = "ECDSA-384"
- ci.KeyTypes["ecdsa256"] = "ECDSA-256"
+ ci.KeyTypes = make(map[string]string)
+ ci.KeyTypes["rsa4096"] = "RSA-4096"
+ ci.KeyTypes["rsa3072"] = "RSA-3072"
+ ci.KeyTypes["rsa2048"] = "RSA-2048"
+ ci.KeyTypes["ecdsa384"] = "ECDSA-384"
+ ci.KeyTypes["ecdsa256"] = "ECDSA-256"
- ci.KeyType = "rsa4096"
+ ci.KeyType = "rsa4096"
}
func (ci *CertificateInfo) Validate() bool {
- ci.Errors = make(map[string]string)
+ ci.Errors = make(map[string]string)
- if ci.CreateType == "generate" {
- if strings.TrimSpace(ci.KeyType) == "" || strings.TrimSpace(ci.KeyTypes[ci.KeyType]) == "" {
- ci.Errors["KeyType"] = "Please select a key type/size"
- }
- if strings.TrimSpace(ci.Country) == "" || len(ci.Country) < 2 {
- ci.Errors["Country"] = "Please enter a valid 2-character country code"
- }
- if strings.TrimSpace(ci.Organization) == "" {
- ci.Errors["Organization"] = "Please enter an organization name"
- }
- if strings.TrimSpace(ci.CommonName) == "" {
- ci.Errors["CommonName"] = "Please enter a common name"
- }
- }
+ if ci.CreateType == "generate" {
+ if strings.TrimSpace(ci.KeyType) == "" || strings.TrimSpace(ci.KeyTypes[ci.KeyType]) == "" {
+ ci.Errors["KeyType"] = "Please select a key type/size"
+ }
+ if strings.TrimSpace(ci.Country) == "" || len(ci.Country) < 2 {
+ ci.Errors["Country"] = "Please enter a valid 2-character country code"
+ }
+ if strings.TrimSpace(ci.Organization) == "" {
+ ci.Errors["Organization"] = "Please enter an organization name"
+ }
+ if strings.TrimSpace(ci.CommonName) == "" {
+ ci.Errors["CommonName"] = "Please enter a common name"
+ }
+ }
- if (ci.CreateType == "import") && (ci.ImportHandler != nil) {
- ext := ci.ImportHandler.Filename[len(ci.ImportHandler.Filename)-4:]
- if (ci.ImportHandler.Size == 0) || (ext != ".zip" && ext != ".pfx") {
- ci.Errors["Import"] = "Please provide a bundle (.pfx or .zip) with a key and certificate"
- }
- }
+ if (ci.CreateType == "import") && (ci.ImportHandler != nil) {
+ ext := ci.ImportHandler.Filename[len(ci.ImportHandler.Filename)-4:]
+ if (ci.ImportHandler.Size == 0) || (ext != ".zip" && ext != ".pfx") {
+ ci.Errors["Import"] = "Please provide a bundle (.pfx or .zip) with a key and certificate"
+ }
+ }
- if ci.CreateType == "upload" {
- if strings.TrimSpace(ci.Key) == "" {
- ci.Errors["Key"] = "Please provide a PEM-encoded key"
- }
- if strings.TrimSpace(ci.Certificate) == "" {
- ci.Errors["Certificate"] = "Please provide a PEM-encoded certificate"
- }
- }
+ if ci.CreateType == "upload" {
+ if strings.TrimSpace(ci.Key) == "" {
+ ci.Errors["Key"] = "Please provide a PEM-encoded key"
+ }
+ if strings.TrimSpace(ci.Certificate) == "" {
+ ci.Errors["Certificate"] = "Please provide a PEM-encoded certificate"
+ }
+ }
- return len(ci.Errors) == 0
+ return len(ci.Errors) == 0
}
func reportError(err error) error {
- lines := strings.Split(string(debug.Stack()), "\n")
- if len(lines) >= 5 {
- lines = append(lines[:0], lines[5:]...)
- }
+ lines := strings.Split(string(debug.Stack()), "\n")
+ if len(lines) >= 5 {
+ lines = append(lines[:0], lines[5:]...)
+ }
- stop := len(lines)
- for i := 0; i < len(lines); i++ {
- if strings.Index(lines[i], ".ServeHTTP(") >= 0 {
- stop = i
- break
- }
- }
- lines = lines[:stop]
- lines = append(lines, "...")
+ stop := len(lines)
+ for i := 0; i < len(lines); i++ {
+ if strings.Index(lines[i], ".ServeHTTP(") >= 0 {
+ stop = i
+ break
+ }
+ }
+ lines = lines[:stop]
+ lines = append(lines, "...")
- fmt.Println(strings.Join(lines, "\n"))
+ fmt.Println(strings.Join(lines, "\n"))
- return errors.New("Error (" + err.Error() + ")! See LabCA logs for details")
+ return errors.New("Error (" + err.Error() + ")! See LabCA logs for details")
}
func preCreateTasks(path string) error {
- if _, err := exe_cmd("touch " + path + "index.txt"); err != nil {
- return reportError(err)
- }
- if _, err := exe_cmd("touch " + path + "index.txt.attr"); err != nil {
- return reportError(err)
- }
+ if _, err := exe_cmd("touch " + path + "index.txt"); err != nil {
+ return reportError(err)
+ }
+ if _, err := exe_cmd("touch " + path + "index.txt.attr"); err != nil {
+ return reportError(err)
+ }
- if _, err := os.Stat(path + "serial"); os.IsNotExist(err) {
- if err := ioutil.WriteFile(path+"serial", []byte("1000\n"), 0644); err != nil {
- return err
- }
- }
- if _, err := os.Stat(path + "crlnumber"); os.IsNotExist(err) {
- if err = ioutil.WriteFile(path+"crlnumber", []byte("1000\n"), 0644); err != nil {
- return err
- }
- }
+ if _, err := os.Stat(path + "serial"); os.IsNotExist(err) {
+ if err := ioutil.WriteFile(path+"serial", []byte("1000\n"), 0644); err != nil {
+ return err
+ }
+ }
+ if _, err := os.Stat(path + "crlnumber"); os.IsNotExist(err) {
+ if err = ioutil.WriteFile(path+"crlnumber", []byte("1000\n"), 0644); err != nil {
+ return err
+ }
+ }
- if _, err := exe_cmd("mkdir -p " + path + "certs"); err != nil {
- return reportError(err)
- }
+ if _, err := exe_cmd("mkdir -p " + path + "certs"); err != nil {
+ return reportError(err)
+ }
- return nil
+ return nil
}
func (ci *CertificateInfo) Create(path string, certBase string) error {
- if err := preCreateTasks(path); err != nil {
- return err
- }
+ if err := preCreateTasks(path); err != nil {
+ return err
+ }
- tmpDir, err := ioutil.TempDir("", "labca")
- if err != nil {
- return err
- }
+ tmpDir, err := ioutil.TempDir("", "labca")
+ if err != nil {
+ return err
+ }
- defer os.RemoveAll(tmpDir)
+ defer os.RemoveAll(tmpDir)
- var tmpKey string
- var tmpCert string
- if ci.IsRoot {
- tmpKey = filepath.Join(tmpDir, "root-ca.key")
- tmpCert = filepath.Join(tmpDir, "root-ca.pem")
- } else {
- tmpKey = filepath.Join(tmpDir, "ca-int.key")
- tmpCert = filepath.Join(tmpDir, "ca-int.pem")
- }
+ var tmpKey string
+ var tmpCert string
+ if ci.IsRoot {
+ tmpKey = filepath.Join(tmpDir, "root-ca.key")
+ tmpCert = filepath.Join(tmpDir, "root-ca.pem")
+ } else {
+ tmpKey = filepath.Join(tmpDir, "ca-int.key")
+ tmpCert = filepath.Join(tmpDir, "ca-int.pem")
+ }
- if ci.CreateType == "generate" {
- // 1. Generate key
- createCmd := "genrsa -aes256 -passout pass:foobar"
- keySize := " 4096"
- if strings.HasPrefix(ci.KeyType, "ecdsa") {
- keySize = ""
- createCmd = "ecparam -genkey -name "
- if ci.KeyType == "ecdsa256" {
- createCmd = createCmd + "prime256v1"
- }
- if ci.KeyType == "ecdsa384" {
- createCmd = createCmd + "secp384r1"
- }
- } else {
- if strings.HasSuffix(ci.KeyType, "3072") {
- keySize = " 3072"
- }
- if strings.HasSuffix(ci.KeyType, "2048") {
- keySize = " 2048"
- }
- }
+ if ci.CreateType == "generate" {
+ // 1. Generate key
+ createCmd := "genrsa -aes256 -passout pass:foobar"
+ keySize := " 4096"
+ if strings.HasPrefix(ci.KeyType, "ecdsa") {
+ keySize = ""
+ createCmd = "ecparam -genkey -name "
+ if ci.KeyType == "ecdsa256" {
+ createCmd = createCmd + "prime256v1"
+ }
+ if ci.KeyType == "ecdsa384" {
+ createCmd = createCmd + "secp384r1"
+ }
+ } else {
+ if strings.HasSuffix(ci.KeyType, "3072") {
+ keySize = " 3072"
+ }
+ if strings.HasSuffix(ci.KeyType, "2048") {
+ keySize = " 2048"
+ }
+ }
- if _, err := exe_cmd("openssl " + createCmd + " -out " + path + certBase + ".key" + keySize); err != nil {
- return reportError(err)
- }
- if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -passin pass:foobar -out " + path + certBase + ".tmp"); err != nil {
- return reportError(err)
- }
- if _, err = exe_cmd("mv " + path + certBase + ".tmp " + path + certBase + ".key"); err != nil {
- return reportError(err)
- }
+ if _, err := exe_cmd("openssl " + createCmd + " -out " + path + certBase + ".key" + keySize); err != nil {
+ return reportError(err)
+ }
+ if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -passin pass:foobar -out " + path + certBase + ".tmp"); err != nil {
+ return reportError(err)
+ }
+ if _, err = exe_cmd("mv " + path + certBase + ".tmp " + path + certBase + ".key"); err != nil {
+ return reportError(err)
+ }
- _, _ = exe_cmd("sleep 1")
+ _, _ = exe_cmd("sleep 1")
- // 2. Generate certificate
- subject := "/C=" + ci.Country + "/O=" + ci.Organization
- if ci.OrgUnit != "" {
- subject = subject + "/OU=" + ci.OrgUnit
- }
- subject = subject + "/CN=" + ci.CommonName
- subject = strings.Replace(subject, " ", "\\\\", -1)
+ // 2. Generate certificate
+ subject := "/C=" + ci.Country + "/O=" + ci.Organization
+ if ci.OrgUnit != "" {
+ subject = subject + "/OU=" + ci.OrgUnit
+ }
+ subject = subject + "/CN=" + ci.CommonName
+ subject = strings.Replace(subject, " ", "\\\\", -1)
- if ci.IsRoot {
- if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -days 3650 -new -x509 -extensions v3_ca -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".pem"); err != nil {
- return reportError(err)
- }
- } else {
- if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -new -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".csr"); err != nil {
- return reportError(err)
- }
- if _, err := exe_cmd("openssl ca -config " + path + "../openssl.cnf -extensions v3_intermediate_ca -days 3600 -md sha384 -notext -batch -in " + path + certBase + ".csr -out " + path + certBase + ".pem"); err != nil {
- return reportError(err)
- }
- }
+ if ci.IsRoot {
+ if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -days 3650 -new -x509 -extensions v3_ca -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".pem"); err != nil {
+ return reportError(err)
+ }
+ } else {
+ if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -new -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".csr"); err != nil {
+ return reportError(err)
+ }
+ if _, err := exe_cmd("openssl ca -config " + path + "../openssl.cnf -extensions v3_intermediate_ca -days 3600 -md sha384 -notext -batch -in " + path + certBase + ".csr -out " + path + certBase + ".pem"); err != nil {
+ return reportError(err)
+ }
+ }
- } else if ci.CreateType == "import" {
- tmpFile := filepath.Join(tmpDir, ci.ImportHandler.Filename)
+ } else if ci.CreateType == "import" {
+ tmpFile := filepath.Join(tmpDir, ci.ImportHandler.Filename)
- f, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0666)
- if err != nil {
- return err
- }
+ f, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
- defer f.Close()
+ defer f.Close()
- io.Copy(f, ci.ImportFile)
+ io.Copy(f, ci.ImportFile)
- contentType := ci.ImportHandler.Header.Get("Content-Type")
- if contentType == "application/x-pkcs12" {
- if ci.IsRoot {
- if strings.Index(ci.ImportHandler.Filename, "labca_root") != 0 {
- fmt.Printf("WARNING: importing root from .pfx file but name is %s\n", ci.ImportHandler.Filename)
- }
- } else {
- if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
- fmt.Printf("WARNING: importing issuer from .pfx file but name is %s\n", ci.ImportHandler.Filename)
- }
- }
+ contentType := ci.ImportHandler.Header.Get("Content-Type")
+ if contentType == "application/x-pkcs12" {
+ if ci.IsRoot {
+ if strings.Index(ci.ImportHandler.Filename, "labca_root") != 0 {
+ fmt.Printf("WARNING: importing root from .pfx file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ } else {
+ if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
+ fmt.Printf("WARNING: importing issuer from .pfx file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ }
- pwd := "pass:dummy"
- if ci.ImportPwd != "" {
- pwd = "pass:" + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
- }
+ pwd := "pass:dummy"
+ if ci.ImportPwd != "" {
+ pwd = "pass:" + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
+ }
- if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nocerts -nodes -out " + tmpKey); err != nil {
- if strings.Index(string(out), "invalid password") >= 0 {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- }
- if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nokeys -out " + tmpCert); err != nil {
- if strings.Index(string(out), "invalid password") >= 0 {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- }
- } else if contentType == "application/zip" {
- if ci.IsRoot {
- if (strings.Index(ci.ImportHandler.Filename, "labca_root") != 0) && (strings.Index(ci.ImportHandler.Filename, "labca_certificates") != 0) {
- fmt.Printf("WARNING: importing root from .zip file but name is %s\n", ci.ImportHandler.Filename)
- }
- } else {
- if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
- fmt.Printf("WARNING: importing issuer from .zip file but name is %s\n", ci.ImportHandler.Filename)
- }
- }
+ if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nocerts -nodes -out " + tmpKey); err != nil {
+ if strings.Index(string(out), "invalid password") >= 0 {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ }
+ if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nokeys -out " + tmpCert); err != nil {
+ if strings.Index(string(out), "invalid password") >= 0 {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ }
+ } else if contentType == "application/zip" {
+ if ci.IsRoot {
+ if (strings.Index(ci.ImportHandler.Filename, "labca_root") != 0) && (strings.Index(ci.ImportHandler.Filename, "labca_certificates") != 0) {
+ fmt.Printf("WARNING: importing root from .zip file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ } else {
+ if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
+ fmt.Printf("WARNING: importing issuer from .zip file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ }
- cmd := "unzip -j"
- if ci.ImportPwd != "" {
- cmd = cmd + " -P " + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
- } else {
- cmd = cmd + " -P dummy"
- }
- cmd = cmd + " " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -d " + tmpDir
+ cmd := "unzip -j"
+ if ci.ImportPwd != "" {
+ cmd = cmd + " -P " + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
+ } else {
+ cmd = cmd + " -P dummy"
+ }
+ cmd = cmd + " " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -d " + tmpDir
- if _, err := exe_cmd(cmd); err != nil {
- if err.Error() == "exit status 82" {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- }
- } else {
- return errors.New("Content Type '" + contentType + "' not supported!")
- }
+ if _, err := exe_cmd(cmd); err != nil {
+ if err.Error() == "exit status 82" {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ }
+ } else {
+ return errors.New("Content Type '" + contentType + "' not supported!")
+ }
- } else if ci.CreateType == "upload" {
- if err := ioutil.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil {
- return err
- }
+ } else if ci.CreateType == "upload" {
+ if err := ioutil.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil {
+ return err
+ }
- pwd := "pass:dummy"
- if ci.Passphrase != "" {
- pwd = "pass:" + strings.Replace(ci.Passphrase, " ", "\\\\", -1)
- }
+ pwd := "pass:dummy"
+ if ci.Passphrase != "" {
+ pwd = "pass:" + strings.Replace(ci.Passphrase, " ", "\\\\", -1)
+ }
- if out, err := exe_cmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil {
- if strings.Index(string(out), ":bad decrypt:") >= 0 {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- } else {
- if _, err = exe_cmd("mv " + tmpKey + "-out " + tmpKey); err != nil {
- return reportError(err)
- }
- }
+ if out, err := exe_cmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil {
+ if strings.Index(string(out), ":bad decrypt:") >= 0 {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ } else {
+ if _, err = exe_cmd("mv " + tmpKey + "-out " + tmpKey); err != nil {
+ return reportError(err)
+ }
+ }
- if err := ioutil.WriteFile(tmpCert, []byte(ci.Certificate), 0644); err != nil {
- return err
- }
+ if err := ioutil.WriteFile(tmpCert, []byte(ci.Certificate), 0644); err != nil {
+ return err
+ }
- } else {
- return fmt.Errorf("Unknown CreateType!")
- }
+ } else {
+ return fmt.Errorf("Unknown CreateType!")
+ }
- // This is shared between pfx/zip upload and pem text upload
- if ci.CreateType != "generate" {
- var rootCert string
- var rootKey string
- var issuerCert string
- var issuerKey string
+ // This is shared between pfx/zip upload and pem text upload
+ if ci.CreateType != "generate" {
+ var rootCert string
+ var rootKey string
+ var issuerCert string
+ var issuerKey string
- if ci.IsRoot {
- rootCert = filepath.Join(tmpDir, "root-ca.pem")
- rootKey = filepath.Join(tmpDir, "root-ca.key")
+ if ci.IsRoot {
+ rootCert = filepath.Join(tmpDir, "root-ca.pem")
+ rootKey = filepath.Join(tmpDir, "root-ca.key")
- if _, err := os.Stat(rootCert); os.IsNotExist(err) {
- return errors.New("File does not contain root-ca.pem!")
- }
- if _, err := os.Stat(rootKey); os.IsNotExist(err) {
- return errors.New("File does not contain root-ca.key!")
- }
- }
+ if _, err := os.Stat(rootCert); os.IsNotExist(err) {
+ return errors.New("File does not contain root-ca.pem!")
+ }
+ if _, err := os.Stat(rootKey); os.IsNotExist(err) {
+ return errors.New("File does not contain root-ca.key!")
+ }
+ }
- issuerCert = filepath.Join(tmpDir, "ca-int.pem")
- issuerKey = filepath.Join(tmpDir, "ca-int.key")
+ issuerCert = filepath.Join(tmpDir, "ca-int.pem")
+ issuerKey = filepath.Join(tmpDir, "ca-int.key")
- if _, err := os.Stat(issuerCert); os.IsNotExist(err) {
- if ci.IsRoot {
- issuerCert = ""
- } else {
- return errors.New("File does not contain ca-int.pem!")
- }
- }
- if _, err := os.Stat(issuerKey); os.IsNotExist(err) {
- if ci.IsRoot {
- issuerKey = ""
- } else {
- return errors.New("File does not contain ca-int.key!")
- }
- }
+ if _, err := os.Stat(issuerCert); os.IsNotExist(err) {
+ if ci.IsRoot {
+ issuerCert = ""
+ } else {
+ return errors.New("File does not contain ca-int.pem!")
+ }
+ }
+ if _, err := os.Stat(issuerKey); os.IsNotExist(err) {
+ if ci.IsRoot {
+ issuerKey = ""
+ } else {
+ return errors.New("File does not contain ca-int.key!")
+ }
+ }
- var rootSubject string
- if (rootCert != "") && (rootKey != "") {
- r, err := exe_cmd("openssl x509 -noout -subject -in " + rootCert)
- if err != nil {
- return reportError(err)
- } else {
- rootSubject = string(r[0 : len(r)-1])
- fmt.Printf("Import root with subject '%s'\n", rootSubject)
- }
+ var rootSubject string
+ if (rootCert != "") && (rootKey != "") {
+ r, err := exe_cmd("openssl x509 -noout -subject -in " + rootCert)
+ if err != nil {
+ return reportError(err)
+ } else {
+ rootSubject = string(r[0 : len(r)-1])
+ fmt.Printf("Import root with subject '%s'\n", rootSubject)
+ }
- r, err = exe_cmd("openssl pkey -noout -in " + rootKey)
- if err != nil {
- return reportError(err)
- } else {
- fmt.Println("Import root key")
- }
- }
+ r, err = exe_cmd("openssl pkey -noout -in " + rootKey)
+ if err != nil {
+ return reportError(err)
+ } else {
+ fmt.Println("Import root key")
+ }
+ }
- if (issuerCert != "") && (issuerKey != "") {
- if ci.IsRoot {
- if err := preCreateTasks(path + "issuer/"); err != nil {
- return err
- }
- }
+ if (issuerCert != "") && (issuerKey != "") {
+ if ci.IsRoot {
+ if err := preCreateTasks(path + "issuer/"); err != nil {
+ return err
+ }
+ }
- r, err := exe_cmd("openssl x509 -noout -subject -in " + issuerCert)
- if err != nil {
- return reportError(err)
- } else {
- fmt.Printf("Import issuer with subject '%s'\n", string(r[0:len(r)-1]))
- }
+ r, err := exe_cmd("openssl x509 -noout -subject -in " + issuerCert)
+ if err != nil {
+ return reportError(err)
+ } else {
+ fmt.Printf("Import issuer with subject '%s'\n", string(r[0:len(r)-1]))
+ }
- r, err = exe_cmd("openssl x509 -noout -issuer -in " + issuerCert)
- if err != nil {
- return reportError(err)
- } else {
- issuerIssuer := string(r[0 : len(r)-1])
- fmt.Printf("Issuer certificate issued by CA '%s'\n", issuerIssuer)
+ r, err = exe_cmd("openssl x509 -noout -issuer -in " + issuerCert)
+ if err != nil {
+ return reportError(err)
+ } else {
+ issuerIssuer := string(r[0 : len(r)-1])
+ fmt.Printf("Issuer certificate issued by CA '%s'\n", issuerIssuer)
- if rootSubject == "" {
- r, err := exe_cmd("openssl x509 -noout -subject -in data/root-ca.pem")
- if err != nil {
- return reportError(err)
- } else {
- rootSubject = string(r[0 : len(r)-1])
- }
- }
+ if rootSubject == "" {
+ r, err := exe_cmd("openssl x509 -noout -subject -in data/root-ca.pem")
+ if err != nil {
+ return reportError(err)
+ } else {
+ rootSubject = string(r[0 : len(r)-1])
+ }
+ }
- issuerIssuer = strings.Replace(issuerIssuer, "issuer=", "", -1)
- rootSubject = strings.Replace(rootSubject, "subject=", "", -1)
- if issuerIssuer != rootSubject {
- return errors.New("Issuer not issued by our Root CA!")
- }
- }
+ issuerIssuer = strings.Replace(issuerIssuer, "issuer=", "", -1)
+ rootSubject = strings.Replace(rootSubject, "subject=", "", -1)
+ if issuerIssuer != rootSubject {
+ return errors.New("Issuer not issued by our Root CA!")
+ }
+ }
- r, err = exe_cmd("openssl pkey -noout -in " + issuerKey)
- if err != nil {
- return reportError(err)
- } else {
- fmt.Println("Import issuer key")
- }
- }
+ r, err = exe_cmd("openssl pkey -noout -in " + issuerKey)
+ if err != nil {
+ return reportError(err)
+ } else {
+ fmt.Println("Import issuer key")
+ }
+ }
- // All is good now, move files to their permanent location...
- if rootCert != "" {
- if _, err = exe_cmd("mv " + rootCert + " " + path); err != nil {
- return reportError(err)
- }
- }
- if rootKey != "" {
- if _, err = exe_cmd("mv " + rootKey + " " + path); err != nil {
- return reportError(err)
- }
- }
- if issuerCert != "" {
- if _, err = exe_cmd("mv " + issuerCert + " data/issuer/"); err != nil {
- return reportError(err)
- }
- }
- if issuerKey != "" {
- if _, err = exe_cmd("mv " + issuerKey + " data/issuer/"); err != nil {
- return reportError(err)
- }
- }
+ // All is good now, move files to their permanent location...
+ if rootCert != "" {
+ if _, err = exe_cmd("mv " + rootCert + " " + path); err != nil {
+ return reportError(err)
+ }
+ }
+ if rootKey != "" {
+ if _, err = exe_cmd("mv " + rootKey + " " + path); err != nil {
+ return reportError(err)
+ }
+ }
+ if issuerCert != "" {
+ if _, err = exe_cmd("mv " + issuerCert + " data/issuer/"); err != nil {
+ return reportError(err)
+ }
+ }
+ if issuerKey != "" {
+ if _, err = exe_cmd("mv " + issuerKey + " data/issuer/"); err != nil {
+ return reportError(err)
+ }
+ }
- if (issuerCert != "") && (issuerKey != "") && ci.IsRoot {
- if err := postCreateTasks(path+"issuer/", "ca-int"); err != nil {
- return err
- }
- }
- }
+ if (issuerCert != "") && (issuerKey != "") && ci.IsRoot {
+ if err := postCreateTasks(path+"issuer/", "ca-int"); err != nil {
+ return err
+ }
+ }
+ }
- if err := postCreateTasks(path, certBase); err != nil {
- return err
- }
+ if err := postCreateTasks(path, certBase); err != nil {
+ return err
+ }
- if ci.IsRoot {
- if _, err := exe_cmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil {
- return reportError(err)
- }
- }
+ if ci.IsRoot {
+ if _, err := exe_cmd("openssl ca -config " + path + "openssl.cnf -gencrl -keyfile " + path + certBase + ".key -cert " + path + certBase + ".pem -out " + path + certBase + ".crl"); err != nil {
+ return reportError(err)
+ }
+ }
- return nil
+ return nil
}
func postCreateTasks(path string, certBase string) error {
- if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -out " + path + certBase + ".key.der -outform der"); err != nil {
- return reportError(err)
- }
+ if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -out " + path + certBase + ".key.der -outform der"); err != nil {
+ return reportError(err)
+ }
- if _, err := exe_cmd("openssl x509 -in " + path + certBase + ".pem -out " + path + certBase + ".der -outform DER"); err != nil {
- return reportError(err)
- }
+ if _, err := exe_cmd("openssl x509 -in " + path + certBase + ".pem -out " + path + certBase + ".der -outform DER"); err != nil {
+ return reportError(err)
+ }
- return nil
+ return nil
}
func exe_cmd(cmd string) ([]byte, error) {
- parts := strings.Fields(cmd)
- for i := 0; i < len(parts); i++ {
- parts[i] = strings.Replace(parts[i], "\\\\", " ", -1)
- }
- head := parts[0]
- parts = parts[1:]
+ parts := strings.Fields(cmd)
+ for i := 0; i < len(parts); i++ {
+ parts[i] = strings.Replace(parts[i], "\\\\", " ", -1)
+ }
+ head := parts[0]
+ parts = parts[1:]
- out, err := exec.Command(head, parts...).CombinedOutput()
- if err != nil {
- fmt.Print(fmt.Sprint(err) + ": " + string(out))
- } else {
- //fmt.Println(string(out))
- }
- return out, err
+ out, err := exec.Command(head, parts...).CombinedOutput()
+ if err != nil {
+ fmt.Print(fmt.Sprint(err) + ": " + string(out))
+ } else {
+ //fmt.Println(string(out))
+ }
+ return out, err
}
diff --git a/gui/dashboard.go b/gui/dashboard.go
index 66cc147..6ae73f1 100644
--- a/gui/dashboard.go
+++ b/gui/dashboard.go
@@ -1,359 +1,358 @@
package main
import (
- "database/sql"
- "fmt"
- "github.com/dustin/go-humanize"
- "log"
- "net/http"
- "regexp"
- "strconv"
- "strings"
- "time"
+ "database/sql"
+ "fmt"
+ "github.com/dustin/go-humanize"
+ "log"
+ "net/http"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
)
type Activity struct {
- Title string
- Message string
- Timestamp string
- TimestampRel string
- Class string
+ Title string
+ Message string
+ Timestamp string
+ TimestampRel string
+ Class string
}
func _parseLine(line string, loc *time.Location) Activity {
- var activity Activity
+ var activity Activity
- // Remove ansi colors
- b := make([]byte, len(line))
- var bl int
- for i := 0; i < len(line); i++ {
- c := line[i]
- if c >= 32 && c != 127 {
- b[bl] = c
- bl++
- }
- }
- line = string(b[:bl])
- line = strings.Replace(line, "[31m", "", -1)
- line = strings.Replace(line, "[1m", "", -1)
- line = strings.Replace(line, "[0m", "", -1)
+ // Remove ansi colors
+ b := make([]byte, len(line))
+ var bl int
+ for i := 0; i < len(line); i++ {
+ c := line[i]
+ if c >= 32 && c != 127 {
+ b[bl] = c
+ bl++
+ }
+ }
+ line = string(b[:bl])
+ line = strings.Replace(line, "[31m", "", -1)
+ line = strings.Replace(line, "[1m", "", -1)
+ line = strings.Replace(line, "[0m", "", -1)
- re := regexp.MustCompile("^.*\\|\\s*(\\S)(\\S+) (\\S+) (\\S+) (.*)$")
- result := re.FindStringSubmatch(line)
+ re := regexp.MustCompile("^.*\\|\\s*(\\S)(\\S+) (\\S+) (\\S+) (.*)$")
+ result := re.FindStringSubmatch(line)
- activity.Class = ""
- if result[1] == "W" {
- activity.Class = "warning"
- }
- if result[1] == "E" {
- activity.Class = "error"
- }
+ activity.Class = ""
+ if result[1] == "W" {
+ activity.Class = "warning"
+ }
+ if result[1] == "E" {
+ activity.Class = "error"
+ }
- timestamp, err := time.ParseInLocation("060102150405", result[2], loc)
- activity.Timestamp = ""
- activity.TimestampRel = "??"
- if err == nil {
- activity.Timestamp = timestamp.Format("02-Jan-2006 15:04:05 MST")
- activity.Timestamp = strings.Replace(activity.Timestamp, "+0000", "GMT", -1)
- activity.TimestampRel = humanize.RelTime(timestamp, time.Now(), "", "")
- }
+ timestamp, err := time.ParseInLocation("060102150405", result[2], loc)
+ activity.Timestamp = ""
+ activity.TimestampRel = "??"
+ if err == nil {
+ activity.Timestamp = timestamp.Format("02-Jan-2006 15:04:05 MST")
+ activity.Timestamp = strings.Replace(activity.Timestamp, "+0000", "GMT", -1)
+ activity.TimestampRel = humanize.RelTime(timestamp, time.Now(), "", "")
+ }
- tail := result[3][len(result[3])-2:]
- activity.Title = ""
- switch tail {
- case "ca":
- activity.Title = "Certification Agent"
- case "ra":
- activity.Title = "Registration Agent"
- case "sa":
- activity.Title = "Storage Agent"
- case "va":
- activity.Title = "Validation Agent"
- }
+ tail := result[3][len(result[3])-2:]
+ activity.Title = ""
+ switch tail {
+ case "ca":
+ activity.Title = "Certification Agent"
+ case "ra":
+ activity.Title = "Registration Agent"
+ case "sa":
+ activity.Title = "Storage Agent"
+ case "va":
+ activity.Title = "Validation Agent"
+ }
- message := result[5]
- idx := strings.Index(message, ".well-known/acme-challenge")
- if idx > -1 {
- message = message[0:idx]
- }
- if strings.Index(message, "Checked CAA records for") > -1 {
- message = message[0:strings.Index(message, ",")]
- }
- if strings.Index(message, "Validation result") > -1 {
- message = message[0:17]
- }
- idx = strings.Index(message, " csr=[")
- if idx > -1 {
- message = message[0:idx]
- }
- idx = strings.Index(message, " precertificate=[")
- if idx > -1 {
- message = message[0:idx]
- }
- if strings.Index(message, "Certificate request - ") > -1 {
- idx = strings.Index(message, " JSON={")
- if idx > -1 {
- message = message[0:idx]
- }
- }
- activity.Message = message
+ message := result[5]
+ idx := strings.Index(message, ".well-known/acme-challenge")
+ if idx > -1 {
+ message = message[0:idx]
+ }
+ if strings.Index(message, "Checked CAA records for") > -1 {
+ message = message[0:strings.Index(message, ",")]
+ }
+ if strings.Index(message, "Validation result") > -1 {
+ message = message[0:17]
+ }
+ idx = strings.Index(message, " csr=[")
+ if idx > -1 {
+ message = message[0:idx]
+ }
+ idx = strings.Index(message, " precertificate=[")
+ if idx > -1 {
+ message = message[0:idx]
+ }
+ if strings.Index(message, "Certificate request - ") > -1 {
+ idx = strings.Index(message, " JSON={")
+ if idx > -1 {
+ message = message[0:idx]
+ }
+ }
+ activity.Message = message
- return activity
+ return activity
}
func _parseActivity(data string) []Activity {
- var activity []Activity
+ var activity []Activity
- lines := strings.Split(data, "\n")
+ lines := strings.Split(data, "\n")
- loc, err := time.LoadLocation(lines[0])
- if err != nil {
- log.Printf("Could not determine location: %s\n", err)
- loc = time.Local
- }
+ loc, err := time.LoadLocation(lines[0])
+ if err != nil {
+ log.Printf("Could not determine location: %s\n", err)
+ loc = time.Local
+ }
- for i := len(lines) - 2; i >= 1; i-- {
- activity = append(activity, _parseLine(lines[i], loc))
- }
+ for i := len(lines) - 2; i >= 1; i-- {
+ activity = append(activity, _parseLine(lines[i], loc))
+ }
- return activity
+ return activity
}
type Component struct {
- Name string
- Timestamp string
- TimestampRel string
- Class string
- LogUrl string
- LogTitle string
- Buttons []map[string]interface{}
+ Name string
+ Timestamp string
+ TimestampRel string
+ Class string
+ LogUrl string
+ LogTitle string
+ Buttons []map[string]interface{}
}
func _parseComponents(data string) []Component {
- var components []Component
+ var components []Component
- if data[len(data)-1:] == "\n" {
- data = data[0 : len(data)-1]
- }
+ if data[len(data)-1:] == "\n" {
+ data = data[0 : len(data)-1]
+ }
- parts := strings.Split(data, "|")
+ parts := strings.Split(data, "|")
- loc, err := time.LoadLocation(parts[0])
- if err != nil {
- log.Printf("Could not determine location: %s\n", err)
- loc = time.Local
- }
+ loc, err := time.LoadLocation(parts[0])
+ if err != nil {
+ log.Printf("Could not determine location: %s\n", err)
+ loc = time.Local
+ }
- nginx, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[1], loc)
- nginxReal := ""
- nginxNice := "stopped"
- nginxClass := "error"
- if err == nil {
- nginxReal = nginx.Format("02-Jan-2006 15:04:05 MST")
- nginxNice = humanize.RelTime(nginx, time.Now(), "", "")
- nginxClass = ""
- }
+ nginx, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[1], loc)
+ nginxReal := ""
+ nginxNice := "stopped"
+ nginxClass := "error"
+ if err == nil {
+ nginxReal = nginx.Format("02-Jan-2006 15:04:05 MST")
+ nginxNice = humanize.RelTime(nginx, time.Now(), "", "")
+ nginxClass = ""
+ }
- svc, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[2], loc)
- svcReal := ""
- svcNice := "stopped"
- svcClass := "error"
- if err == nil {
- svcReal = svc.Format("02-Jan-2006 15:04:05 MST")
- svcNice = humanize.RelTime(svc, time.Now(), "", "")
- svcClass = ""
- }
+ svc, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[2], loc)
+ svcReal := ""
+ svcNice := "stopped"
+ svcClass := "error"
+ if err == nil {
+ svcReal = svc.Format("02-Jan-2006 15:04:05 MST")
+ svcNice = humanize.RelTime(svc, time.Now(), "", "")
+ svcClass = ""
+ }
- boulder, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[3], loc)
- boulderReal := ""
- boulderNice := "stopped"
- boulderClass := "error"
- if err == nil {
- boulderReal = boulder.Format("02-Jan-2006 15:04:05 MST")
- boulderNice = humanize.RelTime(boulder, time.Now(), "", "")
- boulderClass = ""
- }
+ boulder, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[3], loc)
+ boulderReal := ""
+ boulderNice := "stopped"
+ boulderClass := "error"
+ if err == nil {
+ boulderReal = boulder.Format("02-Jan-2006 15:04:05 MST")
+ boulderNice = humanize.RelTime(boulder, time.Now(), "", "")
+ boulderClass = ""
+ }
- labca, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[4], loc)
- labcaReal := ""
- labcaNice := "stopped"
- labcaClass := "error"
- if err == nil {
- labcaReal = labca.Format("02-Jan-2006 15:04:05 MST")
- labcaNice = humanize.RelTime(labca, time.Now(), "", "")
- labcaClass = ""
- }
+ labca, err := time.ParseInLocation("Jan _2 15:04:05 2006", parts[4], loc)
+ labcaReal := ""
+ labcaNice := "stopped"
+ labcaClass := "error"
+ if err == nil {
+ labcaReal = labca.Format("02-Jan-2006 15:04:05 MST")
+ labcaNice = humanize.RelTime(labca, time.Now(), "", "")
+ labcaClass = ""
+ }
- components = append(components, Component{Name: "NGINX Webserver", Timestamp: nginxReal, TimestampRel: nginxNice, Class: nginxClass})
- components = append(components, Component{Name: "Host Service", Timestamp: svcReal, TimestampRel: svcNice, Class: svcClass})
- components = append(components, Component{Name: "Boulder (ACME)", Timestamp: boulderReal, TimestampRel: boulderNice, Class: boulderClass})
- components = append(components, Component{Name: "LabCA Application", Timestamp: labcaReal, TimestampRel: labcaNice, Class: labcaClass})
+ components = append(components, Component{Name: "NGINX Webserver", Timestamp: nginxReal, TimestampRel: nginxNice, Class: nginxClass})
+ components = append(components, Component{Name: "Host Service", Timestamp: svcReal, TimestampRel: svcNice, Class: svcClass})
+ components = append(components, Component{Name: "Boulder (ACME)", Timestamp: boulderReal, TimestampRel: boulderNice, Class: boulderClass})
+ components = append(components, Component{Name: "LabCA Application", Timestamp: labcaReal, TimestampRel: labcaNice, Class: labcaClass})
- return components
+ return components
}
type Stat struct {
- Name string
- Hint string
- Value string
- Class string
+ Name string
+ Hint string
+ Value string
+ Class string
}
func _parseStats(data string) []Stat {
- var stats []Stat
+ var stats []Stat
- if data[len(data)-1:] == "\n" {
- data = data[0 : len(data)-1]
- }
+ if data[len(data)-1:] == "\n" {
+ data = data[0 : len(data)-1]
+ }
- parts := strings.Split(data, "|")
+ parts := strings.Split(data, "|")
- loc, err := time.LoadLocation(parts[0])
- if err != nil {
- log.Printf("Could not determine location: %s\n", err)
- loc = time.Local
- }
+ loc, err := time.LoadLocation(parts[0])
+ if err != nil {
+ log.Printf("Could not determine location: %s\n", err)
+ loc = time.Local
+ }
- since, err := time.ParseInLocation("2006-01-02 15:04:05", parts[1], loc)
- var sinceReal string
- sinceNice := "??"
- if err == nil {
- sinceReal = since.Format("02-Jan-2006 15:04:05 MST")
- sinceNice = humanize.RelTime(since, time.Now(), "", "")
- }
- stats = append(stats, Stat{Name: "System Uptime", Hint: sinceReal, Value: sinceNice})
+ since, err := time.ParseInLocation("2006-01-02 15:04:05", parts[1], loc)
+ var sinceReal string
+ sinceNice := "??"
+ if err == nil {
+ sinceReal = since.Format("02-Jan-2006 15:04:05 MST")
+ sinceNice = humanize.RelTime(since, time.Now(), "", "")
+ }
+ stats = append(stats, Stat{Name: "System Uptime", Hint: sinceReal, Value: sinceNice})
- numProcs, err := strconv.Atoi(parts[2])
- if err != nil {
- numProcs = 0
- }
- stats = append(stats, Stat{Name: "Process Count", Value: strconv.Itoa(numProcs)})
+ numProcs, err := strconv.Atoi(parts[2])
+ if err != nil {
+ numProcs = 0
+ }
+ stats = append(stats, Stat{Name: "Process Count", Value: strconv.Itoa(numProcs)})
- memUsed, err := strconv.ParseUint(parts[3], 10, 64)
- if err != nil {
- memUsed = 0
- }
- memAvail, err := strconv.ParseUint(parts[4], 10, 64)
- if err != nil {
- memAvail = 0
- }
+ memUsed, err := strconv.ParseUint(parts[3], 10, 64)
+ if err != nil {
+ memUsed = 0
+ }
+ memAvail, err := strconv.ParseUint(parts[4], 10, 64)
+ if err != nil {
+ memAvail = 0
+ }
- percMem := float64(0)
- if (memUsed + memAvail) > 0 {
- percMem = float64(100) * float64(memUsed) / float64(memUsed+memAvail)
- }
+ percMem := float64(0)
+ if (memUsed + memAvail) > 0 {
+ percMem = float64(100) * float64(memUsed) / float64(memUsed+memAvail)
+ }
- usedHuman := humanize.IBytes(memUsed)
- availHuman := humanize.IBytes(memAvail)
- percHuman := fmt.Sprintf("%s %%", humanize.FtoaWithDigits(percMem, 1))
+ usedHuman := humanize.IBytes(memUsed)
+ availHuman := humanize.IBytes(memAvail)
+ percHuman := fmt.Sprintf("%s %%", humanize.FtoaWithDigits(percMem, 1))
- class := ""
- if percMem > 75 {
- class = "warning"
- }
- if percMem > 90 {
- class = "error"
- }
- stats = append(stats, Stat{Name: "Memory Usage", Value: percHuman, Class: class})
- stats = append(stats, Stat{Name: "Memory Used", Value: usedHuman})
- class = ""
- if memAvail < 250000000 {
- class = "warning"
- }
- if memAvail < 100000000 {
- class = "error"
- }
- stats = append(stats, Stat{Name: "Memory Available", Value: availHuman, Class: class})
+ class := ""
+ if percMem > 75 {
+ class = "warning"
+ }
+ if percMem > 90 {
+ class = "error"
+ }
+ stats = append(stats, Stat{Name: "Memory Usage", Value: percHuman, Class: class})
+ stats = append(stats, Stat{Name: "Memory Used", Value: usedHuman})
+ class = ""
+ if memAvail < 250000000 {
+ class = "warning"
+ }
+ if memAvail < 100000000 {
+ class = "error"
+ }
+ stats = append(stats, Stat{Name: "Memory Available", Value: availHuman, Class: class})
- return stats
+ return stats
}
-func CollectDashboardData(w http.ResponseWriter, r *http.Request) (map[string]interface {}, error) {
- db, err := sql.Open(dbType, dbConn)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+func CollectDashboardData(w http.ResponseWriter, r *http.Request) (map[string]interface{}, error) {
+ db, err := sql.Open(dbType, dbConn)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- defer db.Close()
+ defer db.Close()
- dashboardData := make(map[string]interface{})
- dashboardData["RequestBase"] = r.Header.Get("X-Request-Base")
+ dashboardData := make(map[string]interface{})
+ dashboardData["RequestBase"] = r.Header.Get("X-Request-Base")
- rows, err := db.Query("SELECT count(*) FROM registrations")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ rows, err := db.Query("SELECT count(*) FROM registrations")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- var dbres int
- if rows.Next() {
- err = rows.Scan(&dbres)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ var dbres int
+ if rows.Next() {
+ err = rows.Scan(&dbres)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- dashboardData["NumAccounts"] = dbres
- }
+ dashboardData["NumAccounts"] = dbres
+ }
- rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate='0000-00-00 00:00:00' AND notAfter >= NOW()")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate='0000-00-00 00:00:00' AND notAfter >= NOW()")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- if rows.Next() {
- err = rows.Scan(&dbres)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ if rows.Next() {
+ err = rows.Scan(&dbres)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- dashboardData["NumCerts"] = dbres
- }
+ dashboardData["NumCerts"] = dbres
+ }
- rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate='0000-00-00 00:00:00' AND notAfter < NOW()")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate='0000-00-00 00:00:00' AND notAfter < NOW()")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- if rows.Next() {
- err = rows.Scan(&dbres)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ if rows.Next() {
+ err = rows.Scan(&dbres)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- dashboardData["NumExpired"] = dbres
- }
+ dashboardData["NumExpired"] = dbres
+ }
- rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate<>'0000-00-00 00:00:00'")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ rows, err = db.Query("SELECT count(*) FROM certificateStatus WHERE revokedDate<>'0000-00-00 00:00:00'")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- if rows.Next() {
- err = rows.Scan(&dbres)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return nil, err
- }
+ if rows.Next() {
+ err = rows.Scan(&dbres)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return nil, err
+ }
- dashboardData["NumRevoked"] = dbres
- }
+ dashboardData["NumRevoked"] = dbres
+ }
- activity := getLog(w, r, "activity")
- dashboardData["Activity"] = _parseActivity(activity)
+ activity := getLog(w, r, "activity")
+ dashboardData["Activity"] = _parseActivity(activity)
- components := getLog(w, r, "components")
- dashboardData["Components"] = _parseComponents(components)
+ components := getLog(w, r, "components")
+ dashboardData["Components"] = _parseComponents(components)
- stats := getLog(w, r, "stats")
- dashboardData["Stats"] = _parseStats(stats)
+ stats := getLog(w, r, "stats")
+ dashboardData["Stats"] = _parseStats(stats)
- return dashboardData, nil
+ return dashboardData, nil
}
-
diff --git a/gui/main.go b/gui/main.go
index 9cfbe48..3f53ea3 100644
--- a/gui/main.go
+++ b/gui/main.go
@@ -1,2274 +1,2273 @@
package main
import (
- "bufio"
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/rand"
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "github.com/biz/templates"
- _ "github.com/go-sql-driver/mysql"
- "github.com/gorilla/mux"
- "github.com/gorilla/securecookie"
- "github.com/gorilla/sessions"
- "github.com/gorilla/websocket"
- "github.com/nbutton23/zxcvbn-go"
- "github.com/theherk/viper"
- "golang.org/x/crypto/bcrypt"
- "html/template"
- "io"
- "io/ioutil"
- "log"
- "math"
- "net"
- "net/http"
- "os"
- "os/exec"
- "path/filepath"
- "reflect"
- "regexp"
- "runtime"
- "runtime/debug"
- "strconv"
- "strings"
- "time"
+ "bufio"
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "github.com/biz/templates"
+ _ "github.com/go-sql-driver/mysql"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/securecookie"
+ "github.com/gorilla/sessions"
+ "github.com/gorilla/websocket"
+ "github.com/nbutton23/zxcvbn-go"
+ "github.com/theherk/viper"
+ "golang.org/x/crypto/bcrypt"
+ "html/template"
+ "io"
+ "io/ioutil"
+ "log"
+ "math"
+ "net"
+ "net/http"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "reflect"
+ "regexp"
+ "runtime"
+ "runtime/debug"
+ "strconv"
+ "strings"
+ "time"
)
const (
- writeWait = 10 * time.Second
- pongWait = 60 * time.Second
- pingPeriod = (pongWait * 9) / 10
+ writeWait = 10 * time.Second
+ pongWait = 60 * time.Second
+ pingPeriod = (pongWait * 9) / 10
)
var (
- appSession *sessions.Session
- restartSecret string
- sessionStore *sessions.CookieStore
- tmpls *templates.Templates
- version string
- dbConn string
- dbType string
- isDev bool
+ appSession *sessions.Session
+ restartSecret string
+ sessionStore *sessions.CookieStore
+ tmpls *templates.Templates
+ version string
+ dbConn string
+ dbType string
+ isDev bool
- upgrader = websocket.Upgrader{
- ReadBufferSize: 1024,
- WriteBufferSize: 1024,
- }
+ upgrader = websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ }
)
type User struct {
- Name string
- Email string
- Password string
- Confirm string
- NewPassword string
- RequestBase string
- Errors map[string]string
+ Name string
+ Email string
+ Password string
+ Confirm string
+ NewPassword string
+ RequestBase string
+ Errors map[string]string
}
func (reg *User) Validate(isNew bool, isChange bool) bool {
- reg.Errors = make(map[string]string)
+ reg.Errors = make(map[string]string)
- if strings.TrimSpace(reg.Name) == "" {
- reg.Errors["Name"] = "Please enter a user name"
- }
+ if strings.TrimSpace(reg.Name) == "" {
+ reg.Errors["Name"] = "Please enter a user name"
+ }
- if isNew || isChange {
- re := regexp.MustCompile(".+@.+\\..+")
- matched := re.Match([]byte(reg.Email))
- if matched == false {
- reg.Errors["Email"] = "Please enter a valid email address"
- }
- }
+ if isNew || isChange {
+ re := regexp.MustCompile(".+@.+\\..+")
+ matched := re.Match([]byte(reg.Email))
+ if matched == false {
+ reg.Errors["Email"] = "Please enter a valid email address"
+ }
+ }
- blacklist := []string{"labca", "acme", reg.Name}
- if x := strings.Index(reg.Email, "@"); x > 0 {
- blacklist = append(blacklist, reg.Email[:x])
- d := strings.Split(reg.Email[x+1:], ".")
- for i:=0; i 0 {
+ blacklist = append(blacklist, reg.Email[:x])
+ d := strings.Split(reg.Email[x+1:], ".")
+ for i := 0; i < len(d)-1; i++ {
+ blacklist = append(blacklist, d[i])
+ }
+ }
- if strings.TrimSpace(reg.Password) == "" {
- reg.Errors["Password"] = "Please enter a password"
- } else if isNew {
- strength := zxcvbn.PasswordStrength(reg.Password, blacklist).Score
- if strength < 1 {
- reg.Errors["Password"] = "Please pick a stronger, more secure password"
- }
- }
+ if strings.TrimSpace(reg.Password) == "" {
+ reg.Errors["Password"] = "Please enter a password"
+ } else if isNew {
+ strength := zxcvbn.PasswordStrength(reg.Password, blacklist).Score
+ if strength < 1 {
+ reg.Errors["Password"] = "Please pick a stronger, more secure password"
+ }
+ }
- if isNew {
- if strings.TrimSpace(reg.Confirm) == "" {
- reg.Errors["Confirm"] = "Please enter the password again"
- } else if strings.TrimSpace(reg.Confirm) != strings.TrimSpace(reg.Password) {
- reg.Errors["Confirm"] = "Passwords do not match!"
- }
- }
+ if isNew {
+ if strings.TrimSpace(reg.Confirm) == "" {
+ reg.Errors["Confirm"] = "Please enter the password again"
+ } else if strings.TrimSpace(reg.Confirm) != strings.TrimSpace(reg.Password) {
+ reg.Errors["Confirm"] = "Passwords do not match!"
+ }
+ }
- if isChange {
- if strings.TrimSpace(reg.NewPassword) != "" {
- strength := zxcvbn.PasswordStrength(reg.NewPassword, blacklist).Score
- if strength < 1 {
- reg.Errors["NewPassword"] = "Please pick a stronger, more secure password"
- }
+ if isChange {
+ if strings.TrimSpace(reg.NewPassword) != "" {
+ strength := zxcvbn.PasswordStrength(reg.NewPassword, blacklist).Score
+ if strength < 1 {
+ reg.Errors["NewPassword"] = "Please pick a stronger, more secure password"
+ }
- if strings.TrimSpace(reg.Confirm) == "" {
- reg.Errors["Confirm"] = "Please enter the new password again"
- } else if strings.TrimSpace(reg.Confirm) != strings.TrimSpace(reg.NewPassword) {
- reg.Errors["Confirm"] = "New passwords do not match!"
- }
- }
+ if strings.TrimSpace(reg.Confirm) == "" {
+ reg.Errors["Confirm"] = "Please enter the new password again"
+ } else if strings.TrimSpace(reg.Confirm) != strings.TrimSpace(reg.NewPassword) {
+ reg.Errors["Confirm"] = "New passwords do not match!"
+ }
+ }
- byteStored := []byte(viper.GetString("user.password"))
- err := bcrypt.CompareHashAndPassword(byteStored, []byte(reg.Password))
- if err != nil {
- reg.Errors["Password"] = "Current password is not correct!"
- }
- }
+ byteStored := []byte(viper.GetString("user.password"))
+ err := bcrypt.CompareHashAndPassword(byteStored, []byte(reg.Password))
+ if err != nil {
+ reg.Errors["Password"] = "Current password is not correct!"
+ }
+ }
- return len(reg.Errors) == 0
+ return len(reg.Errors) == 0
}
type SetupConfig struct {
- Fqdn string
- Organization string
- Dns string
- DomainMode string
- LockdownDomains string
- WhitelistDomains string
- RequestBase string
- Errors map[string]string
+ Fqdn string
+ Organization string
+ Dns string
+ DomainMode string
+ LockdownDomains string
+ WhitelistDomains string
+ RequestBase string
+ Errors map[string]string
}
func (cfg *SetupConfig) Validate(orgRequired bool) bool {
- cfg.Errors = make(map[string]string)
+ cfg.Errors = make(map[string]string)
- if strings.TrimSpace(cfg.Fqdn) == "" {
- cfg.Errors["Fqdn"] = "Please enter the Fully Qualified Domain name for this host"
- }
+ if strings.TrimSpace(cfg.Fqdn) == "" {
+ cfg.Errors["Fqdn"] = "Please enter the Fully Qualified Domain name for this host"
+ }
- if strings.TrimSpace(cfg.Organization) == "" && orgRequired {
- cfg.Errors["Organization"] = "Please enter the organization name to show on the public pages"
- }
+ if strings.TrimSpace(cfg.Organization) == "" && orgRequired {
+ cfg.Errors["Organization"] = "Please enter the organization name to show on the public pages"
+ }
- if strings.TrimSpace(cfg.Dns) == "" {
- cfg.Errors["Dns"] = "Please enter the DNS server to be used for validation"
- }
+ if strings.TrimSpace(cfg.Dns) == "" {
+ cfg.Errors["Dns"] = "Please enter the DNS server to be used for validation"
+ }
- if cfg.DomainMode != "lockdown" && cfg.DomainMode != "whitelist" && cfg.DomainMode != "standard" {
- cfg.Errors["DomainMode"] = "Please select the domain mode to use"
- }
+ if cfg.DomainMode != "lockdown" && cfg.DomainMode != "whitelist" && cfg.DomainMode != "standard" {
+ cfg.Errors["DomainMode"] = "Please select the domain mode to use"
+ }
- if cfg.DomainMode == "lockdown" && strings.TrimSpace(cfg.LockdownDomains) == "" {
- cfg.Errors["LockdownDomains"] = "Please enter one or more domains that this PKI host is locked down to"
- }
+ if cfg.DomainMode == "lockdown" && strings.TrimSpace(cfg.LockdownDomains) == "" {
+ cfg.Errors["LockdownDomains"] = "Please enter one or more domains that this PKI host is locked down to"
+ }
- if cfg.DomainMode == "whitelist" && strings.TrimSpace(cfg.WhitelistDomains) == "" {
- cfg.Errors["WhitelistDomains"] = "Please enter one or more domains that are whitelisted for this PKI host"
- }
+ if cfg.DomainMode == "whitelist" && strings.TrimSpace(cfg.WhitelistDomains) == "" {
+ cfg.Errors["WhitelistDomains"] = "Please enter one or more domains that are whitelisted for this PKI host"
+ }
- return len(cfg.Errors) == 0
+ return len(cfg.Errors) == 0
}
func getSession(w http.ResponseWriter, r *http.Request) *sessions.Session {
- if appSession != nil {
- return appSession
- }
+ if appSession != nil {
+ return appSession
+ }
- session, err := sessionStore.Get(r, "labca")
- if err != nil {
- // Create new session
- session = sessions.NewSession(sessionStore, "labca")
- session.Save(r, w)
- }
+ session, err := sessionStore.Get(r, "labca")
+ if err != nil {
+ // Create new session
+ session = sessions.NewSession(sessionStore, "labca")
+ session.Save(r, w)
+ }
- appSession = session
- return appSession
+ appSession = session
+ return appSession
}
func errorHandler(w http.ResponseWriter, r *http.Request, err error, status int) {
- log.Printf("errorHandler: %v", err)
+ log.Printf("errorHandler: %v", err)
- w.WriteHeader(status)
+ w.WriteHeader(status)
- pc := make([]uintptr, 15)
- n := runtime.Callers(2, pc)
- frames := runtime.CallersFrames(pc[:n])
- frame, _ := frames.Next()
- //fmt.Printf("%s:%d, %s\n", frame.File, frame.Line, frame.Function)
+ pc := make([]uintptr, 15)
+ n := runtime.Callers(2, pc)
+ frames := runtime.CallersFrames(pc[:n])
+ frame, _ := frames.Next()
+ //fmt.Printf("%s:%d, %s\n", frame.File, frame.Line, frame.Function)
- if frame.Function == "main.render" {
- fmt.Fprintf(w, "Could not render requested page")
- return
- }
+ if frame.Function == "main.render" {
+ fmt.Fprintf(w, "Could not render requested page")
+ return
+ }
- if status == http.StatusNotFound {
- render(w, r, "error", map[string]interface{}{"Message": "That page does not exist"})
- } else {
- lines := strings.Split(string(debug.Stack()), "\n")
- if len(lines) >= 5 {
- lines = append(lines[:0], lines[5:]...)
- }
- fmt.Print(strings.Join(lines, "\n"))
+ if status == http.StatusNotFound {
+ render(w, r, "error", map[string]interface{}{"Message": "That page does not exist"})
+ } else {
+ lines := strings.Split(string(debug.Stack()), "\n")
+ if len(lines) >= 5 {
+ lines = append(lines[:0], lines[5:]...)
+ }
+ fmt.Print(strings.Join(lines, "\n"))
- render(w, r, "error", map[string]interface{}{"Message": "Some unexpected error occurred!"})
- // TODO: send email eventually with info on the error
- }
+ render(w, r, "error", map[string]interface{}{"Message": "Some unexpected error occurred!"})
+ // TODO: send email eventually with info on the error
+ }
}
func rootHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- dashboardData, err := CollectDashboardData(w, r)
- if err == nil {
- render(w, r, "dashboard", dashboardData)
- }
+ dashboardData, err := CollectDashboardData(w, r)
+ if err == nil {
+ render(w, r, "dashboard", dashboardData)
+ }
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
- render(w, r, "about", map[string]interface{}{
- "Title": "About",
- })
+ render(w, r, "about", map[string]interface{}{
+ "Title": "About",
+ })
}
func loginHandler(w http.ResponseWriter, r *http.Request) {
- if viper.Get("user.password") == nil {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if viper.Get("user.password") == nil {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- session := getSession(w, r)
- var bounceUrl string
- if session.Values["bounce"] == nil {
- bounceUrl = "/"
- } else {
- bounceUrl = session.Values["bounce"].(string)
- }
+ session := getSession(w, r)
+ var bounceUrl string
+ if session.Values["bounce"] == nil {
+ bounceUrl = "/"
+ } else {
+ bounceUrl = session.Values["bounce"].(string)
+ }
- if session.Values["user"] != nil {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+bounceUrl, http.StatusFound)
- return
- }
+ if session.Values["user"] != nil {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+bounceUrl, http.StatusFound)
+ return
+ }
- if r.Method == "GET" {
- reg := &User{
- RequestBase: r.Header.Get("X-Request-Base"),
- }
- render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
- return
- } else if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ if r.Method == "GET" {
+ reg := &User{
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+ render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
+ return
+ } else if r.Method == "POST" {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- reg := &User{
- Name: r.Form.Get("username"),
- Password: r.Form.Get("password"),
- RequestBase: r.Header.Get("X-Request-Base"),
- }
+ reg := &User{
+ Name: r.Form.Get("username"),
+ Password: r.Form.Get("password"),
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
- if reg.Validate(false, false) == false {
- render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
- return
- }
+ if reg.Validate(false, false) == false {
+ render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
+ return
+ }
- if viper.GetString("user.name") != reg.Name {
- reg.Errors["Name"] = "Incorrect username or password"
- render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
- return
- } else {
- byteStored := []byte(viper.GetString("user.password"))
- err := bcrypt.CompareHashAndPassword(byteStored, []byte(reg.Password))
- if err != nil {
- log.Println(err)
- reg.Errors["Name"] = "Incorrect username or password"
- render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
- return
- }
- }
+ if viper.GetString("user.name") != reg.Name {
+ reg.Errors["Name"] = "Incorrect username or password"
+ render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
+ return
+ } else {
+ byteStored := []byte(viper.GetString("user.password"))
+ err := bcrypt.CompareHashAndPassword(byteStored, []byte(reg.Password))
+ if err != nil {
+ log.Println(err)
+ reg.Errors["Name"] = "Incorrect username or password"
+ render(w, r, "login", map[string]interface{}{"User": reg, "IsLogin": true})
+ return
+ }
+ }
- session.Values["user"] = reg.Name
- session.Save(r, w)
+ session.Values["user"] = reg.Name
+ session.Save(r, w)
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+bounceUrl, http.StatusFound)
- } else {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/login", http.StatusSeeOther)
- return
- }
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+bounceUrl, http.StatusFound)
+ } else {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/login", http.StatusSeeOther)
+ return
+ }
}
func logoutHandler(w http.ResponseWriter, r *http.Request) {
- appSession = nil
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
+ appSession = nil
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
}
func _sendCmdOutput(w http.ResponseWriter, r *http.Request, cmd string) {
- parts := strings.Fields(cmd)
- for i := 0; i < len(parts); i++ {
- parts[i] = strings.Replace(parts[i], "\\\\", " ", -1)
- }
- head := parts[0]
- parts = parts[1:]
+ parts := strings.Fields(cmd)
+ for i := 0; i < len(parts); i++ {
+ parts[i] = strings.Replace(parts[i], "\\\\", " ", -1)
+ }
+ head := parts[0]
+ parts = parts[1:]
- out, err := exec.Command(head, parts...).Output()
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ out, err := exec.Command(head, parts...).Output()
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- buf := bytes.NewBuffer(out)
- _, err = buf.WriteTo(w)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ buf := bytes.NewBuffer(out)
+ _, err = buf.WriteTo(w)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
}
func _backupHandler(w http.ResponseWriter, r *http.Request) {
- res := struct {
- Success bool
- Message string
- }{Success: true}
+ res := struct {
+ Success bool
+ Message string
+ }{Success: true}
- action := r.Form.Get("action")
- if action == "backup-restore" {
- backup := r.Form.Get("backup")
- if !_hostCommand(w, r, action, backup) {
- res.Success = false
- res.Message = "Command failed - see LabCA log for any details"
- }
+ action := r.Form.Get("action")
+ if action == "backup-restore" {
+ backup := r.Form.Get("backup")
+ if !_hostCommand(w, r, action, backup) {
+ res.Success = false
+ res.Message = "Command failed - see LabCA log for any details"
+ }
- defer _hostCommand(w, r, "server-restart")
- } else if action == "backup-delete" {
- backup := r.Form.Get("backup")
- if !_hostCommand(w, r, action, backup) {
- res.Success = false
- res.Message = "Command failed - see LabCA log for any details"
- }
- } else if action == "backup-now" {
- res.Message = getLog(w, r, "server-backup")
- if res.Message == "" {
- res.Success = false
- res.Message = "Command failed - see LabCA log for any details"
- } else {
- res.Message = filepath.Base(res.Message)
- }
- }
+ defer _hostCommand(w, r, "server-restart")
+ } else if action == "backup-delete" {
+ backup := r.Form.Get("backup")
+ if !_hostCommand(w, r, action, backup) {
+ res.Success = false
+ res.Message = "Command failed - see LabCA log for any details"
+ }
+ } else if action == "backup-now" {
+ res.Message = getLog(w, r, "server-backup")
+ if res.Message == "" {
+ res.Success = false
+ res.Message = "Command failed - see LabCA log for any details"
+ } else {
+ res.Message = filepath.Base(res.Message)
+ }
+ }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(res)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(res)
}
func _accountUpdateHandler(w http.ResponseWriter, r *http.Request) {
- reg := &User{
- Name: r.Form.Get("username"),
- Email: r.Form.Get("email"),
- NewPassword: r.Form.Get("new-password"),
- Confirm: r.Form.Get("confirm"),
- Password: r.Form.Get("password"),
- }
+ reg := &User{
+ Name: r.Form.Get("username"),
+ Email: r.Form.Get("email"),
+ NewPassword: r.Form.Get("new-password"),
+ Confirm: r.Form.Get("confirm"),
+ Password: r.Form.Get("password"),
+ }
- res := struct {
- Success bool
- Errors map[string]string
- }{Success: true}
+ res := struct {
+ Success bool
+ Errors map[string]string
+ }{Success: true}
- if reg.Validate(false, true) {
- viper.Set("user.name", reg.Name)
- viper.Set("user.email", reg.Email)
+ if reg.Validate(false, true) {
+ viper.Set("user.name", reg.Name)
+ viper.Set("user.email", reg.Email)
- if reg.NewPassword != "" {
- hash, err := bcrypt.GenerateFromPassword([]byte(reg.NewPassword), bcrypt.MinCost)
- if err != nil {
- res.Success = false
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
- viper.Set("user.password", string(hash))
+ if reg.NewPassword != "" {
+ hash, err := bcrypt.GenerateFromPassword([]byte(reg.NewPassword), bcrypt.MinCost)
+ if err != nil {
+ res.Success = false
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
+ viper.Set("user.password", string(hash))
- // Forget current session, so user has to login with the new password
- appSession = nil
- }
+ // Forget current session, so user has to login with the new password
+ appSession = nil
+ }
- viper.WriteConfig()
+ viper.WriteConfig()
- } else {
- res.Success = false
- res.Errors = reg.Errors
- }
+ } else {
+ res.Success = false
+ res.Errors = reg.Errors
+ }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(res)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(res)
}
func _configUpdateHandler(w http.ResponseWriter, r *http.Request) {
- cfg := &SetupConfig{
- Fqdn: r.Form.Get("fqdn"),
- Organization: r.Form.Get("organization"),
- Dns: r.Form.Get("dns"),
- DomainMode: r.Form.Get("domain_mode"),
- LockdownDomains: r.Form.Get("lockdown_domains"),
- WhitelistDomains: r.Form.Get("whitelist_domains"),
- }
+ cfg := &SetupConfig{
+ Fqdn: r.Form.Get("fqdn"),
+ Organization: r.Form.Get("organization"),
+ Dns: r.Form.Get("dns"),
+ DomainMode: r.Form.Get("domain_mode"),
+ LockdownDomains: r.Form.Get("lockdown_domains"),
+ WhitelistDomains: r.Form.Get("whitelist_domains"),
+ }
- res := struct {
- Success bool
- Errors map[string]string
- }{Success: true}
+ res := struct {
+ Success bool
+ Errors map[string]string
+ }{Success: true}
- if cfg.Validate(true) {
- delta := false
+ if cfg.Validate(true) {
+ delta := false
- if cfg.Fqdn != viper.GetString("labca.fqdn") {
- delta = true
- viper.Set("labca.fqdn", cfg.Fqdn)
- }
+ if cfg.Fqdn != viper.GetString("labca.fqdn") {
+ delta = true
+ viper.Set("labca.fqdn", cfg.Fqdn)
+ }
- if cfg.Organization != viper.GetString("labca.organization") {
- delta = true
- viper.Set("labca.organization", cfg.Organization)
- }
+ if cfg.Organization != viper.GetString("labca.organization") {
+ delta = true
+ viper.Set("labca.organization", cfg.Organization)
+ }
- matched, err := regexp.MatchString(":\\d+$", cfg.Dns)
- if err == nil && !matched {
- cfg.Dns += ":53"
- }
+ matched, err := regexp.MatchString(":\\d+$", cfg.Dns)
+ if err == nil && !matched {
+ cfg.Dns += ":53"
+ }
- if cfg.Dns != viper.GetString("labca.dns") {
- delta = true
- viper.Set("labca.dns", cfg.Dns)
- }
+ if cfg.Dns != viper.GetString("labca.dns") {
+ delta = true
+ viper.Set("labca.dns", cfg.Dns)
+ }
- domain_mode := cfg.DomainMode
- if domain_mode != viper.GetString("labca.domain_mode") {
- delta = true
- viper.Set("labca.domain_mode", cfg.DomainMode)
- }
+ domain_mode := cfg.DomainMode
+ if domain_mode != viper.GetString("labca.domain_mode") {
+ delta = true
+ viper.Set("labca.domain_mode", cfg.DomainMode)
+ }
- if domain_mode == "lockdown" {
- if cfg.LockdownDomains != viper.GetString("labca.lockdown") {
- delta = true
- viper.Set("labca.lockdown", cfg.LockdownDomains)
- }
- }
- if domain_mode == "whitelist" {
- if cfg.WhitelistDomains != viper.GetString("labca.whitelist") {
- delta = true
- viper.Set("labca.whitelist", cfg.WhitelistDomains)
- }
- }
+ if domain_mode == "lockdown" {
+ if cfg.LockdownDomains != viper.GetString("labca.lockdown") {
+ delta = true
+ viper.Set("labca.lockdown", cfg.LockdownDomains)
+ }
+ }
+ if domain_mode == "whitelist" {
+ if cfg.WhitelistDomains != viper.GetString("labca.whitelist") {
+ delta = true
+ viper.Set("labca.whitelist", cfg.WhitelistDomains)
+ }
+ }
- if delta {
- viper.WriteConfig()
+ if delta {
+ viper.WriteConfig()
- err := _applyConfig()
- if err != nil {
- res.Success = false
- res.Errors = cfg.Errors
- res.Errors["ConfigUpdate"] = "Config apply error: '" + err.Error() + "'"
- }
- } else {
- res.Success = false
- res.Errors = cfg.Errors
- res.Errors["ConfigUpdate"] = "Nothing changed!"
- }
+ err := _applyConfig()
+ if err != nil {
+ res.Success = false
+ res.Errors = cfg.Errors
+ res.Errors["ConfigUpdate"] = "Config apply error: '" + err.Error() + "'"
+ }
+ } else {
+ res.Success = false
+ res.Errors = cfg.Errors
+ res.Errors["ConfigUpdate"] = "Nothing changed!"
+ }
- } else {
- res.Success = false
- res.Errors = cfg.Errors
- }
+ } else {
+ res.Success = false
+ res.Errors = cfg.Errors
+ }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(res)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(res)
}
type EmailConfig struct {
- DoEmail bool
- Server string
- Port string
- EmailUser string
- EmailPwd []byte
- From string
- Errors map[string]string
+ DoEmail bool
+ Server string
+ Port string
+ EmailUser string
+ EmailPwd []byte
+ From string
+ Errors map[string]string
}
func (cfg *EmailConfig) Validate() bool {
- cfg.Errors = make(map[string]string)
+ cfg.Errors = make(map[string]string)
- result, err := _encrypt(cfg.EmailPwd)
- if err == nil {
- cfg.EmailPwd = []byte(result)
- } else {
- cfg.Errors["EmailPwd"] = "Could not encrypt this password: " + err.Error()
- }
+ result, err := _encrypt(cfg.EmailPwd)
+ if err == nil {
+ cfg.EmailPwd = []byte(result)
+ } else {
+ cfg.Errors["EmailPwd"] = "Could not encrypt this password: " + err.Error()
+ }
- if cfg.DoEmail == false {
- return len(cfg.Errors) == 0
- }
+ if cfg.DoEmail == false {
+ return len(cfg.Errors) == 0
+ }
- if strings.TrimSpace(cfg.Server) == "" {
- cfg.Errors["Server"] = "Please enter the email server address"
- }
+ if strings.TrimSpace(cfg.Server) == "" {
+ cfg.Errors["Server"] = "Please enter the email server address"
+ }
- if strings.TrimSpace(cfg.Port) == "" {
- cfg.Errors["Port"] = "Please enter the email server port number"
- }
+ if strings.TrimSpace(cfg.Port) == "" {
+ cfg.Errors["Port"] = "Please enter the email server port number"
+ }
- p, err := strconv.Atoi(cfg.Port)
- if err != nil {
- cfg.Errors["Port"] = "Port number must be numeric"
- } else if p <= 0 {
- cfg.Errors["Port"] = "Port number must be positive"
- } else if p > 65535 {
- cfg.Errors["Port"] = "Port number too large"
- }
+ p, err := strconv.Atoi(cfg.Port)
+ if err != nil {
+ cfg.Errors["Port"] = "Port number must be numeric"
+ } else if p <= 0 {
+ cfg.Errors["Port"] = "Port number must be positive"
+ } else if p > 65535 {
+ cfg.Errors["Port"] = "Port number too large"
+ }
- if strings.TrimSpace(cfg.EmailUser) == "" {
- cfg.Errors["EmailUser"] = "Please enter the username for authorization to the email server"
- }
+ if strings.TrimSpace(cfg.EmailUser) == "" {
+ cfg.Errors["EmailUser"] = "Please enter the username for authorization to the email server"
+ }
- res, err := _decrypt(string(cfg.EmailPwd))
- if err != nil {
- cfg.Errors["EmailPwd"] = "Could not decrypt this password: " + err.Error()
- }
- if strings.TrimSpace(string(res)) == "" {
- cfg.Errors["EmailPwd"] = "Please enter the password for authorization to the email server"
- }
+ res, err := _decrypt(string(cfg.EmailPwd))
+ if err != nil {
+ cfg.Errors["EmailPwd"] = "Could not decrypt this password: " + err.Error()
+ }
+ if strings.TrimSpace(string(res)) == "" {
+ cfg.Errors["EmailPwd"] = "Please enter the password for authorization to the email server"
+ }
- if strings.TrimSpace(cfg.From) == "" {
- cfg.Errors["From"] = "Please enter the from email address"
- }
+ if strings.TrimSpace(cfg.From) == "" {
+ cfg.Errors["From"] = "Please enter the from email address"
+ }
- return len(cfg.Errors) == 0
+ return len(cfg.Errors) == 0
}
-
func _emailUpdateHandler(w http.ResponseWriter, r *http.Request) {
- cfg := &EmailConfig{
- DoEmail: (r.Form.Get("do_email") == "true"),
- Server: r.Form.Get("server"),
- Port: r.Form.Get("port"),
- EmailUser: r.Form.Get("email_user"),
- EmailPwd: []byte(r.Form.Get("email_pwd")),
- From: r.Form.Get("from"),
- }
+ cfg := &EmailConfig{
+ DoEmail: (r.Form.Get("do_email") == "true"),
+ Server: r.Form.Get("server"),
+ Port: r.Form.Get("port"),
+ EmailUser: r.Form.Get("email_user"),
+ EmailPwd: []byte(r.Form.Get("email_pwd")),
+ From: r.Form.Get("from"),
+ }
- res := struct {
- Success bool
- Errors map[string]string
- }{Success: true}
+ res := struct {
+ Success bool
+ Errors map[string]string
+ }{Success: true}
- if cfg.Validate() {
- delta := false
+ if cfg.Validate() {
+ delta := false
- if cfg.DoEmail != viper.GetBool("labca.email.enable") {
- delta = true
- viper.Set("labca.email.enable", cfg.DoEmail)
- }
+ if cfg.DoEmail != viper.GetBool("labca.email.enable") {
+ delta = true
+ viper.Set("labca.email.enable", cfg.DoEmail)
+ }
- if cfg.Server != viper.GetString("labca.email.server") {
- delta = true
- viper.Set("labca.email.server", cfg.Server)
- }
+ if cfg.Server != viper.GetString("labca.email.server") {
+ delta = true
+ viper.Set("labca.email.server", cfg.Server)
+ }
- if cfg.Port != viper.GetString("labca.email.port") {
- delta = true
- viper.Set("labca.email.port", cfg.Port)
- }
+ if cfg.Port != viper.GetString("labca.email.port") {
+ delta = true
+ viper.Set("labca.email.port", cfg.Port)
+ }
- if cfg.EmailUser != viper.GetString("labca.email.user") {
- delta = true
- viper.Set("labca.email.user", cfg.EmailUser)
- }
+ if cfg.EmailUser != viper.GetString("labca.email.user") {
+ delta = true
+ viper.Set("labca.email.user", cfg.EmailUser)
+ }
- res1, err1 := _decrypt(string(cfg.EmailPwd))
- if err1 != nil && cfg.DoEmail {
- log.Println("WARNING: could not decrypt given password: " + err1.Error())
- }
- res2, err2 := _decrypt(viper.GetString("labca.email.pass"))
- if err2 != nil && cfg.DoEmail && viper.GetString("labca.email.pass") != "" {
- log.Println("WARNING: could not decrypt stored password: " + err2.Error())
- }
- if string(res1) != string(res2) {
- delta = true
- viper.Set("labca.email.pass", string(cfg.EmailPwd))
- }
+ res1, err1 := _decrypt(string(cfg.EmailPwd))
+ if err1 != nil && cfg.DoEmail {
+ log.Println("WARNING: could not decrypt given password: " + err1.Error())
+ }
+ res2, err2 := _decrypt(viper.GetString("labca.email.pass"))
+ if err2 != nil && cfg.DoEmail && viper.GetString("labca.email.pass") != "" {
+ log.Println("WARNING: could not decrypt stored password: " + err2.Error())
+ }
+ if string(res1) != string(res2) {
+ delta = true
+ viper.Set("labca.email.pass", string(cfg.EmailPwd))
+ }
- if cfg.From != viper.GetString("labca.email.from") {
- delta = true
- viper.Set("labca.email.from", cfg.From)
- }
+ if cfg.From != viper.GetString("labca.email.from") {
+ delta = true
+ viper.Set("labca.email.from", cfg.From)
+ }
- if delta {
- viper.WriteConfig()
+ if delta {
+ viper.WriteConfig()
- err := _applyConfig()
- if err != nil {
- res.Success = false
- res.Errors = cfg.Errors
- res.Errors["EmailUpdate"] = "Config apply error: '" + err.Error() + "'"
- }
- } else {
- res.Success = false
- res.Errors = cfg.Errors
- res.Errors["EmailUpdate"] = "Nothing changed!"
- }
+ err := _applyConfig()
+ if err != nil {
+ res.Success = false
+ res.Errors = cfg.Errors
+ res.Errors["EmailUpdate"] = "Config apply error: '" + err.Error() + "'"
+ }
+ } else {
+ res.Success = false
+ res.Errors = cfg.Errors
+ res.Errors["EmailUpdate"] = "Nothing changed!"
+ }
- } else {
- res.Success = false
- res.Errors = cfg.Errors
- }
+ } else {
+ res.Success = false
+ res.Errors = cfg.Errors
+ }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(res)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(res)
}
func _emailSendHandler(w http.ResponseWriter, r *http.Request) {
- res := struct {
- Success bool
- Errors map[string]string
- }{Success: true, Errors: make(map[string]string)}
+ res := struct {
+ Success bool
+ Errors map[string]string
+ }{Success: true, Errors: make(map[string]string)}
- recipient := viper.GetString("user.email")
- if !_hostCommand(w, r, "test-email", recipient) {
- res.Success = false
- res.Errors["EmailSend"] = "Failed to send email - see logs"
- }
+ recipient := viper.GetString("user.email")
+ if !_hostCommand(w, r, "test-email", recipient) {
+ res.Success = false
+ res.Errors["EmailSend"] = "Failed to send email - see logs"
+ }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(res)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(res)
}
func _exportHandler(w http.ResponseWriter, r *http.Request) {
- basename := "certificates"
- if r.Form.Get("root") != "true" {
- basename = "issuer"
- }
- if r.Form.Get("issuer") != "true" {
- basename = "root"
- }
+ basename := "certificates"
+ if r.Form.Get("root") != "true" {
+ basename = "issuer"
+ }
+ if r.Form.Get("issuer") != "true" {
+ basename = "root"
+ }
- if r.Form.Get("type") == "pfx" {
- w.Header().Set("Content-Type", "application/x-pkcs12")
- w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".pfx")
+ if r.Form.Get("type") == "pfx" {
+ w.Header().Set("Content-Type", "application/x-pkcs12")
+ w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".pfx")
- var certBase string
- if basename == "root" {
- certBase = "data/root-ca"
- } else {
- certBase = "data/issuer/ca-int"
- }
+ var certBase string
+ if basename == "root" {
+ certBase = "data/root-ca"
+ } else {
+ certBase = "data/issuer/ca-int"
+ }
- cmd := "openssl pkcs12 -export -inkey " + certBase + ".key -in " + certBase + ".pem -passout pass:" + r.Form.Get("export-pwd")
+ cmd := "openssl pkcs12 -export -inkey " + certBase + ".key -in " + certBase + ".pem -passout pass:" + r.Form.Get("export-pwd")
- _sendCmdOutput(w, r, cmd)
- }
+ _sendCmdOutput(w, r, cmd)
+ }
- if r.Form.Get("type") == "zip" {
- w.Header().Set("Content-Type", "application/zip")
- w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".zip")
+ if r.Form.Get("type") == "zip" {
+ w.Header().Set("Content-Type", "application/zip")
+ w.Header().Set("Content-Disposition", "attachment; filename=labca_"+basename+".zip")
- cmd := "zip -j -P " + r.Form.Get("export-pwd") + " - "
- var certBase string
- if r.Form.Get("root") == "true" {
- certBase = "data/root-ca"
- cmd = cmd + certBase + ".key " + certBase + ".pem "
- }
- if r.Form.Get("issuer") == "true" {
- certBase = "data/issuer/ca-int"
- cmd = cmd + certBase + ".key " + certBase + ".pem "
- }
+ cmd := "zip -j -P " + r.Form.Get("export-pwd") + " - "
+ var certBase string
+ if r.Form.Get("root") == "true" {
+ certBase = "data/root-ca"
+ cmd = cmd + certBase + ".key " + certBase + ".pem "
+ }
+ if r.Form.Get("issuer") == "true" {
+ certBase = "data/issuer/ca-int"
+ cmd = cmd + certBase + ".key " + certBase + ".pem "
+ }
- _sendCmdOutput(w, r, cmd)
- }
+ _sendCmdOutput(w, r, cmd)
+ }
}
func _doCmdOutput(w http.ResponseWriter, r *http.Request, cmd string) string {
- parts := strings.Fields(cmd)
- for i := 0; i < len(parts); i++ {
- parts[i] = strings.Replace(parts[i], "\\\\", " ", -1)
- }
- head := parts[0]
- parts = parts[1:]
+ parts := strings.Fields(cmd)
+ for i := 0; i < len(parts); i++ {
+ parts[i] = strings.Replace(parts[i], "\\\\", " ", -1)
+ }
+ head := parts[0]
+ parts = parts[1:]
- out, err := exec.Command(head, parts...).Output()
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ""
- }
+ out, err := exec.Command(head, parts...).Output()
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ""
+ }
- return string(out)
+ return string(out)
}
func _encrypt(plaintext []byte) (string, error) {
- key := []byte(viper.GetString("keys.enc"))
- block, err := aes.NewCipher(key[:32])
- if err != nil {
- return "", err
- }
+ key := []byte(viper.GetString("keys.enc"))
+ block, err := aes.NewCipher(key[:32])
+ if err != nil {
+ return "", err
+ }
- gcm, err := cipher.NewGCM(block)
- if err != nil {
- return "", err
- }
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return "", err
+ }
- nonce := make([]byte, gcm.NonceSize())
- _, err = io.ReadFull(rand.Reader, nonce)
- if err != nil {
- return "", err
- }
+ nonce := make([]byte, gcm.NonceSize())
+ _, err = io.ReadFull(rand.Reader, nonce)
+ if err != nil {
+ return "", err
+ }
- return base64.StdEncoding.EncodeToString(gcm.Seal(nonce, nonce, plaintext, nil)), nil
+ return base64.StdEncoding.EncodeToString(gcm.Seal(nonce, nonce, plaintext, nil)), nil
}
func _decrypt(ciphertext string) ([]byte, error) {
- key := []byte(viper.GetString("keys.enc"))
- block, err := aes.NewCipher(key[:32])
- if err != nil {
- return nil, err
- }
+ key := []byte(viper.GetString("keys.enc"))
+ block, err := aes.NewCipher(key[:32])
+ if err != nil {
+ return nil, err
+ }
- gcm, err := cipher.NewGCM(block)
- if err != nil {
- return nil, err
- }
+ gcm, err := cipher.NewGCM(block)
+ if err != nil {
+ return nil, err
+ }
- ct, err := base64.StdEncoding.DecodeString(ciphertext)
- if err != nil {
- return nil, err
- }
+ ct, err := base64.StdEncoding.DecodeString(ciphertext)
+ if err != nil {
+ return nil, err
+ }
- if len(ct) < gcm.NonceSize() {
- return nil, errors.New("malformed ciphertext")
- }
+ if len(ct) < gcm.NonceSize() {
+ return nil, errors.New("malformed ciphertext")
+ }
- return gcm.Open(nil, ct[:gcm.NonceSize()], ct[gcm.NonceSize():], nil,)
+ return gcm.Open(nil, ct[:gcm.NonceSize()], ct[gcm.NonceSize():], nil)
}
func manageHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ if r.Method == "POST" {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- action := r.Form.Get("action")
- switch action {
- case "backup-restore":
- case "backup-delete":
- case "backup-now":
- case "cert-export":
- case "nginx-reload":
- case "nginx-restart":
- case "svc-restart":
- case "boulder-start":
- case "boulder-stop":
- case "boulder-restart":
- case "labca-restart":
- case "server-restart":
- case "server-shutdown":
- case "update-account":
- case "update-config":
- case "update-email":
- case "send-email":
- default:
- errorHandler(w, r, errors.New(fmt.Sprintf("Unknown manage action '%s'", action)), http.StatusBadRequest)
- return
- }
+ action := r.Form.Get("action")
+ switch action {
+ case "backup-restore":
+ case "backup-delete":
+ case "backup-now":
+ case "cert-export":
+ case "nginx-reload":
+ case "nginx-restart":
+ case "svc-restart":
+ case "boulder-start":
+ case "boulder-stop":
+ case "boulder-restart":
+ case "labca-restart":
+ case "server-restart":
+ case "server-shutdown":
+ case "update-account":
+ case "update-config":
+ case "update-email":
+ case "send-email":
+ default:
+ errorHandler(w, r, errors.New(fmt.Sprintf("Unknown manage action '%s'", action)), http.StatusBadRequest)
+ return
+ }
- if action == "backup-restore" || action == "backup-delete" || action == "backup-now" {
- _backupHandler(w, r)
- return
- }
+ if action == "backup-restore" || action == "backup-delete" || action == "backup-now" {
+ _backupHandler(w, r)
+ return
+ }
- if action == "cert-export" {
- _exportHandler(w, r)
- return
- }
+ if action == "cert-export" {
+ _exportHandler(w, r)
+ return
+ }
- if action == "update-account" {
- _accountUpdateHandler(w, r)
- return
- }
+ if action == "update-account" {
+ _accountUpdateHandler(w, r)
+ return
+ }
- if action == "update-config" {
- _configUpdateHandler(w, r)
- return
- }
+ if action == "update-config" {
+ _configUpdateHandler(w, r)
+ return
+ }
- if action == "update-email" {
- _emailUpdateHandler(w, r)
- return
- }
+ if action == "update-email" {
+ _emailUpdateHandler(w, r)
+ return
+ }
- if action == "send-email" {
- _emailSendHandler(w, r)
- return
- }
+ if action == "send-email" {
+ _emailSendHandler(w, r)
+ return
+ }
- res := struct {
- Success bool
- Message string
- Timestamp string
- TimestampRel string
- Class string
- }{Success: true}
- if !_hostCommand(w, r, action) {
- res.Success = false
- res.Message = "Command failed - see LabCA log for any details"
- }
+ res := struct {
+ Success bool
+ Message string
+ Timestamp string
+ TimestampRel string
+ Class string
+ }{Success: true}
+ if !_hostCommand(w, r, action) {
+ res.Success = false
+ res.Message = "Command failed - see LabCA log for any details"
+ }
- if action != "server-restart" && action != "server-shutdown" {
- components := _parseComponents(getLog(w, r, "components"))
- for i := 0; i < len(components); i++ {
- if (components[i].Name == "NGINX Webserver" && (action == "nginx-reload" || action == "nginx-restart")) ||
- (components[i].Name == "Host Service" && action == "svc-restart") ||
- (components[i].Name == "Boulder (ACME)" && (action == "boulder-start" || action == "boulder-stop" || action == "boulder-restart")) ||
- (components[i].Name == "LabCA Application" && action == "labca-restart") {
- res.Timestamp = components[i].Timestamp
- res.TimestampRel = components[i].TimestampRel
- res.Class = components[i].Class
- break
- }
- }
- }
+ if action != "server-restart" && action != "server-shutdown" {
+ components := _parseComponents(getLog(w, r, "components"))
+ for i := 0; i < len(components); i++ {
+ if (components[i].Name == "NGINX Webserver" && (action == "nginx-reload" || action == "nginx-restart")) ||
+ (components[i].Name == "Host Service" && action == "svc-restart") ||
+ (components[i].Name == "Boulder (ACME)" && (action == "boulder-start" || action == "boulder-stop" || action == "boulder-restart")) ||
+ (components[i].Name == "LabCA Application" && action == "labca-restart") {
+ res.Timestamp = components[i].Timestamp
+ res.TimestampRel = components[i].TimestampRel
+ res.Class = components[i].Class
+ break
+ }
+ }
+ }
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(res)
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(res)
- } else {
- manageData := make(map[string]interface{})
- manageData["RequestBase"] = r.Header.Get("X-Request-Base")
+ } else {
+ manageData := make(map[string]interface{})
+ manageData["RequestBase"] = r.Header.Get("X-Request-Base")
- components := _parseComponents(getLog(w, r, "components"))
- for i := 0; i < len(components); i++ {
- if components[i].Name == "NGINX Webserver" {
- components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/weberr"
- components[i].LogTitle = "Web Error Log"
+ components := _parseComponents(getLog(w, r, "components"))
+ for i := 0; i < len(components); i++ {
+ if components[i].Name == "NGINX Webserver" {
+ components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/weberr"
+ components[i].LogTitle = "Web Error Log"
- btn := make(map[string]interface{})
- btn["Class"] = "btn-info"
- btn["Id"] = "nginx-reload"
- btn["Title"] = "Reload web server configuration with minimal impact to the users"
- btn["Label"] = "Reload"
- components[i].Buttons = append(components[i].Buttons, btn)
+ btn := make(map[string]interface{})
+ btn["Class"] = "btn-info"
+ btn["Id"] = "nginx-reload"
+ btn["Title"] = "Reload web server configuration with minimal impact to the users"
+ btn["Label"] = "Reload"
+ components[i].Buttons = append(components[i].Buttons, btn)
- btn = make(map[string]interface{})
- btn["Class"] = "btn-warning"
- btn["Id"] = "nginx-restart"
- btn["Title"] = "Restart the web server with some downtime for the users"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
+ btn = make(map[string]interface{})
+ btn["Class"] = "btn-warning"
+ btn["Id"] = "nginx-restart"
+ btn["Title"] = "Restart the web server with some downtime for the users"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
- if components[i].Name == "Host Service" {
- components[i].LogUrl = ""
- components[i].LogTitle = ""
+ if components[i].Name == "Host Service" {
+ components[i].LogUrl = ""
+ components[i].LogTitle = ""
- btn := make(map[string]interface{})
- btn["Class"] = "btn-warning"
- btn["Id"] = "svc-restart"
- btn["Title"] = "Restart the host service"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
+ btn := make(map[string]interface{})
+ btn["Class"] = "btn-warning"
+ btn["Id"] = "svc-restart"
+ btn["Title"] = "Restart the host service"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
- if components[i].Name == "Boulder (ACME)" {
- components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/boulder"
- components[i].LogTitle = "ACME Log"
+ if components[i].Name == "Boulder (ACME)" {
+ components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/boulder"
+ components[i].LogTitle = "ACME Log"
- btn := make(map[string]interface{})
- cls := "btn-success"
- if components[i].TimestampRel != "stopped" {
- cls = cls + " hidden"
- }
- btn["Class"] = cls
- btn["Id"] = "boulder-start"
- btn["Title"] = "Start the core ACME application"
- btn["Label"] = "Start"
- components[i].Buttons = append(components[i].Buttons, btn)
+ btn := make(map[string]interface{})
+ cls := "btn-success"
+ if components[i].TimestampRel != "stopped" {
+ cls = cls + " hidden"
+ }
+ btn["Class"] = cls
+ btn["Id"] = "boulder-start"
+ btn["Title"] = "Start the core ACME application"
+ btn["Label"] = "Start"
+ components[i].Buttons = append(components[i].Buttons, btn)
- btn = make(map[string]interface{})
- cls = "btn-warning"
- if components[i].TimestampRel == "stopped" {
- cls = cls + " hidden"
- }
- btn["Class"] = cls
- btn["Id"] = "boulder-restart"
- btn["Title"] = "Stop and restart the core ACME application"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
+ btn = make(map[string]interface{})
+ cls = "btn-warning"
+ if components[i].TimestampRel == "stopped" {
+ cls = cls + " hidden"
+ }
+ btn["Class"] = cls
+ btn["Id"] = "boulder-restart"
+ btn["Title"] = "Stop and restart the core ACME application"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
- btn = make(map[string]interface{})
- cls = "btn-danger"
- if components[i].TimestampRel == "stopped" {
- cls = cls + " hidden"
- }
- btn["Class"] = cls
- btn["Id"] = "boulder-stop"
- btn["Title"] = "Stop the core ACME application; users can no longer use ACME clients to interact with this instance"
- btn["Label"] = "Stop"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
+ btn = make(map[string]interface{})
+ cls = "btn-danger"
+ if components[i].TimestampRel == "stopped" {
+ cls = cls + " hidden"
+ }
+ btn["Class"] = cls
+ btn["Id"] = "boulder-stop"
+ btn["Title"] = "Stop the core ACME application; users can no longer use ACME clients to interact with this instance"
+ btn["Label"] = "Stop"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
- if components[i].Name == "LabCA Application" {
- components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/labca"
- components[i].LogTitle = "LabCA Log"
+ if components[i].Name == "LabCA Application" {
+ components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/labca"
+ components[i].LogTitle = "LabCA Log"
- btn := make(map[string]interface{})
- btn["Class"] = "btn-warning"
- btn["Id"] = "labca-restart"
- btn["Title"] = "Stop and restart this LabCA admin application"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
- }
- manageData["Components"] = components
+ btn := make(map[string]interface{})
+ btn["Class"] = "btn-warning"
+ btn["Id"] = "labca-restart"
+ btn["Title"] = "Stop and restart this LabCA admin application"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
+ }
+ manageData["Components"] = components
- stats := _parseStats(getLog(w, r, "stats"))
- for _, stat := range stats {
- if stat.Name == "System Uptime" {
- manageData["ServerTimestamp"] = stat.Hint
- manageData["ServerTimestampRel"] = stat.Value
- break
- }
- }
+ stats := _parseStats(getLog(w, r, "stats"))
+ for _, stat := range stats {
+ if stat.Name == "System Uptime" {
+ manageData["ServerTimestamp"] = stat.Hint
+ manageData["ServerTimestampRel"] = stat.Value
+ break
+ }
+ }
- backupFiles := strings.Split(getLog(w, r, "backups"), "\n")
- backupFiles = backupFiles[:len(backupFiles)-1]
- manageData["BackupFiles"] = backupFiles
+ backupFiles := strings.Split(getLog(w, r, "backups"), "\n")
+ backupFiles = backupFiles[:len(backupFiles)-1]
+ manageData["BackupFiles"] = backupFiles
- manageData["RootDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/root-ca.pem")
- manageData["IssuerDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/issuer/ca-int.pem")
+ manageData["RootDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/root-ca.pem")
+ manageData["IssuerDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/issuer/ca-int.pem")
- manageData["Fqdn"] = viper.GetString("labca.fqdn")
- manageData["Organization"] = viper.GetString("labca.organization")
- manageData["Dns"] = viper.GetString("labca.dns")
- domain_mode := viper.GetString("labca.domain_mode")
- manageData["DomainMode"] = domain_mode
- if domain_mode == "lockdown" {
- manageData["LockdownDomains"] = viper.GetString("labca.lockdown")
- }
- if domain_mode == "whitelist" {
- manageData["WhitelistDomains"] = viper.GetString("labca.whitelist")
- }
+ manageData["Fqdn"] = viper.GetString("labca.fqdn")
+ manageData["Organization"] = viper.GetString("labca.organization")
+ manageData["Dns"] = viper.GetString("labca.dns")
+ domain_mode := viper.GetString("labca.domain_mode")
+ manageData["DomainMode"] = domain_mode
+ if domain_mode == "lockdown" {
+ manageData["LockdownDomains"] = viper.GetString("labca.lockdown")
+ }
+ if domain_mode == "whitelist" {
+ manageData["WhitelistDomains"] = viper.GetString("labca.whitelist")
+ }
- manageData["DoEmail"] = viper.GetBool("labca.email.enable")
- manageData["Server"] = viper.GetString("labca.email.server")
- manageData["Port"] = viper.GetInt("labca.email.port")
- manageData["EmailUser"] = viper.GetString("labca.email.user")
- manageData["EmailPwd"] = ""
- if viper.Get("labca.email.pass") != nil {
- pwd := viper.GetString("labca.email.pass")
- result, err := _decrypt(pwd)
- if err == nil {
- manageData["EmailPwd"] = string(result)
- } else {
- log.Printf("WARNING: could not decrypt email password: %s!\n", err.Error())
- }
- }
- manageData["From"] = viper.GetString("labca.email.from")
+ manageData["DoEmail"] = viper.GetBool("labca.email.enable")
+ manageData["Server"] = viper.GetString("labca.email.server")
+ manageData["Port"] = viper.GetInt("labca.email.port")
+ manageData["EmailUser"] = viper.GetString("labca.email.user")
+ manageData["EmailPwd"] = ""
+ if viper.Get("labca.email.pass") != nil {
+ pwd := viper.GetString("labca.email.pass")
+ result, err := _decrypt(pwd)
+ if err == nil {
+ manageData["EmailPwd"] = string(result)
+ } else {
+ log.Printf("WARNING: could not decrypt email password: %s!\n", err.Error())
+ }
+ }
+ manageData["From"] = viper.GetString("labca.email.from")
- manageData["Name"] = viper.GetString("user.name")
- manageData["Email"] = viper.GetString("user.email")
+ manageData["Name"] = viper.GetString("user.name")
+ manageData["Email"] = viper.GetString("user.email")
- render(w, r, "manage", manageData)
- }
+ render(w, r, "manage", manageData)
+ }
}
func logsHandler(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- logType := vars["type"]
+ vars := mux.Vars(r)
+ logType := vars["type"]
- proto := "ws"
- if r.Header.Get("X-Forwarded-Proto") == "https" {
- proto = "wss"
- }
+ proto := "ws"
+ if r.Header.Get("X-Forwarded-Proto") == "https" {
+ proto = "wss"
+ }
- wsurl := proto + "://" + r.Host + r.Header.Get("X-Request-Base") + "/ws?logType=" + logType
+ wsurl := proto + "://" + r.Host + r.Header.Get("X-Request-Base") + "/ws?logType=" + logType
- var name string
- var message string
- var data string
+ var name string
+ var message string
+ var data string
- switch logType {
- case "cert":
- name = "Web Certificate Log"
- message = "Log file for the certificate renewal for this server."
- wsurl = ""
- data = getLog(w, r, logType)
- case "boulder":
- name = "ACME Backend Log"
- message = "Live view on the backend ACME application (Boulder) logs."
- case "audit":
- name = "ACME Audit Log"
- message = "Live view on only the audit messages in the backend ACME application (Boulder) logs."
- case "labca":
- name = "LabCA Log"
- message = "Live view on the logs for this LabCA web application."
- case "web":
- name = "Web Access Log"
- message = "Live view on the NGINX web server access log."
- case "weberr":
- name = "Web Error Log"
- message = "Log file for the NGINX web server error log."
- wsurl = ""
- data = getLog(w, r, logType)
- default:
- errorHandler(w, r, errors.New(fmt.Sprintf("Unknown log type '%s'", logType)), http.StatusBadRequest)
- return
- }
+ switch logType {
+ case "cert":
+ name = "Web Certificate Log"
+ message = "Log file for the certificate renewal for this server."
+ wsurl = ""
+ data = getLog(w, r, logType)
+ case "boulder":
+ name = "ACME Backend Log"
+ message = "Live view on the backend ACME application (Boulder) logs."
+ case "audit":
+ name = "ACME Audit Log"
+ message = "Live view on only the audit messages in the backend ACME application (Boulder) logs."
+ case "labca":
+ name = "LabCA Log"
+ message = "Live view on the logs for this LabCA web application."
+ case "web":
+ name = "Web Access Log"
+ message = "Live view on the NGINX web server access log."
+ case "weberr":
+ name = "Web Error Log"
+ message = "Log file for the NGINX web server error log."
+ wsurl = ""
+ data = getLog(w, r, logType)
+ default:
+ errorHandler(w, r, errors.New(fmt.Sprintf("Unknown log type '%s'", logType)), http.StatusBadRequest)
+ return
+ }
- render(w, r, "logs", map[string]interface{}{
- "Name": name,
- "Message": message,
- "Data": data,
- "WsUrl": wsurl,
- })
+ render(w, r, "logs", map[string]interface{}{
+ "Name": name,
+ "Message": message,
+ "Data": data,
+ "WsUrl": wsurl,
+ })
}
func getLog(w http.ResponseWriter, r *http.Request, logType string) string {
- ip, err := _discoverGateway()
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ""
- }
+ ip, err := _discoverGateway()
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ""
+ }
- conn, err := net.Dial("tcp", ip.String()+":3030")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ""
- }
+ conn, err := net.Dial("tcp", ip.String()+":3030")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ""
+ }
- defer conn.Close()
+ defer conn.Close()
- fmt.Fprintf(conn, "log-"+logType+"\n")
- reader := bufio.NewReader(conn)
- contents, err := ioutil.ReadAll(reader)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return ""
- }
+ fmt.Fprintf(conn, "log-"+logType+"\n")
+ reader := bufio.NewReader(conn)
+ contents, err := ioutil.ReadAll(reader)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return ""
+ }
- return string(contents)
+ return string(contents)
}
func wsErrorHandler(err error) {
- log.Printf("wsErrorHandler: %v", err)
+ log.Printf("wsErrorHandler: %v", err)
- pc := make([]uintptr, 15)
- n := runtime.Callers(2, pc)
- frames := runtime.CallersFrames(pc[:n])
- frame, _ := frames.Next()
- fmt.Printf("%s:%d, %s\n", frame.File, frame.Line, frame.Function)
+ pc := make([]uintptr, 15)
+ n := runtime.Callers(2, pc)
+ frames := runtime.CallersFrames(pc[:n])
+ frame, _ := frames.Next()
+ fmt.Printf("%s:%d, %s\n", frame.File, frame.Line, frame.Function)
- debug.PrintStack()
+ debug.PrintStack()
}
func showLog(ws *websocket.Conn, logType string) {
- ip, err := _discoverGateway()
- if err != nil {
- wsErrorHandler(err)
- return
- }
+ ip, err := _discoverGateway()
+ if err != nil {
+ wsErrorHandler(err)
+ return
+ }
- conn, err := net.Dial("tcp", ip.String()+":3030")
- if err != nil {
- wsErrorHandler(err)
- return
- }
+ conn, err := net.Dial("tcp", ip.String()+":3030")
+ if err != nil {
+ wsErrorHandler(err)
+ return
+ }
- defer conn.Close()
+ defer conn.Close()
- fmt.Fprintf(conn, "log-"+logType+"\n")
- scanner := bufio.NewScanner(conn)
- for scanner.Scan() {
- msg := scanner.Text()
- if logType != "audit" || strings.Index(msg, "[AUDIT]") > -1 {
- ws.SetWriteDeadline(time.Now().Add(writeWait))
- if err := ws.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
- // Probably "websocket: close sent"
- return
- }
- }
- }
- if err := scanner.Err(); err != nil {
- wsErrorHandler(err)
- return
- }
+ fmt.Fprintf(conn, "log-"+logType+"\n")
+ scanner := bufio.NewScanner(conn)
+ for scanner.Scan() {
+ msg := scanner.Text()
+ if logType != "audit" || strings.Index(msg, "[AUDIT]") > -1 {
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.TextMessage, []byte(msg)); err != nil {
+ // Probably "websocket: close sent"
+ return
+ }
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ wsErrorHandler(err)
+ return
+ }
- return
+ return
}
func reader(ws *websocket.Conn) {
- defer ws.Close()
- ws.SetReadLimit(512)
- ws.SetReadDeadline(time.Now().Add(pongWait))
- ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
- for {
- _, _, err := ws.ReadMessage()
- if err != nil {
- break
- }
- }
+ defer ws.Close()
+ ws.SetReadLimit(512)
+ ws.SetReadDeadline(time.Now().Add(pongWait))
+ ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
+ for {
+ _, _, err := ws.ReadMessage()
+ if err != nil {
+ break
+ }
+ }
}
func writer(ws *websocket.Conn, logType string) {
- pingTicker := time.NewTicker(pingPeriod)
- defer func() {
- pingTicker.Stop()
- ws.Close()
- }()
+ pingTicker := time.NewTicker(pingPeriod)
+ defer func() {
+ pingTicker.Stop()
+ ws.Close()
+ }()
- go showLog(ws, logType)
+ go showLog(ws, logType)
- for {
- select {
- case <-pingTicker.C:
- ws.SetWriteDeadline(time.Now().Add(writeWait))
- if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
- // Probably "websocket: close sent"
- return
- }
- }
- }
+ for {
+ select {
+ case <-pingTicker.C:
+ ws.SetWriteDeadline(time.Now().Add(writeWait))
+ if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
+ // Probably "websocket: close sent"
+ return
+ }
+ }
+ }
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
- ws, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- if _, ok := err.(websocket.HandshakeError); !ok {
- log.Println(err)
- }
- return
- }
+ ws, err := upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ if _, ok := err.(websocket.HandshakeError); !ok {
+ log.Println(err)
+ }
+ return
+ }
- logType := r.FormValue("logType")
+ logType := r.FormValue("logType")
- switch logType {
- case "boulder":
- case "audit":
- case "labca":
- case "web":
- default:
- errorHandler(w, r, errors.New(fmt.Sprintf("Unknown log type '%s'", logType)), http.StatusBadRequest)
- return
- }
+ switch logType {
+ case "boulder":
+ case "audit":
+ case "labca":
+ case "web":
+ default:
+ errorHandler(w, r, errors.New(fmt.Sprintf("Unknown log type '%s'", logType)), http.StatusBadRequest)
+ return
+ }
- go writer(ws, logType)
- reader(ws)
+ go writer(ws, logType)
+ reader(ws)
}
func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot bool) bool {
- path := "data/"
- if !isRoot {
- path = path + "issuer/"
- }
+ path := "data/"
+ if !isRoot {
+ path = path + "issuer/"
+ }
- if _, err := os.Stat(path + certBase + ".pem"); os.IsNotExist(err) {
- session := getSession(w, r)
+ if _, err := os.Stat(path + certBase + ".pem"); os.IsNotExist(err) {
+ session := getSession(w, r)
- if r.Method == "GET" {
- ci := &CertificateInfo{
- IsRoot: isRoot,
- CreateType: "generate",
- CommonName: "Root CA",
- RequestBase: r.Header.Get("X-Request-Base"),
- }
- if !isRoot {
- ci.CommonName = "CA"
- }
- ci.Initialize()
+ if r.Method == "GET" {
+ ci := &CertificateInfo{
+ IsRoot: isRoot,
+ CreateType: "generate",
+ CommonName: "Root CA",
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+ if !isRoot {
+ ci.CommonName = "CA"
+ }
+ ci.Initialize()
- if session.Values["ct"] != nil {
- ci.CreateType = session.Values["ct"].(string)
- }
- if session.Values["kt"] != nil {
- ci.KeyType = session.Values["kt"].(string)
- }
- if session.Values["c"] != nil {
- ci.Country = session.Values["c"].(string)
- }
- if session.Values["o"] != nil {
- ci.Organization = session.Values["o"].(string)
- }
- if session.Values["ou"] != nil {
- ci.OrgUnit = session.Values["ou"].(string)
- }
- if session.Values["cn"] != nil {
- ci.CommonName = session.Values["cn"].(string)
- ci.CommonName = strings.Replace(ci.CommonName, "Root", "", -1)
- ci.CommonName = strings.Replace(ci.CommonName, " ", " ", -1)
- }
+ if session.Values["ct"] != nil {
+ ci.CreateType = session.Values["ct"].(string)
+ }
+ if session.Values["kt"] != nil {
+ ci.KeyType = session.Values["kt"].(string)
+ }
+ if session.Values["c"] != nil {
+ ci.Country = session.Values["c"].(string)
+ }
+ if session.Values["o"] != nil {
+ ci.Organization = session.Values["o"].(string)
+ }
+ if session.Values["ou"] != nil {
+ ci.OrgUnit = session.Values["ou"].(string)
+ }
+ if session.Values["cn"] != nil {
+ ci.CommonName = session.Values["cn"].(string)
+ ci.CommonName = strings.Replace(ci.CommonName, "Root", "", -1)
+ ci.CommonName = strings.Replace(ci.CommonName, " ", " ", -1)
+ }
- render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
- return false
- } else if r.Method == "POST" {
- if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return false
- }
+ render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
+ return false
+ } else if r.Method == "POST" {
+ if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
- ci := &CertificateInfo{}
- ci.Initialize()
- ci.IsRoot = r.Form.Get("cert") == "root"
- ci.CreateType = r.Form.Get("createtype")
+ ci := &CertificateInfo{}
+ ci.Initialize()
+ ci.IsRoot = r.Form.Get("cert") == "root"
+ ci.CreateType = r.Form.Get("createtype")
- if r.Form.Get("keytype") != "" {
- ci.KeyType = r.Form.Get("keytype")
- }
- ci.Country = r.Form.Get("c")
- ci.Organization = r.Form.Get("o")
- ci.OrgUnit = r.Form.Get("ou")
- ci.CommonName = r.Form.Get("cn")
+ if r.Form.Get("keytype") != "" {
+ ci.KeyType = r.Form.Get("keytype")
+ }
+ ci.Country = r.Form.Get("c")
+ ci.Organization = r.Form.Get("o")
+ ci.OrgUnit = r.Form.Get("ou")
+ ci.CommonName = r.Form.Get("cn")
- if ci.CreateType == "import" {
- file, handler, err := r.FormFile("import")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return false
- }
+ if ci.CreateType == "import" {
+ file, handler, err := r.FormFile("import")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
- defer file.Close()
+ defer file.Close()
- ci.ImportFile = file
- ci.ImportHandler = handler
- ci.ImportPwd = r.Form.Get("import-pwd")
- }
+ ci.ImportFile = file
+ ci.ImportHandler = handler
+ ci.ImportPwd = r.Form.Get("import-pwd")
+ }
- ci.Key = r.Form.Get("key")
- ci.Passphrase = r.Form.Get("passphrase")
- ci.Certificate = r.Form.Get("certificate")
- ci.RequestBase = r.Header.Get("X-Request-Base")
+ ci.Key = r.Form.Get("key")
+ ci.Passphrase = r.Form.Get("passphrase")
+ ci.Certificate = r.Form.Get("certificate")
+ ci.RequestBase = r.Header.Get("X-Request-Base")
- if ci.Validate() == false {
- render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
- return false
- }
+ if ci.Validate() == false {
+ render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
+ return false
+ }
- if err := ci.Create(path, certBase); err != nil {
- ci.Errors[strings.Title(ci.CreateType)] = err.Error()
- log.Printf("_certCreate: create failed: %v", err)
- render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
- return false
- }
+ if err := ci.Create(path, certBase); err != nil {
+ ci.Errors[strings.Title(ci.CreateType)] = err.Error()
+ log.Printf("_certCreate: create failed: %v", err)
+ render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
+ return false
+ }
- if viper.Get("labca.organization") == nil {
- viper.Set("labca.organization", ci.Organization)
- viper.WriteConfig()
- }
+ if viper.Get("labca.organization") == nil {
+ viper.Set("labca.organization", ci.Organization)
+ viper.WriteConfig()
+ }
- session.Values["ct"] = ci.CreateType
- session.Values["kt"] = ci.KeyType
- session.Values["c"] = ci.Country
- session.Values["o"] = ci.Organization
- session.Values["ou"] = ci.OrgUnit
- session.Values["cn"] = ci.CommonName
- session.Save(r, w)
+ session.Values["ct"] = ci.CreateType
+ session.Values["kt"] = ci.KeyType
+ session.Values["c"] = ci.Country
+ session.Values["o"] = ci.Organization
+ session.Values["ou"] = ci.OrgUnit
+ session.Values["cn"] = ci.CommonName
+ session.Save(r, w)
- // Fake the method to GET as we need to continue in the setupHandler() function
- r.Method = "GET"
- } else {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
- return false
- }
- }
+ // Fake the method to GET as we need to continue in the setupHandler() function
+ r.Method = "GET"
+ } else {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
+ return false
+ }
+ }
- return true
+ return true
}
func _parseLinuxIPRouteShow(output []byte) (net.IP, error) {
- // Linux '/usr/bin/ip route show' format looks like this:
- // default via 192.168.178.1 dev wlp3s0 metric 303
- // 192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303
- lines := strings.Split(string(output), "\n")
- for _, line := range lines {
- fields := strings.Fields(line)
- if len(fields) >= 3 && fields[0] == "default" {
- ip := net.ParseIP(fields[2])
- if ip != nil {
- return ip, nil
- }
- }
- }
+ // Linux '/usr/bin/ip route show' format looks like this:
+ // default via 192.168.178.1 dev wlp3s0 metric 303
+ // 192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303
+ lines := strings.Split(string(output), "\n")
+ for _, line := range lines {
+ fields := strings.Fields(line)
+ if len(fields) >= 3 && fields[0] == "default" {
+ ip := net.ParseIP(fields[2])
+ if ip != nil {
+ return ip, nil
+ }
+ }
+ }
- return nil, errors.New("no gateway found")
+ return nil, errors.New("no gateway found")
}
func _discoverGateway() (net.IP, error) {
- if isDev {
- ip := net.ParseIP("127.0.0.1")
- if ip != nil {
- return ip, nil
- }
- }
+ if isDev {
+ ip := net.ParseIP("127.0.0.1")
+ if ip != nil {
+ return ip, nil
+ }
+ }
- routeCmd := exec.Command("ip", "route", "show")
- output, err := routeCmd.CombinedOutput()
- if err != nil {
- return nil, err
- }
+ routeCmd := exec.Command("ip", "route", "show")
+ output, err := routeCmd.CombinedOutput()
+ if err != nil {
+ return nil, err
+ }
- return _parseLinuxIPRouteShow(output)
+ return _parseLinuxIPRouteShow(output)
}
func _hostCommand(w http.ResponseWriter, r *http.Request, command string, params ...string) bool {
- ip, err := _discoverGateway()
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return false
- }
+ ip, err := _discoverGateway()
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
- conn, err := net.Dial("tcp", ip.String()+":3030")
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return false
- }
+ conn, err := net.Dial("tcp", ip.String()+":3030")
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
- defer conn.Close()
+ defer conn.Close()
- fmt.Fprintf(conn, command+"\n")
- for _, param := range params {
- fmt.Fprintf(conn, param+"\n")
- }
+ fmt.Fprintf(conn, command+"\n")
+ for _, param := range params {
+ fmt.Fprintf(conn, param+"\n")
+ }
- reader := bufio.NewReader(conn)
- message, err := ioutil.ReadAll(reader)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return false
- }
+ reader := bufio.NewReader(conn)
+ message, err := ioutil.ReadAll(reader)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
- if strings.Compare(string(message), "ok\n") == 0 {
- return true
- }
+ if strings.Compare(string(message), "ok\n") == 0 {
+ return true
+ }
- tail := message[len(message)-4:]
- if strings.Compare(string(tail), "\nok\n") == 0 {
- msg := message[0 : len(message)-4]
- log.Printf("Message from server: '%s'", msg)
- return true
- }
+ tail := message[len(message)-4:]
+ if strings.Compare(string(tail), "\nok\n") == 0 {
+ msg := message[0 : len(message)-4]
+ log.Printf("Message from server: '%s'", msg)
+ return true
+ }
- log.Printf("ERROR: Message from server: '%s'", message)
- errorHandler(w, r, errors.New(string(message)), http.StatusInternalServerError)
- return false
+ log.Printf("ERROR: Message from server: '%s'", message)
+ errorHandler(w, r, errors.New(string(message)), http.StatusInternalServerError)
+ return false
}
func randToken() string {
- b := make([]byte, 8)
- rand.Read(b)
- return fmt.Sprintf("%x", b)
+ b := make([]byte, 8)
+ rand.Read(b)
+ return fmt.Sprintf("%x", b)
}
func _applyConfig() error {
- os.Setenv("PKI_ROOT_CERT_BASE", "data/root-ca")
- os.Setenv("PKI_INT_CERT_BASE", "data/issuer/ca-int")
- os.Setenv("PKI_DEFAULT_O", viper.GetString("labca.organization"))
- os.Setenv("PKI_DNS", viper.GetString("labca.dns"))
- domain := viper.GetString("labca.fqdn")
- os.Setenv("PKI_FQDN", domain)
- pos := strings.Index(domain, ".")
- if pos > -1 {
- pos = pos + 1
- domain = domain[pos:]
- }
- os.Setenv("PKI_DOMAIN", domain)
- os.Setenv("PKI_DOMAIN_MODE", viper.GetString("labca.domain_mode"))
- os.Setenv("PKI_LOCKDOWN_DOMAINS", viper.GetString("labca.lockdown"))
- os.Setenv("PKI_WHITELIST_DOMAINS", viper.GetString("labca.whitelist"))
- if viper.GetBool("labca.email.enable") {
- os.Setenv("PKI_EMAIL_SERVER", viper.GetString("labca.email.server"))
- os.Setenv("PKI_EMAIL_PORT", viper.GetString("labca.email.port"))
- os.Setenv("PKI_EMAIL_USER", viper.GetString("labca.email.user"))
- res, err := _decrypt(viper.GetString("labca.email.pass"))
- if err != nil {
- log.Println("WARNING: could not decrypt stored password: " + err.Error())
- }
- os.Setenv("PKI_EMAIL_PASS", string(res))
- os.Setenv("PKI_EMAIL_FROM", viper.GetString("labca.email.from"))
- } else {
- os.Setenv("PKI_EMAIL_SERVER", "localhost")
- os.Setenv("PKI_EMAIL_PORT", "9380")
- os.Setenv("PKI_EMAIL_USER", "cert-master@example.com")
- os.Setenv("PKI_EMAIL_PASS", "password")
- os.Setenv("PKI_EMAIL_FROM", "Expiry bot ")
- }
+ os.Setenv("PKI_ROOT_CERT_BASE", "data/root-ca")
+ os.Setenv("PKI_INT_CERT_BASE", "data/issuer/ca-int")
+ os.Setenv("PKI_DEFAULT_O", viper.GetString("labca.organization"))
+ os.Setenv("PKI_DNS", viper.GetString("labca.dns"))
+ domain := viper.GetString("labca.fqdn")
+ os.Setenv("PKI_FQDN", domain)
+ pos := strings.Index(domain, ".")
+ if pos > -1 {
+ pos = pos + 1
+ domain = domain[pos:]
+ }
+ os.Setenv("PKI_DOMAIN", domain)
+ os.Setenv("PKI_DOMAIN_MODE", viper.GetString("labca.domain_mode"))
+ os.Setenv("PKI_LOCKDOWN_DOMAINS", viper.GetString("labca.lockdown"))
+ os.Setenv("PKI_WHITELIST_DOMAINS", viper.GetString("labca.whitelist"))
+ if viper.GetBool("labca.email.enable") {
+ os.Setenv("PKI_EMAIL_SERVER", viper.GetString("labca.email.server"))
+ os.Setenv("PKI_EMAIL_PORT", viper.GetString("labca.email.port"))
+ os.Setenv("PKI_EMAIL_USER", viper.GetString("labca.email.user"))
+ res, err := _decrypt(viper.GetString("labca.email.pass"))
+ if err != nil {
+ log.Println("WARNING: could not decrypt stored password: " + err.Error())
+ }
+ os.Setenv("PKI_EMAIL_PASS", string(res))
+ os.Setenv("PKI_EMAIL_FROM", viper.GetString("labca.email.from"))
+ } else {
+ os.Setenv("PKI_EMAIL_SERVER", "localhost")
+ os.Setenv("PKI_EMAIL_PORT", "9380")
+ os.Setenv("PKI_EMAIL_USER", "cert-master@example.com")
+ os.Setenv("PKI_EMAIL_PASS", "password")
+ os.Setenv("PKI_EMAIL_FROM", "Expiry bot ")
+ }
- _, err := exe_cmd("./apply")
- if err != nil {
- fmt.Println("")
- }
- return err
+ _, err := exe_cmd("./apply")
+ if err != nil {
+ fmt.Println("")
+ }
+ return err
}
func _progress(stage string) int {
- max := 20.0 / 100.0
- curr := 1.0
+ max := 20.0 / 100.0
+ curr := 1.0
- if stage == "register" {
- return int(math.Round(curr / max))
- } else {
- curr += 2.0
- }
+ if stage == "register" {
+ return int(math.Round(curr / max))
+ } else {
+ curr += 2.0
+ }
- if stage == "setup" {
- return int(math.Round(curr / max))
- } else {
- curr += 3.0
- }
+ if stage == "setup" {
+ return int(math.Round(curr / max))
+ } else {
+ curr += 3.0
+ }
- if stage == "root-ca" {
- return int(math.Round(curr / max))
- } else {
- curr += 4.0
- }
+ if stage == "root-ca" {
+ return int(math.Round(curr / max))
+ } else {
+ curr += 4.0
+ }
- if stage == "ca-int" {
- return int(math.Round(curr / max))
- } else {
- curr += 3.0
- }
+ if stage == "ca-int" {
+ return int(math.Round(curr / max))
+ } else {
+ curr += 3.0
+ }
- if stage == "polling" {
- return int(math.Round(curr / max))
- } else {
- curr += 4.0
- }
+ if stage == "polling" {
+ return int(math.Round(curr / max))
+ } else {
+ curr += 4.0
+ }
- if stage == "wrapup" {
- return int(math.Round(curr / max))
- } else {
- curr += 3.0
- }
+ if stage == "wrapup" {
+ return int(math.Round(curr / max))
+ } else {
+ curr += 3.0
+ }
- if stage == "final" {
- return int(math.Round(curr / max))
- } else {
- return 0
- }
+ if stage == "final" {
+ return int(math.Round(curr / max))
+ } else {
+ return 0
+ }
}
func _helptext(stage string) template.HTML {
- if stage == "register" {
- return template.HTML(fmt.Sprint("You need to create an admin account for managing this instance of\n",
- "LabCA. There can only be one admin account, but you can configure all its attributes once the\n",
- "initial setup has completed.
"))
- } else if stage == "setup" {
- return template.HTML(fmt.Sprint("The fully qualified domain name (FQDN) is what end users will use\n",
- "to connect to this server. It was provided in the initial setup and is shown here for reference.
\n",
- "Please fill in a DNS server (and optionally port, default is ':53') that will be used to lookup\n",
- "the domains for which a certificate is requested.
\n",
- "LabCA is primarily intended for use inside an organization where all domains end in the same\n",
- "domain, e.g. '.localdomain'. In lockdown mode only those domains are allowed. In whitelist mode\n",
- "those domains are allowed next to all official, internet accessible domains and in standard\n",
- "mode only the official domains are allowed.
"))
- } else if stage == "root-ca" {
- return template.HTML(fmt.Sprint("This is the top level certificate that will sign the issuer\n",
- "certificate(s). You can either generate a fresh Root CA (Certificate Authority) or import an\n",
- "existing one, e.g. a backup from another LabCA instance.
\n",
- "If you want to generate a certificate, pick a key type and strength (the higher the number the\n",
- "more secure, ECDSA is more modern than RSA), provide at least a country and organization name,\n",
- "and the common name. It is recommended that the common name contains the word 'Root' as well\n",
- "as your organization name so you can recognize it, and that's why that is automatically filled\n",
- "once you leave the organization field.
"))
- } else if stage == "ca-int" {
- return template.HTML(fmt.Sprint("This is what end users will see as the issuing certificate. Again,\n",
- "you can either generate a fresh certificate or import an existing one, as long as it is signed by\n",
- "the Root CA from the previous step.
\n",
- "If you want to generate a certificate, by default the same key type and strength is selected as\n",
- "was choosen in the previous step when generating the root, but you may choose a different one. By\n",
- "default the common name is the same as the CN for the Root CA, minus the word 'Root'.
"))
- } else {
- return template.HTML("")
- }
+ if stage == "register" {
+ return template.HTML(fmt.Sprint("You need to create an admin account for managing this instance of\n",
+ "LabCA. There can only be one admin account, but you can configure all its attributes once the\n",
+ "initial setup has completed.
"))
+ } else if stage == "setup" {
+ return template.HTML(fmt.Sprint("The fully qualified domain name (FQDN) is what end users will use\n",
+ "to connect to this server. It was provided in the initial setup and is shown here for reference.
\n",
+ "Please fill in a DNS server (and optionally port, default is ':53') that will be used to lookup\n",
+ "the domains for which a certificate is requested.
\n",
+ "LabCA is primarily intended for use inside an organization where all domains end in the same\n",
+ "domain, e.g. '.localdomain'. In lockdown mode only those domains are allowed. In whitelist mode\n",
+ "those domains are allowed next to all official, internet accessible domains and in standard\n",
+ "mode only the official domains are allowed.
"))
+ } else if stage == "root-ca" {
+ return template.HTML(fmt.Sprint("This is the top level certificate that will sign the issuer\n",
+ "certificate(s). You can either generate a fresh Root CA (Certificate Authority) or import an\n",
+ "existing one, e.g. a backup from another LabCA instance.
\n",
+ "If you want to generate a certificate, pick a key type and strength (the higher the number the\n",
+ "more secure, ECDSA is more modern than RSA), provide at least a country and organization name,\n",
+ "and the common name. It is recommended that the common name contains the word 'Root' as well\n",
+ "as your organization name so you can recognize it, and that's why that is automatically filled\n",
+ "once you leave the organization field.
"))
+ } else if stage == "ca-int" {
+ return template.HTML(fmt.Sprint("This is what end users will see as the issuing certificate. Again,\n",
+ "you can either generate a fresh certificate or import an existing one, as long as it is signed by\n",
+ "the Root CA from the previous step.
\n",
+ "If you want to generate a certificate, by default the same key type and strength is selected as\n",
+ "was choosen in the previous step when generating the root, but you may choose a different one. By\n",
+ "default the common name is the same as the CN for the Root CA, minus the word 'Root'.
"))
+ } else {
+ return template.HTML("")
+ }
}
func setupHandler(w http.ResponseWriter, r *http.Request) {
- if viper.GetBool("config.complete") == true {
- render(w, r, "index:manage", map[string]interface{}{"Message": template.HTML("Setup already completed! Go home ")})
- return
- }
+ if viper.GetBool("config.complete") == true {
+ render(w, r, "index:manage", map[string]interface{}{"Message": template.HTML("Setup already completed! Go home ")})
+ return
+ }
- // 1. Setup admin user
- if viper.Get("user.password") == nil {
- if r.Method == "GET" {
- reg := &User{
- RequestBase: r.Header.Get("X-Request-Base"),
- }
- render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
- return
- } else if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ // 1. Setup admin user
+ if viper.Get("user.password") == nil {
+ if r.Method == "GET" {
+ reg := &User{
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+ render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
+ return
+ } else if r.Method == "POST" {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- reg := &User{
- Name: r.Form.Get("username"),
- Email: r.Form.Get("email"),
- Password: r.Form.Get("password"),
- Confirm: r.Form.Get("confirm"),
- RequestBase: r.Header.Get("X-Request-Base"),
- }
+ reg := &User{
+ Name: r.Form.Get("username"),
+ Email: r.Form.Get("email"),
+ Password: r.Form.Get("password"),
+ Confirm: r.Form.Get("confirm"),
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
- if reg.Validate(true, false) == false {
- render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
- return
- }
+ if reg.Validate(true, false) == false {
+ render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
+ return
+ }
- hash, err := bcrypt.GenerateFromPassword([]byte(reg.Password), bcrypt.MinCost)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
- viper.Set("user.name", reg.Name)
- viper.Set("user.email", reg.Email)
- viper.Set("user.password", string(hash))
- viper.WriteConfig()
+ hash, err := bcrypt.GenerateFromPassword([]byte(reg.Password), bcrypt.MinCost)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
+ viper.Set("user.name", reg.Name)
+ viper.Set("user.email", reg.Email)
+ viper.Set("user.password", string(hash))
+ viper.WriteConfig()
- session := getSession(w, r)
- session.Values["user"] = reg.Name
- session.Save(r, w)
+ session := getSession(w, r)
+ session.Values["user"] = reg.Name
+ session.Save(r, w)
- // Fake the method to GET as we need to continue in the setupHandler() function
- r.Method = "GET"
- } else {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
- return
- }
- }
+ // Fake the method to GET as we need to continue in the setupHandler() function
+ r.Method = "GET"
+ } else {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
+ return
+ }
+ }
- // 2. Setup essential configuration
- if viper.Get("labca.dns") == nil {
- if r.Method == "GET" {
- domain := viper.GetString("labca.fqdn")
- pos := strings.Index(domain, ".")
- if pos > -1 {
- pos = pos + 1
- domain = domain[pos:]
- }
+ // 2. Setup essential configuration
+ if viper.Get("labca.dns") == nil {
+ if r.Method == "GET" {
+ domain := viper.GetString("labca.fqdn")
+ pos := strings.Index(domain, ".")
+ if pos > -1 {
+ pos = pos + 1
+ domain = domain[pos:]
+ }
- cfg := &SetupConfig{
- Fqdn: viper.GetString("labca.fqdn"),
- DomainMode: "lockdown",
- LockdownDomains: domain,
- WhitelistDomains: domain,
- RequestBase: r.Header.Get("X-Request-Base"),
- }
+ cfg := &SetupConfig{
+ Fqdn: viper.GetString("labca.fqdn"),
+ DomainMode: "lockdown",
+ LockdownDomains: domain,
+ WhitelistDomains: domain,
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
- render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
- return
- } else if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
+ return
+ } else if r.Method == "POST" {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- cfg := &SetupConfig{
- Fqdn: r.Form.Get("fqdn"),
- Dns: r.Form.Get("dns"),
- DomainMode: r.Form.Get("domain_mode"),
- LockdownDomains: r.Form.Get("lockdown_domains"),
- WhitelistDomains: r.Form.Get("whitelist_domains"),
- RequestBase: r.Header.Get("X-Request-Base"),
- }
+ cfg := &SetupConfig{
+ Fqdn: r.Form.Get("fqdn"),
+ Dns: r.Form.Get("dns"),
+ DomainMode: r.Form.Get("domain_mode"),
+ LockdownDomains: r.Form.Get("lockdown_domains"),
+ WhitelistDomains: r.Form.Get("whitelist_domains"),
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
- if cfg.Validate(false) == false {
- render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
- return
- }
+ if cfg.Validate(false) == false {
+ render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
+ return
+ }
- matched, err := regexp.MatchString(":\\d+$", cfg.Dns)
- if err == nil && !matched {
- cfg.Dns += ":53"
- }
+ matched, err := regexp.MatchString(":\\d+$", cfg.Dns)
+ if err == nil && !matched {
+ cfg.Dns += ":53"
+ }
- viper.Set("labca.fqdn", cfg.Fqdn)
- viper.Set("labca.dns", cfg.Dns)
- viper.Set("labca.domain_mode", cfg.DomainMode)
- if cfg.DomainMode == "lockdown" {
- viper.Set("labca.lockdown", cfg.LockdownDomains)
- }
- if cfg.DomainMode == "whitelist" {
- viper.Set("labca.whitelist", cfg.WhitelistDomains)
- }
- viper.WriteConfig()
+ viper.Set("labca.fqdn", cfg.Fqdn)
+ viper.Set("labca.dns", cfg.Dns)
+ viper.Set("labca.domain_mode", cfg.DomainMode)
+ if cfg.DomainMode == "lockdown" {
+ viper.Set("labca.lockdown", cfg.LockdownDomains)
+ }
+ if cfg.DomainMode == "whitelist" {
+ viper.Set("labca.whitelist", cfg.WhitelistDomains)
+ }
+ viper.WriteConfig()
- // Fake the method to GET as we need to continue in the setupHandler() function
- r.Method = "GET"
- } else {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
- return
- }
- }
+ // Fake the method to GET as we need to continue in the setupHandler() function
+ r.Method = "GET"
+ } else {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
+ return
+ }
+ }
- // 3. Setup root CA certificate
- if !_certCreate(w, r, "root-ca", true) {
- return
- }
+ // 3. Setup root CA certificate
+ if !_certCreate(w, r, "root-ca", true) {
+ return
+ }
- // 4. Setup issuer certificate
- if !_certCreate(w, r, "ca-int", false) {
- return
- }
+ // 4. Setup issuer certificate
+ if !_certCreate(w, r, "ca-int", false) {
+ return
+ }
- // 5. Apply configuration / populate with certificate info
- err := _applyConfig()
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ // 5. Apply configuration / populate with certificate info
+ err := _applyConfig()
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- if !viper.GetBool("config.restarted") {
- // 6. Trust the new certs
- if !_hostCommand(w, r, "trust-store") {
- return
- }
+ if !viper.GetBool("config.restarted") {
+ // 6. Trust the new certs
+ if !_hostCommand(w, r, "trust-store") {
+ return
+ }
- // Don't let the retry mechanism generate new restartSecret!
- if r.Header.Get("X-Requested-With") == "XMLHttpRequest" {
- render(w, r, "index", map[string]interface{}{"Message": "Retry OK"})
- } else {
- // 8. Restart application
- restartSecret = randToken()
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/wait?restart="+restartSecret, http.StatusFound)
- }
- return
+ // Don't let the retry mechanism generate new restartSecret!
+ if r.Header.Get("X-Requested-With") == "XMLHttpRequest" {
+ render(w, r, "index", map[string]interface{}{"Message": "Retry OK"})
+ } else {
+ // 8. Restart application
+ restartSecret = randToken()
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/wait?restart="+restartSecret, http.StatusFound)
+ }
+ return
- } else {
- render(w, r, "wrapup:manage", map[string]interface{}{"Progress": _progress("wrapup"), "HelpText": _helptext("wrapup")})
- }
+ } else {
+ render(w, r, "wrapup:manage", map[string]interface{}{"Progress": _progress("wrapup"), "HelpText": _helptext("wrapup")})
+ }
}
func waitHandler(w http.ResponseWriter, r *http.Request) {
- if viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
- return
- }
+ if viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
+ return
+ }
- render(w, r, "polling:manage", map[string]interface{}{"Progress": _progress("polling"), "HelpText": _helptext("polling")})
+ render(w, r, "polling:manage", map[string]interface{}{"Progress": _progress("polling"), "HelpText": _helptext("polling")})
}
func restartHandler(w http.ResponseWriter, r *http.Request) {
- if viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
- return
- }
+ if viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
+ return
+ }
- if strings.Compare(r.URL.Query().Get("token"), restartSecret) != 0 {
- log.Println("WARNING: Restart token ('" + r.URL.Query().Get("token") + "') does not match our secret ('" + restartSecret + "')!")
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if strings.Compare(r.URL.Query().Get("token"), restartSecret) != 0 {
+ log.Println("WARNING: Restart token ('" + r.URL.Query().Get("token") + "') does not match our secret ('" + restartSecret + "')!")
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- viper.Set("config.restarted", true)
- viper.WriteConfig()
+ viper.Set("config.restarted", true)
+ viper.WriteConfig()
- if !_hostCommand(w, r, "docker-restart") {
- viper.Set("config.restarted", false)
- viper.WriteConfig()
- return
- }
+ if !_hostCommand(w, r, "docker-restart") {
+ viper.Set("config.restarted", false)
+ viper.WriteConfig()
+ return
+ }
}
func finalHandler(w http.ResponseWriter, r *http.Request) {
- if viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
- return
- }
+ if viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/", http.StatusFound)
+ return
+ }
- // Don't let the retry mechanism trigger a certificate request and restart!
- if r.Header.Get("X-Requested-With") == "XMLHttpRequest" {
- render(w, r, "index", map[string]interface{}{"Message": "Retry OK"})
- } else {
- // 9. Setup our own web certificate
- if !_hostCommand(w, r, "acme-request") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/logs/cert", http.StatusSeeOther)
- return
- }
+ // Don't let the retry mechanism trigger a certificate request and restart!
+ if r.Header.Get("X-Requested-With") == "XMLHttpRequest" {
+ render(w, r, "index", map[string]interface{}{"Message": "Retry OK"})
+ } else {
+ // 9. Setup our own web certificate
+ if !_hostCommand(w, r, "acme-request") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/logs/cert", http.StatusSeeOther)
+ return
+ }
- // 10. remove the temporary bit from nginx config
- if !_hostCommand(w, r, "nginx-remove-redirect") {
- return
- }
+ // 10. remove the temporary bit from nginx config
+ if !_hostCommand(w, r, "nginx-remove-redirect") {
+ return
+ }
- // 11. reload nginx
- if !_hostCommand(w, r, "nginx-reload") {
- return
- }
+ // 11. reload nginx
+ if !_hostCommand(w, r, "nginx-reload") {
+ return
+ }
- viper.Set("config.complete", true)
- viper.WriteConfig()
+ viper.Set("config.complete", true)
+ viper.WriteConfig()
- render(w, r, "final:manage", map[string]interface{}{"RequestBase": r.Header.Get("X-Request-Base"), "Progress": _progress("final"), "HelpText": _helptext("final")})
- }
+ render(w, r, "final:manage", map[string]interface{}{"RequestBase": r.Header.Get("X-Request-Base"), "Progress": _progress("final"), "HelpText": _helptext("final")})
+ }
}
// RangeStructer takes the first argument, which must be a struct, and
// returns the value of each field in a slice. It will return nil
// if there are no arguments or first argument is not a struct
func RangeStructer(args ...interface{}) []interface{} {
- if len(args) == 0 {
- return nil
- }
+ if len(args) == 0 {
+ return nil
+ }
- v := reflect.ValueOf(args[0])
- if v.Kind() != reflect.Struct {
- return nil
- }
+ v := reflect.ValueOf(args[0])
+ if v.Kind() != reflect.Struct {
+ return nil
+ }
- out := make([]interface{}, v.NumField())
- for i := 0; i < v.NumField(); i++ {
- switch v.Field(i).Kind() {
- case reflect.String:
- if v.Field(i).Type().String() == "template.HTML" {
- out[i] = template.HTML(v.Field(i).String())
- } else {
- out[i] = v.Field(i).String()
- }
- case reflect.Bool:
- out[i] = v.Field(i).Bool()
- default:
- out[i] = v.Field(i)
- }
- }
+ out := make([]interface{}, v.NumField())
+ for i := 0; i < v.NumField(); i++ {
+ switch v.Field(i).Kind() {
+ case reflect.String:
+ if v.Field(i).Type().String() == "template.HTML" {
+ out[i] = template.HTML(v.Field(i).String())
+ } else {
+ out[i] = v.Field(i).String()
+ }
+ case reflect.Bool:
+ out[i] = v.Field(i).Bool()
+ default:
+ out[i] = v.Field(i)
+ }
+ }
- return out
+ return out
}
func accountsHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- Accounts, err := GetAccounts(w, r)
- if err == nil {
- render(w, r, "list:accounts", map[string]interface{}{"List": Accounts})
- }
+ Accounts, err := GetAccounts(w, r)
+ if err == nil {
+ render(w, r, "list:accounts", map[string]interface{}{"List": Accounts})
+ }
}
func accountHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- vars := mux.Vars(r)
- id, err := strconv.Atoi(vars["id"])
- if err != nil {
- errorHandler(w, r, err, http.StatusBadRequest)
- return
- }
+ vars := mux.Vars(r)
+ id, err := strconv.Atoi(vars["id"])
+ if err != nil {
+ errorHandler(w, r, err, http.StatusBadRequest)
+ return
+ }
- AccountDetails, err := GetAccount(w, r, id)
- if err == nil {
- render(w, r, "show:accounts", map[string]interface{}{"Details": AccountDetails})
- }
+ AccountDetails, err := GetAccount(w, r, id)
+ if err == nil {
+ render(w, r, "show:accounts", map[string]interface{}{"Details": AccountDetails})
+ }
}
func ordersHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- Orders, err := GetOrders(w, r)
- if err == nil {
- render(w, r, "list:orders", map[string]interface{}{"List": Orders})
- }
+ Orders, err := GetOrders(w, r)
+ if err == nil {
+ render(w, r, "list:orders", map[string]interface{}{"List": Orders})
+ }
}
func orderHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- vars := mux.Vars(r)
- id, err := strconv.Atoi(vars["id"])
- if err != nil {
- errorHandler(w, r, err, http.StatusBadRequest)
- return
- }
+ vars := mux.Vars(r)
+ id, err := strconv.Atoi(vars["id"])
+ if err != nil {
+ errorHandler(w, r, err, http.StatusBadRequest)
+ return
+ }
- OrderDetails, err := GetOrder(w, r, id)
- if err == nil {
- render(w, r, "show:orders", map[string]interface{}{"Details": OrderDetails})
- }
+ OrderDetails, err := GetOrder(w, r, id)
+ if err == nil {
+ render(w, r, "show:orders", map[string]interface{}{"Details": OrderDetails})
+ }
}
func authzHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- Authz, err := GetAuthz(w, r)
- if err == nil {
- render(w, r, "list:authz", map[string]interface{}{"List": Authz})
- }
+ Authz, err := GetAuthz(w, r)
+ if err == nil {
+ render(w, r, "list:authz", map[string]interface{}{"List": Authz})
+ }
}
func authHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- vars := mux.Vars(r)
- id := vars["id"]
+ vars := mux.Vars(r)
+ id := vars["id"]
- AuthDetails, err := GetAuth(w, r, id)
- if err == nil {
- render(w, r, "show:authz", map[string]interface{}{"Details": AuthDetails})
- }
+ AuthDetails, err := GetAuth(w, r, id)
+ if err == nil {
+ render(w, r, "show:authz", map[string]interface{}{"Details": AuthDetails})
+ }
}
func challengesHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- Challenges, err := GetChallenges(w, r)
- if err == nil {
- render(w, r, "list:challenges", map[string]interface{}{"List": Challenges})
- }
+ Challenges, err := GetChallenges(w, r)
+ if err == nil {
+ render(w, r, "list:challenges", map[string]interface{}{"List": Challenges})
+ }
}
func challengeHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- vars := mux.Vars(r)
- id, err := strconv.Atoi(vars["id"])
- if err != nil {
- errorHandler(w, r, err, http.StatusBadRequest)
- return
- }
+ vars := mux.Vars(r)
+ id, err := strconv.Atoi(vars["id"])
+ if err != nil {
+ errorHandler(w, r, err, http.StatusBadRequest)
+ return
+ }
- ChallengeDetails, err := GetChallenge(w, r, id)
- if err == nil {
- render(w, r, "show:challenges", map[string]interface{}{"Details": ChallengeDetails})
- }
+ ChallengeDetails, err := GetChallenge(w, r, id)
+ if err == nil {
+ render(w, r, "show:challenges", map[string]interface{}{"Details": ChallengeDetails})
+ }
}
func certificatesHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- Certificates, err := GetCertificates(w, r)
- if err == nil {
- render(w, r, "list:certificates", map[string]interface{}{"List": Certificates})
- }
+ Certificates, err := GetCertificates(w, r)
+ if err == nil {
+ render(w, r, "list:certificates", map[string]interface{}{"List": Certificates})
+ }
}
func certificateHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
- return
- }
+ if !viper.GetBool("config.complete") {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
+ return
+ }
- var serial string
- vars := mux.Vars(r)
- id, err := strconv.Atoi(vars["id"])
- if err != nil {
- serial = vars["id"]
- }
+ var serial string
+ vars := mux.Vars(r)
+ id, err := strconv.Atoi(vars["id"])
+ if err != nil {
+ serial = vars["id"]
+ }
- CertificateDetails, err := GetCertificate(w, r, id, serial)
- if err == nil {
- render(w, r, "show:certificates", map[string]interface{}{"Details": CertificateDetails})
- }
+ CertificateDetails, err := GetCertificate(w, r, id, serial)
+ if err == nil {
+ render(w, r, "show:certificates", map[string]interface{}{"Details": CertificateDetails})
+ }
}
func certRevokeHandler(w http.ResponseWriter, r *http.Request) {
- if !viper.GetBool("config.complete") {
- errorHandler(w, r, errors.New("Method not allowed at this point"), http.StatusMethodNotAllowed)
- return
- }
+ if !viper.GetBool("config.complete") {
+ errorHandler(w, r, errors.New("Method not allowed at this point"), http.StatusMethodNotAllowed)
+ return
+ }
- if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ if r.Method == "POST" {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- serial := r.Form.Get("serial")
- reason, err := strconv.Atoi(r.Form.Get("reason"))
- if err != nil {
- errorHandler(w, r, err, http.StatusBadRequest)
- return
- }
+ serial := r.Form.Get("serial")
+ reason, err := strconv.Atoi(r.Form.Get("reason"))
+ if err != nil {
+ errorHandler(w, r, err, http.StatusBadRequest)
+ return
+ }
- if !_hostCommand(w, r, "revoke-cert", serial, strconv.Itoa(reason)) {
- return
- }
- }
+ if !_hostCommand(w, r, "revoke-cert", serial, strconv.Itoa(reason)) {
+ return
+ }
+ }
}
type navItem struct {
- Name string
- Icon string
- Attrs map[template.HTMLAttr]string
- IsActive bool
- SubMenu []navItem
+ Name string
+ Icon string
+ Attrs map[template.HTMLAttr]string
+ IsActive bool
+ SubMenu []navItem
}
func activeNav(active string, uri string, requestBase string) []navItem {
- isAcmeActive := (uri == "/accounts" || strings.HasPrefix(uri, "/accounts/") ||
- uri == "/orders" || strings.HasPrefix(uri, "/orders/") ||
- uri == "/authz" || strings.HasPrefix(uri, "/authz/") ||
- uri == "/challenges" || strings.HasPrefix(uri, "/challenges/") ||
- uri == "/certificates" || strings.HasPrefix(uri, "/certificates/") ||
- false)
+ isAcmeActive := (uri == "/accounts" || strings.HasPrefix(uri, "/accounts/") ||
+ uri == "/orders" || strings.HasPrefix(uri, "/orders/") ||
+ uri == "/authz" || strings.HasPrefix(uri, "/authz/") ||
+ uri == "/challenges" || strings.HasPrefix(uri, "/challenges/") ||
+ uri == "/certificates" || strings.HasPrefix(uri, "/certificates/") ||
+ false)
- // create menu items
- home := navItem{
- Name: "Dashboard",
- Icon: "fa-dashboard",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/",
- "title": "Main page with the status of the system",
- },
- }
- accounts := navItem{
- Name: "Accounts",
- Icon: "fa-list-alt",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/accounts",
- "title": "ACME Accounts",
- },
- }
- orders := navItem{
- Name: "Orders",
- Icon: "fa-tags",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/orders",
- "title": "ACME Orders",
- },
- }
- authz := navItem{
- Name: "Authorizations",
- Icon: "fa-chain",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/authz",
- "title": "ACME Authorizations",
- },
- }
- challenges := navItem{
- Name: "Challenges",
- Icon: "fa-exchange",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/challenges",
- "title": "ACME Challenges",
- },
- }
- certificates := navItem{
- Name: "Certificates",
- Icon: "fa-lock",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/certificates",
- "title": "ACME Certificates",
- },
- }
- acme := navItem{
- Name: "ACME",
- Icon: "fa-sitemap",
- Attrs: map[template.HTMLAttr]string{
- "href": "#",
- "title": "Automated Certificate Management Environment",
- },
- IsActive: isAcmeActive,
- SubMenu: []navItem{accounts, certificates, orders, authz, challenges},
- }
- cert := navItem{
- Name: "Web Certificate",
- Icon: "fa-lock",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/logs/cert",
- "title": "Log file for the certificate renewal for this server",
- },
- }
- boulder := navItem{
- Name: "ACME",
- Icon: "fa-search-plus",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/logs/boulder",
- "title": "Live view on the backend ACME application logs",
- },
- }
- audit := navItem{
- Name: "ACME Audit Log",
- Icon: "fa-paw",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/logs/audit",
- "title": "Live view on only the audit messages in the backend ACME application logs",
- },
- }
- labca := navItem{
- Name: "LabCA",
- Icon: "fa-edit",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/logs/labca",
- "title": "Live view on the logs for this LabCA web application",
- },
- }
- web := navItem{
- Name: "Web Access",
- Icon: "fa-globe",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/logs/web",
- "title": "Live view on the NGINX web server access log",
- },
- }
- weberr := navItem{
- Name: "Web Error",
- Icon: "fa-times",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/logs/weberr",
- "title": "Log file for the NGINX web server error log",
- },
- }
- logs := navItem{
- Name: "Logs",
- Icon: "fa-files-o",
- Attrs: map[template.HTMLAttr]string{
- "href": "#",
- "title": "Log Files",
- },
- SubMenu: []navItem{cert, boulder, audit, labca, web, weberr},
- }
- manage := navItem{
- Name: "Manage",
- Icon: "fa-wrench",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/manage",
- "title": "Manage the system",
- },
- }
- about := navItem{
- Name: "About",
- Icon: "fa-comments",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/about",
- "title": "About LabCA",
- },
- }
- public := navItem{
- Name: "Public Area",
- Icon: "fa-home",
- Attrs: map[template.HTMLAttr]string{
- "href": "/",
- "title": "The non-Admin pages of this LabCA instance",
- },
- }
+ // create menu items
+ home := navItem{
+ Name: "Dashboard",
+ Icon: "fa-dashboard",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/",
+ "title": "Main page with the status of the system",
+ },
+ }
+ accounts := navItem{
+ Name: "Accounts",
+ Icon: "fa-list-alt",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/accounts",
+ "title": "ACME Accounts",
+ },
+ }
+ orders := navItem{
+ Name: "Orders",
+ Icon: "fa-tags",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/orders",
+ "title": "ACME Orders",
+ },
+ }
+ authz := navItem{
+ Name: "Authorizations",
+ Icon: "fa-chain",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/authz",
+ "title": "ACME Authorizations",
+ },
+ }
+ challenges := navItem{
+ Name: "Challenges",
+ Icon: "fa-exchange",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/challenges",
+ "title": "ACME Challenges",
+ },
+ }
+ certificates := navItem{
+ Name: "Certificates",
+ Icon: "fa-lock",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/certificates",
+ "title": "ACME Certificates",
+ },
+ }
+ acme := navItem{
+ Name: "ACME",
+ Icon: "fa-sitemap",
+ Attrs: map[template.HTMLAttr]string{
+ "href": "#",
+ "title": "Automated Certificate Management Environment",
+ },
+ IsActive: isAcmeActive,
+ SubMenu: []navItem{accounts, certificates, orders, authz, challenges},
+ }
+ cert := navItem{
+ Name: "Web Certificate",
+ Icon: "fa-lock",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/logs/cert",
+ "title": "Log file for the certificate renewal for this server",
+ },
+ }
+ boulder := navItem{
+ Name: "ACME",
+ Icon: "fa-search-plus",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/logs/boulder",
+ "title": "Live view on the backend ACME application logs",
+ },
+ }
+ audit := navItem{
+ Name: "ACME Audit Log",
+ Icon: "fa-paw",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/logs/audit",
+ "title": "Live view on only the audit messages in the backend ACME application logs",
+ },
+ }
+ labca := navItem{
+ Name: "LabCA",
+ Icon: "fa-edit",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/logs/labca",
+ "title": "Live view on the logs for this LabCA web application",
+ },
+ }
+ web := navItem{
+ Name: "Web Access",
+ Icon: "fa-globe",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/logs/web",
+ "title": "Live view on the NGINX web server access log",
+ },
+ }
+ weberr := navItem{
+ Name: "Web Error",
+ Icon: "fa-times",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/logs/weberr",
+ "title": "Log file for the NGINX web server error log",
+ },
+ }
+ logs := navItem{
+ Name: "Logs",
+ Icon: "fa-files-o",
+ Attrs: map[template.HTMLAttr]string{
+ "href": "#",
+ "title": "Log Files",
+ },
+ SubMenu: []navItem{cert, boulder, audit, labca, web, weberr},
+ }
+ manage := navItem{
+ Name: "Manage",
+ Icon: "fa-wrench",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/manage",
+ "title": "Manage the system",
+ },
+ }
+ about := navItem{
+ Name: "About",
+ Icon: "fa-comments",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/about",
+ "title": "About LabCA",
+ },
+ }
+ public := navItem{
+ Name: "Public Area",
+ Icon: "fa-home",
+ Attrs: map[template.HTMLAttr]string{
+ "href": "/",
+ "title": "The non-Admin pages of this LabCA instance",
+ },
+ }
- // set active menu class
- switch active {
- case "about":
- about.Attrs["class"] = "active"
- case "accounts":
- accounts.Attrs["class"] = "active"
- case "orders":
- orders.Attrs["class"] = "active"
- case "authz":
- authz.Attrs["class"] = "active"
- case "challenges":
- challenges.Attrs["class"] = "active"
- case "certificates":
- certificates.Attrs["class"] = "active"
- case "index":
- home.Attrs["class"] = "active"
- case "manage":
- manage.Attrs["class"] = "active"
- case "logs":
- logs.Attrs["class"] = "active"
- }
+ // set active menu class
+ switch active {
+ case "about":
+ about.Attrs["class"] = "active"
+ case "accounts":
+ accounts.Attrs["class"] = "active"
+ case "orders":
+ orders.Attrs["class"] = "active"
+ case "authz":
+ authz.Attrs["class"] = "active"
+ case "challenges":
+ challenges.Attrs["class"] = "active"
+ case "certificates":
+ certificates.Attrs["class"] = "active"
+ case "index":
+ home.Attrs["class"] = "active"
+ case "manage":
+ manage.Attrs["class"] = "active"
+ case "logs":
+ logs.Attrs["class"] = "active"
+ }
- return []navItem{home, acme, logs, manage, about, public}
+ return []navItem{home, acme, logs, manage, about, public}
}
func render(w http.ResponseWriter, r *http.Request, view string, data map[string]interface{}) {
- viewSlice := strings.Split(view, ":")
- menu := viewSlice[0]
- if len(viewSlice) > 1 {
- menu = viewSlice[1]
- }
- data["Menu"] = activeNav(menu, r.RequestURI, r.Header.Get("X-Request-Base"))
+ viewSlice := strings.Split(view, ":")
+ menu := viewSlice[0]
+ if len(viewSlice) > 1 {
+ menu = viewSlice[1]
+ }
+ data["Menu"] = activeNav(menu, r.RequestURI, r.Header.Get("X-Request-Base"))
- if version != "" {
- data["Version"] = version
- }
+ if version != "" {
+ data["Version"] = version
+ }
- b, err := tmpls.Render("base.tmpl", "views/"+viewSlice[0]+".tmpl", data)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
+ b, err := tmpls.Render("base.tmpl", "views/"+viewSlice[0]+".tmpl", data)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
- w.Write(b)
+ w.Write(b)
}
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
- errorHandler(w, r, fmt.Errorf("NotFoundHandler for: %s %s", r.Method, r.URL), http.StatusNotFound)
+ errorHandler(w, r, fmt.Errorf("NotFoundHandler for: %s %s", r.Method, r.URL), http.StatusNotFound)
}
func authorized(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- log.Println(r.Method + " " + r.RequestURI)
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ log.Println(r.Method + " " + r.RequestURI)
- if r.RequestURI == "/login" || strings.Contains(r.RequestURI, "/static/") {
- next.ServeHTTP(w, r)
- } else {
- session := getSession(w, r)
- if session.Values["user"] != nil || (r.RequestURI == "/setup" && viper.Get("user.password") == nil) {
- next.ServeHTTP(w, r)
- } else {
- session.Values["bounce"] = r.RequestURI
- session.Save(r, w)
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/login", http.StatusFound)
- }
- }
- })
+ if r.RequestURI == "/login" || strings.Contains(r.RequestURI, "/static/") {
+ next.ServeHTTP(w, r)
+ } else {
+ session := getSession(w, r)
+ if session.Values["user"] != nil || (r.RequestURI == "/setup" && viper.Get("user.password") == nil) {
+ next.ServeHTTP(w, r)
+ } else {
+ session.Values["bounce"] = r.RequestURI
+ session.Save(r, w)
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/login", http.StatusFound)
+ }
+ }
+ })
}
func init() {
- if os.Getenv("DEVELOPMENT") != "" {
- isDev = true
- }
+ if os.Getenv("DEVELOPMENT") != "" {
+ isDev = true
+ }
- var err error
- tmpls, err = templates.New().ParseDir("./templates", "templates/")
- if err != nil {
- panic(fmt.Errorf("Fatal error templates: %s \n", err))
- }
- tmpls.AddFunc("rangeStruct", RangeStructer)
+ var err error
+ tmpls, err = templates.New().ParseDir("./templates", "templates/")
+ if err != nil {
+ panic(fmt.Errorf("Fatal error templates: %s \n", err))
+ }
+ tmpls.AddFunc("rangeStruct", RangeStructer)
- viper.SetConfigName("config")
- viper.AddConfigPath("data")
- viper.SetDefault("config.complete", false)
- if err := viper.ReadInConfig(); err != nil {
- panic(fmt.Errorf("Fatal error config file: %s \n", err))
- }
+ viper.SetConfigName("config")
+ viper.AddConfigPath("data")
+ viper.SetDefault("config.complete", false)
+ if err := viper.ReadInConfig(); err != nil {
+ panic(fmt.Errorf("Fatal error config file: %s \n", err))
+ }
- if viper.Get("keys.auth") == nil {
- key := securecookie.GenerateRandomKey(32)
- if key == nil {
- panic(fmt.Errorf("Fatal error random key\n"))
- }
- viper.Set("keys.auth", key)
- viper.WriteConfig()
- }
- if viper.Get("keys.enc") == nil {
- key := securecookie.GenerateRandomKey(32)
- if key == nil {
- panic(fmt.Errorf("Fatal error random key\n"))
- }
- viper.Set("keys.enc", key)
- viper.WriteConfig()
- }
+ if viper.Get("keys.auth") == nil {
+ key := securecookie.GenerateRandomKey(32)
+ if key == nil {
+ panic(fmt.Errorf("Fatal error random key\n"))
+ }
+ viper.Set("keys.auth", key)
+ viper.WriteConfig()
+ }
+ if viper.Get("keys.enc") == nil {
+ key := securecookie.GenerateRandomKey(32)
+ if key == nil {
+ panic(fmt.Errorf("Fatal error random key\n"))
+ }
+ viper.Set("keys.enc", key)
+ viper.WriteConfig()
+ }
- if viper.Get("server.addr") == nil {
- viper.Set("server.addr", "0.0.0.0")
- viper.WriteConfig()
- }
+ if viper.Get("server.addr") == nil {
+ viper.Set("server.addr", "0.0.0.0")
+ viper.WriteConfig()
+ }
- if viper.Get("server.port") == nil {
- viper.Set("server.port", 3000)
- viper.WriteConfig()
- }
+ if viper.Get("server.port") == nil {
+ viper.Set("server.port", 3000)
+ viper.WriteConfig()
+ }
- if viper.Get("server.session.maxage") == nil {
- viper.Set("server.session.maxage", 3600) // 1 hour
- viper.WriteConfig()
- }
+ if viper.Get("server.session.maxage") == nil {
+ viper.Set("server.session.maxage", 3600) // 1 hour
+ viper.WriteConfig()
+ }
- if viper.Get("db.conn") == nil {
- viper.Set("db.type", "mysql")
- viper.Set("db.conn", "root@tcp(boulder-mysql:3306)/boulder_sa_integration")
- viper.WriteConfig()
- }
- dbConn = viper.GetString("db.conn")
- dbType = viper.GetString("db.type")
+ if viper.Get("db.conn") == nil {
+ viper.Set("db.type", "mysql")
+ viper.Set("db.conn", "root@tcp(boulder-mysql:3306)/boulder_sa_integration")
+ viper.WriteConfig()
+ }
+ dbConn = viper.GetString("db.conn")
+ dbType = viper.GetString("db.type")
- version = viper.GetString("version")
+ version = viper.GetString("version")
}
func main() {
- tmpls.Parse()
+ tmpls.Parse()
- sessionStore = sessions.NewCookieStore([]byte(viper.GetString("keys.auth")), []byte(viper.GetString("keys.enc")))
- sessionStore.Options = &sessions.Options{
- Path: "/",
- MaxAge: viper.GetInt("server.session.maxage") * 1,
- HttpOnly: true,
- }
+ sessionStore = sessions.NewCookieStore([]byte(viper.GetString("keys.auth")), []byte(viper.GetString("keys.enc")))
+ sessionStore.Options = &sessions.Options{
+ Path: "/",
+ MaxAge: viper.GetInt("server.session.maxage") * 1,
+ HttpOnly: true,
+ }
- r := mux.NewRouter()
- r.HandleFunc("/", rootHandler).Methods("GET")
- r.HandleFunc("/about", aboutHandler).Methods("GET")
- r.HandleFunc("/manage", manageHandler).Methods("GET", "POST")
- r.HandleFunc("/final", finalHandler).Methods("GET")
- r.HandleFunc("/login", loginHandler).Methods("GET", "POST")
- r.HandleFunc("/logout", logoutHandler).Methods("GET")
- r.HandleFunc("/logs/{type}", logsHandler).Methods("GET")
- r.HandleFunc("/restart", restartHandler).Methods("GET")
- r.HandleFunc("/setup", setupHandler).Methods("GET", "POST")
- r.HandleFunc("/wait", waitHandler).Methods("GET")
- r.HandleFunc("/ws", wsHandler).Methods("GET")
+ r := mux.NewRouter()
+ r.HandleFunc("/", rootHandler).Methods("GET")
+ r.HandleFunc("/about", aboutHandler).Methods("GET")
+ r.HandleFunc("/manage", manageHandler).Methods("GET", "POST")
+ r.HandleFunc("/final", finalHandler).Methods("GET")
+ r.HandleFunc("/login", loginHandler).Methods("GET", "POST")
+ r.HandleFunc("/logout", logoutHandler).Methods("GET")
+ r.HandleFunc("/logs/{type}", logsHandler).Methods("GET")
+ r.HandleFunc("/restart", restartHandler).Methods("GET")
+ r.HandleFunc("/setup", setupHandler).Methods("GET", "POST")
+ r.HandleFunc("/wait", waitHandler).Methods("GET")
+ r.HandleFunc("/ws", wsHandler).Methods("GET")
- r.HandleFunc("/accounts", accountsHandler).Methods("GET")
- r.HandleFunc("/accounts/{id}", accountHandler).Methods("GET")
- r.HandleFunc("/orders", ordersHandler).Methods("GET")
- r.HandleFunc("/orders/{id}", orderHandler).Methods("GET")
- r.HandleFunc("/authz", authzHandler).Methods("GET")
- r.HandleFunc("/authz/{id}", authHandler).Methods("GET")
- r.HandleFunc("/challenges", challengesHandler).Methods("GET")
- r.HandleFunc("/challenges/{id}", challengeHandler).Methods("GET")
- r.HandleFunc("/certificates", certificatesHandler).Methods("GET")
- r.HandleFunc("/certificates/{id}", certificateHandler).Methods("GET")
- r.HandleFunc("/certificates/{id}", certRevokeHandler).Methods("POST")
+ r.HandleFunc("/accounts", accountsHandler).Methods("GET")
+ r.HandleFunc("/accounts/{id}", accountHandler).Methods("GET")
+ r.HandleFunc("/orders", ordersHandler).Methods("GET")
+ r.HandleFunc("/orders/{id}", orderHandler).Methods("GET")
+ r.HandleFunc("/authz", authzHandler).Methods("GET")
+ r.HandleFunc("/authz/{id}", authHandler).Methods("GET")
+ r.HandleFunc("/challenges", challengesHandler).Methods("GET")
+ r.HandleFunc("/challenges/{id}", challengeHandler).Methods("GET")
+ r.HandleFunc("/certificates", certificatesHandler).Methods("GET")
+ r.HandleFunc("/certificates/{id}", certificateHandler).Methods("GET")
+ r.HandleFunc("/certificates/{id}", certRevokeHandler).Methods("POST")
- r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
- if isDev {
- r.PathPrefix("/accounts/static/").Handler(http.StripPrefix("/accounts/static/", http.FileServer(http.Dir("../www"))))
- r.PathPrefix("/authz/static/").Handler(http.StripPrefix("/authz/static/", http.FileServer(http.Dir("../www"))))
- r.PathPrefix("/challenges/static/").Handler(http.StripPrefix("/challenges/static/", http.FileServer(http.Dir("../www"))))
- r.PathPrefix("/certificates/static/").Handler(http.StripPrefix("/certificates/static/", http.FileServer(http.Dir("../www"))))
- r.PathPrefix("/orders/static/").Handler(http.StripPrefix("/orders/static/", http.FileServer(http.Dir("../www"))))
- r.PathPrefix("/logs/static/").Handler(http.StripPrefix("/logs/static/", http.FileServer(http.Dir("../www"))))
- r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("../www"))))
- }
- r.Use(authorized)
+ r.NotFoundHandler = http.HandlerFunc(notFoundHandler)
+ if isDev {
+ r.PathPrefix("/accounts/static/").Handler(http.StripPrefix("/accounts/static/", http.FileServer(http.Dir("../www"))))
+ r.PathPrefix("/authz/static/").Handler(http.StripPrefix("/authz/static/", http.FileServer(http.Dir("../www"))))
+ r.PathPrefix("/challenges/static/").Handler(http.StripPrefix("/challenges/static/", http.FileServer(http.Dir("../www"))))
+ r.PathPrefix("/certificates/static/").Handler(http.StripPrefix("/certificates/static/", http.FileServer(http.Dir("../www"))))
+ r.PathPrefix("/orders/static/").Handler(http.StripPrefix("/orders/static/", http.FileServer(http.Dir("../www"))))
+ r.PathPrefix("/logs/static/").Handler(http.StripPrefix("/logs/static/", http.FileServer(http.Dir("../www"))))
+ r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("../www"))))
+ }
+ r.Use(authorized)
- log.Printf("Listening on %s:%d...\n", viper.GetString("server.addr"), viper.GetInt("server.port"))
- srv := &http.Server{
- Handler: r,
- Addr: viper.GetString("server.addr") + ":" + viper.GetString("server.port"),
- WriteTimeout: 15 * time.Second,
- ReadTimeout: 15 * time.Second,
- }
- log.Fatal(srv.ListenAndServe())
+ log.Printf("Listening on %s:%d...\n", viper.GetString("server.addr"), viper.GetInt("server.port"))
+ srv := &http.Server{
+ Handler: r,
+ Addr: viper.GetString("server.addr") + ":" + viper.GetString("server.port"),
+ WriteTimeout: 15 * time.Second,
+ ReadTimeout: 15 * time.Second,
+ }
+ log.Fatal(srv.ListenAndServe())
}
From 04aed0ed86044d422246ab20a7e0869f9183e515 Mon Sep 17 00:00:00 2001
From: Arjan H
Date: Fri, 23 Nov 2018 20:46:57 +0100
Subject: [PATCH 2/3] Refactor code to reduce cyclomatic complexity
#2
---
README.md | 2 +-
gui/acme.go | 56 +--
gui/certificate.go | 636 ++++++++++++++++++---------------
gui/dashboard.go | 13 +-
gui/main.go | 865 ++++++++++++++++++++++++---------------------
5 files changed, 862 insertions(+), 710 deletions(-)
diff --git a/README.md b/README.md
index 3aef0b3..e265d7a 100644
--- a/README.md
+++ b/README.md
@@ -52,7 +52,7 @@ Once the setup is completed, please make a backup of your Root and Issuer certif
### Update
-Until issue #1 is implemented, updates can only be done from the Linux shell. On the server run this command as root to update the installation:
+Until [issue #1](https://github.com/hakwerk/labca/issues/1) is implemented, updates can only be done from the Linux shell. On the server run this command as root to update the installation:
```sh
~labca/labca/install
diff --git a/gui/acme.go b/gui/acme.go
index 3bacc2d..3ede93f 100644
--- a/gui/acme.go
+++ b/gui/acme.go
@@ -630,6 +630,36 @@ type CertificateExtra struct {
IsExpired bool
}
+func _getReasonText(RevokedReason int, Revoked string) string {
+ reasonText := ""
+ switch RevokedReason {
+ case 0:
+ if Revoked != "0000-00-00 00:00:00" {
+ reasonText = " - Unspecified"
+ }
+ case 1:
+ reasonText = " - Key Compromise"
+ case 2:
+ reasonText = " - CA Compromise"
+ case 3:
+ reasonText = " - Affiliation Changed"
+ case 4:
+ reasonText = " - Superseded"
+ case 5:
+ reasonText = " - Cessation Of Operation"
+ case 6:
+ reasonText = " - Certificate Hold"
+ case 8:
+ reasonText = " - Remove From CRL"
+ case 9:
+ reasonText = " - Privilege Withdrawn"
+ case 10:
+ reasonText = " - AA Compromise"
+ }
+
+ return reasonText
+}
+
func GetCertificate(w http.ResponseWriter, r *http.Request, id int, serial string) (CertificateShow, error) {
db, err := sql.Open(dbType, dbConn)
if err != nil {
@@ -681,31 +711,7 @@ func GetCertificate(w http.ResponseWriter, r *http.Request, id int, serial strin
CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Status", row.Status})
CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"OCSP Last Update", row.OCSPLastUpdate})
CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked", row.Revoked})
- reasonText := ""
- switch row.RevokedReason {
- case 0:
- if row.Revoked != "0000-00-00 00:00:00" {
- reasonText = " - Unspecified"
- }
- case 1:
- reasonText = " - Key Compromise"
- case 2:
- reasonText = " - CA Compromise"
- case 3:
- reasonText = " - Affiliation Changed"
- case 4:
- reasonText = " - Superseded"
- case 5:
- reasonText = " - Cessation Of Operation"
- case 6:
- reasonText = " - Certificate Hold"
- case 8:
- reasonText = " - Remove From CRL"
- case 9:
- reasonText = " - Privilege Withdrawn"
- case 10:
- reasonText = " - AA Compromise"
- }
+ reasonText := _getReasonText(row.RevokedReason, row.Revoked)
CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Revoked Reason", strconv.Itoa(row.RevokedReason) + reasonText})
CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Last Expiration Nag Sent", row.LastNagSent})
CertificateDetails.Rows = append(CertificateDetails.Rows, NameValue{"Not After", row.NotAfter})
diff --git a/gui/certificate.go b/gui/certificate.go
index 94bd441..66b9b8a 100644
--- a/gui/certificate.go
+++ b/gui/certificate.go
@@ -49,22 +49,26 @@ func (ci *CertificateInfo) Initialize() {
ci.KeyType = "rsa4096"
}
+func (ci *CertificateInfo) ValidateGenerate() {
+ if strings.TrimSpace(ci.KeyType) == "" || strings.TrimSpace(ci.KeyTypes[ci.KeyType]) == "" {
+ ci.Errors["KeyType"] = "Please select a key type/size"
+ }
+ if strings.TrimSpace(ci.Country) == "" || len(ci.Country) < 2 {
+ ci.Errors["Country"] = "Please enter a valid 2-character country code"
+ }
+ if strings.TrimSpace(ci.Organization) == "" {
+ ci.Errors["Organization"] = "Please enter an organization name"
+ }
+ if strings.TrimSpace(ci.CommonName) == "" {
+ ci.Errors["CommonName"] = "Please enter a common name"
+ }
+}
+
func (ci *CertificateInfo) Validate() bool {
ci.Errors = make(map[string]string)
if ci.CreateType == "generate" {
- if strings.TrimSpace(ci.KeyType) == "" || strings.TrimSpace(ci.KeyTypes[ci.KeyType]) == "" {
- ci.Errors["KeyType"] = "Please select a key type/size"
- }
- if strings.TrimSpace(ci.Country) == "" || len(ci.Country) < 2 {
- ci.Errors["Country"] = "Please enter a valid 2-character country code"
- }
- if strings.TrimSpace(ci.Organization) == "" {
- ci.Errors["Organization"] = "Please enter an organization name"
- }
- if strings.TrimSpace(ci.CommonName) == "" {
- ci.Errors["CommonName"] = "Please enter a common name"
- }
+ ci.ValidateGenerate()
}
if (ci.CreateType == "import") && (ci.ImportHandler != nil) {
@@ -133,6 +137,337 @@ func preCreateTasks(path string) error {
return nil
}
+func (ci *CertificateInfo) Generate(path string, certBase string) error {
+ // 1. Generate key
+ createCmd := "genrsa -aes256 -passout pass:foobar"
+ keySize := " 4096"
+ if strings.HasPrefix(ci.KeyType, "ecdsa") {
+ keySize = ""
+ createCmd = "ecparam -genkey -name "
+ if ci.KeyType == "ecdsa256" {
+ createCmd = createCmd + "prime256v1"
+ }
+ if ci.KeyType == "ecdsa384" {
+ createCmd = createCmd + "secp384r1"
+ }
+ } else {
+ if strings.HasSuffix(ci.KeyType, "3072") {
+ keySize = " 3072"
+ }
+ if strings.HasSuffix(ci.KeyType, "2048") {
+ keySize = " 2048"
+ }
+ }
+
+ if _, err := exe_cmd("openssl " + createCmd + " -out " + path + certBase + ".key" + keySize); err != nil {
+ return reportError(err)
+ }
+ if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -passin pass:foobar -out " + path + certBase + ".tmp"); err != nil {
+ return reportError(err)
+ }
+ if _, err := exe_cmd("mv " + path + certBase + ".tmp " + path + certBase + ".key"); err != nil {
+ return reportError(err)
+ }
+
+ _, _ = exe_cmd("sleep 1")
+
+ // 2. Generate certificate
+ subject := "/C=" + ci.Country + "/O=" + ci.Organization
+ if ci.OrgUnit != "" {
+ subject = subject + "/OU=" + ci.OrgUnit
+ }
+ subject = subject + "/CN=" + ci.CommonName
+ subject = strings.Replace(subject, " ", "\\\\", -1)
+
+ if ci.IsRoot {
+ if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -days 3650 -new -x509 -extensions v3_ca -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".pem"); err != nil {
+ return reportError(err)
+ }
+ } else {
+ if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -new -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".csr"); err != nil {
+ return reportError(err)
+ }
+ if _, err := exe_cmd("openssl ca -config " + path + "../openssl.cnf -extensions v3_intermediate_ca -days 3600 -md sha384 -notext -batch -in " + path + certBase + ".csr -out " + path + certBase + ".pem"); err != nil {
+ return reportError(err)
+ }
+ }
+
+ return nil
+}
+
+func (ci *CertificateInfo) ImportPkcs12(tmpFile string, tmpKey string, tmpCert string) error {
+ if ci.IsRoot {
+ if strings.Index(ci.ImportHandler.Filename, "labca_root") != 0 {
+ fmt.Printf("WARNING: importing root from .pfx file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ } else {
+ if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
+ fmt.Printf("WARNING: importing issuer from .pfx file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ }
+
+ pwd := "pass:dummy"
+ if ci.ImportPwd != "" {
+ pwd = "pass:" + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
+ }
+
+ if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nocerts -nodes -out " + tmpKey); err != nil {
+ if strings.Index(string(out), "invalid password") >= 0 {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ }
+ if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nokeys -out " + tmpCert); err != nil {
+ if strings.Index(string(out), "invalid password") >= 0 {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ }
+
+ return nil
+}
+
+func (ci *CertificateInfo) ImportZip(tmpFile string, tmpDir string) error {
+ if ci.IsRoot {
+ if (strings.Index(ci.ImportHandler.Filename, "labca_root") != 0) && (strings.Index(ci.ImportHandler.Filename, "labca_certificates") != 0) {
+ fmt.Printf("WARNING: importing root from .zip file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ } else {
+ if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
+ fmt.Printf("WARNING: importing issuer from .zip file but name is %s\n", ci.ImportHandler.Filename)
+ }
+ }
+
+ cmd := "unzip -j"
+ if ci.ImportPwd != "" {
+ cmd = cmd + " -P " + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
+ } else {
+ cmd = cmd + " -P dummy"
+ }
+ cmd = cmd + " " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -d " + tmpDir
+
+ if _, err := exe_cmd(cmd); err != nil {
+ if err.Error() == "exit status 82" {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ }
+
+ return nil
+}
+
+func (ci *CertificateInfo) Import(path string, certBase string, tmpDir string, tmpKey string, tmpCert string) error {
+ tmpFile := filepath.Join(tmpDir, ci.ImportHandler.Filename)
+
+ f, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0666)
+ if err != nil {
+ return err
+ }
+
+ defer f.Close()
+
+ io.Copy(f, ci.ImportFile)
+
+ contentType := ci.ImportHandler.Header.Get("Content-Type")
+ if contentType == "application/x-pkcs12" {
+ err := ci.ImportPkcs12(tmpFile, tmpKey, tmpCert)
+ if err != nil {
+ return err
+ }
+
+ } else if contentType == "application/zip" {
+ err := ci.ImportZip(tmpFile, tmpDir)
+ if err != nil {
+ return err
+ }
+
+ } else {
+ return errors.New("Content Type '" + contentType + "' not supported!")
+ }
+
+ return nil
+}
+
+func (ci *CertificateInfo) Upload(path string, certBase string, tmpKey string, tmpCert string) error {
+ if err := ioutil.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil {
+ return err
+ }
+
+ pwd := "pass:dummy"
+ if ci.Passphrase != "" {
+ pwd = "pass:" + strings.Replace(ci.Passphrase, " ", "\\\\", -1)
+ }
+
+ if out, err := exe_cmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil {
+ if strings.Index(string(out), ":bad decrypt:") >= 0 {
+ return errors.New("Incorrect password!")
+ } else {
+ return reportError(err)
+ }
+ } else {
+ if _, err = exe_cmd("mv " + tmpKey + "-out " + tmpKey); err != nil {
+ return reportError(err)
+ }
+ }
+
+ if err := ioutil.WriteFile(tmpCert, []byte(ci.Certificate), 0644); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (ci *CertificateInfo) ImportCerts(path string, rootCert string, rootKey string, issuerCert string, issuerKey string) error {
+ var rootSubject string
+ if (rootCert != "") && (rootKey != "") {
+ r, err := exe_cmd("openssl x509 -noout -subject -in " + rootCert)
+ if err != nil {
+ return reportError(err)
+ } else {
+ rootSubject = string(r[0 : len(r)-1])
+ fmt.Printf("Import root with subject '%s'\n", rootSubject)
+ }
+
+ r, err = exe_cmd("openssl pkey -noout -in " + rootKey)
+ if err != nil {
+ return reportError(err)
+ } else {
+ fmt.Println("Import root key")
+ }
+ }
+
+ if (issuerCert != "") && (issuerKey != "") {
+ if ci.IsRoot {
+ if err := preCreateTasks(path + "issuer/"); err != nil {
+ return err
+ }
+ }
+
+ r, err := exe_cmd("openssl x509 -noout -subject -in " + issuerCert)
+ if err != nil {
+ return reportError(err)
+ } else {
+ fmt.Printf("Import issuer with subject '%s'\n", string(r[0:len(r)-1]))
+ }
+
+ r, err = exe_cmd("openssl x509 -noout -issuer -in " + issuerCert)
+ if err != nil {
+ return reportError(err)
+ } else {
+ issuerIssuer := string(r[0 : len(r)-1])
+ fmt.Printf("Issuer certificate issued by CA '%s'\n", issuerIssuer)
+
+ if rootSubject == "" {
+ r, err := exe_cmd("openssl x509 -noout -subject -in data/root-ca.pem")
+ if err != nil {
+ return reportError(err)
+ } else {
+ rootSubject = string(r[0 : len(r)-1])
+ }
+ }
+
+ issuerIssuer = strings.Replace(issuerIssuer, "issuer=", "", -1)
+ rootSubject = strings.Replace(rootSubject, "subject=", "", -1)
+ if issuerIssuer != rootSubject {
+ return errors.New("Issuer not issued by our Root CA!")
+ }
+ }
+
+ r, err = exe_cmd("openssl pkey -noout -in " + issuerKey)
+ if err != nil {
+ return reportError(err)
+ } else {
+ fmt.Println("Import issuer key")
+ }
+ }
+
+ return nil
+}
+
+func (ci *CertificateInfo) MoveFiles(path string, rootCert string, rootKey string, issuerCert string, issuerKey string) error {
+ if rootCert != "" {
+ if _, err := exe_cmd("mv " + rootCert + " " + path); err != nil {
+ return reportError(err)
+ }
+ }
+ if rootKey != "" {
+ if _, err := exe_cmd("mv " + rootKey + " " + path); err != nil {
+ return reportError(err)
+ }
+ }
+ if issuerCert != "" {
+ if _, err := exe_cmd("mv " + issuerCert + " data/issuer/"); err != nil {
+ return reportError(err)
+ }
+ }
+ if issuerKey != "" {
+ if _, err := exe_cmd("mv " + issuerKey + " data/issuer/"); err != nil {
+ return reportError(err)
+ }
+ }
+
+ if (issuerCert != "") && (issuerKey != "") && ci.IsRoot {
+ if err := postCreateTasks(path+"issuer/", "ca-int"); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (ci *CertificateInfo) Extract(path string, certBase string, tmpDir string) error {
+ var rootCert string
+ var rootKey string
+ var issuerCert string
+ var issuerKey string
+
+ if ci.IsRoot {
+ rootCert = filepath.Join(tmpDir, "root-ca.pem")
+ rootKey = filepath.Join(tmpDir, "root-ca.key")
+
+ if _, err := os.Stat(rootCert); os.IsNotExist(err) {
+ return errors.New("File does not contain root-ca.pem!")
+ }
+ if _, err := os.Stat(rootKey); os.IsNotExist(err) {
+ return errors.New("File does not contain root-ca.key!")
+ }
+ }
+
+ issuerCert = filepath.Join(tmpDir, "ca-int.pem")
+ issuerKey = filepath.Join(tmpDir, "ca-int.key")
+
+ if _, err := os.Stat(issuerCert); os.IsNotExist(err) {
+ if ci.IsRoot {
+ issuerCert = ""
+ } else {
+ return errors.New("File does not contain ca-int.pem!")
+ }
+ }
+ if _, err := os.Stat(issuerKey); os.IsNotExist(err) {
+ if ci.IsRoot {
+ issuerKey = ""
+ } else {
+ return errors.New("File does not contain ca-int.key!")
+ }
+ }
+
+ err := ci.ImportCerts(path, rootCert, rootKey, issuerCert, issuerKey)
+ if err != nil {
+ return err
+ }
+
+ // All is good now, move files to their permanent location...
+ err = ci.MoveFiles(path, rootCert, rootKey, issuerCert, issuerKey)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
func (ci *CertificateInfo) Create(path string, certBase string) error {
if err := preCreateTasks(path); err != nil {
return err
@@ -156,156 +491,20 @@ func (ci *CertificateInfo) Create(path string, certBase string) error {
}
if ci.CreateType == "generate" {
- // 1. Generate key
- createCmd := "genrsa -aes256 -passout pass:foobar"
- keySize := " 4096"
- if strings.HasPrefix(ci.KeyType, "ecdsa") {
- keySize = ""
- createCmd = "ecparam -genkey -name "
- if ci.KeyType == "ecdsa256" {
- createCmd = createCmd + "prime256v1"
- }
- if ci.KeyType == "ecdsa384" {
- createCmd = createCmd + "secp384r1"
- }
- } else {
- if strings.HasSuffix(ci.KeyType, "3072") {
- keySize = " 3072"
- }
- if strings.HasSuffix(ci.KeyType, "2048") {
- keySize = " 2048"
- }
- }
-
- if _, err := exe_cmd("openssl " + createCmd + " -out " + path + certBase + ".key" + keySize); err != nil {
- return reportError(err)
- }
- if _, err := exe_cmd("openssl pkey -in " + path + certBase + ".key -passin pass:foobar -out " + path + certBase + ".tmp"); err != nil {
- return reportError(err)
- }
- if _, err = exe_cmd("mv " + path + certBase + ".tmp " + path + certBase + ".key"); err != nil {
- return reportError(err)
- }
-
- _, _ = exe_cmd("sleep 1")
-
- // 2. Generate certificate
- subject := "/C=" + ci.Country + "/O=" + ci.Organization
- if ci.OrgUnit != "" {
- subject = subject + "/OU=" + ci.OrgUnit
- }
- subject = subject + "/CN=" + ci.CommonName
- subject = strings.Replace(subject, " ", "\\\\", -1)
-
- if ci.IsRoot {
- if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -days 3650 -new -x509 -extensions v3_ca -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".pem"); err != nil {
- return reportError(err)
- }
- } else {
- if _, err := exe_cmd("openssl req -config " + path + "openssl.cnf -new -subj " + subject + " -key " + path + certBase + ".key -out " + path + certBase + ".csr"); err != nil {
- return reportError(err)
- }
- if _, err := exe_cmd("openssl ca -config " + path + "../openssl.cnf -extensions v3_intermediate_ca -days 3600 -md sha384 -notext -batch -in " + path + certBase + ".csr -out " + path + certBase + ".pem"); err != nil {
- return reportError(err)
- }
- }
-
- } else if ci.CreateType == "import" {
- tmpFile := filepath.Join(tmpDir, ci.ImportHandler.Filename)
-
- f, err := os.OpenFile(tmpFile, os.O_WRONLY|os.O_CREATE, 0666)
+ err := ci.Generate(path, certBase)
if err != nil {
return err
}
- defer f.Close()
-
- io.Copy(f, ci.ImportFile)
-
- contentType := ci.ImportHandler.Header.Get("Content-Type")
- if contentType == "application/x-pkcs12" {
- if ci.IsRoot {
- if strings.Index(ci.ImportHandler.Filename, "labca_root") != 0 {
- fmt.Printf("WARNING: importing root from .pfx file but name is %s\n", ci.ImportHandler.Filename)
- }
- } else {
- if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
- fmt.Printf("WARNING: importing issuer from .pfx file but name is %s\n", ci.ImportHandler.Filename)
- }
- }
-
- pwd := "pass:dummy"
- if ci.ImportPwd != "" {
- pwd = "pass:" + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
- }
-
- if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nocerts -nodes -out " + tmpKey); err != nil {
- if strings.Index(string(out), "invalid password") >= 0 {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- }
- if out, err := exe_cmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nokeys -out " + tmpCert); err != nil {
- if strings.Index(string(out), "invalid password") >= 0 {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- }
- } else if contentType == "application/zip" {
- if ci.IsRoot {
- if (strings.Index(ci.ImportHandler.Filename, "labca_root") != 0) && (strings.Index(ci.ImportHandler.Filename, "labca_certificates") != 0) {
- fmt.Printf("WARNING: importing root from .zip file but name is %s\n", ci.ImportHandler.Filename)
- }
- } else {
- if strings.Index(ci.ImportHandler.Filename, "labca_issuer") != 0 {
- fmt.Printf("WARNING: importing issuer from .zip file but name is %s\n", ci.ImportHandler.Filename)
- }
- }
-
- cmd := "unzip -j"
- if ci.ImportPwd != "" {
- cmd = cmd + " -P " + strings.Replace(ci.ImportPwd, " ", "\\\\", -1)
- } else {
- cmd = cmd + " -P dummy"
- }
- cmd = cmd + " " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -d " + tmpDir
-
- if _, err := exe_cmd(cmd); err != nil {
- if err.Error() == "exit status 82" {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- }
- } else {
- return errors.New("Content Type '" + contentType + "' not supported!")
- }
-
- } else if ci.CreateType == "upload" {
- if err := ioutil.WriteFile(tmpKey, []byte(ci.Key), 0644); err != nil {
+ } else if ci.CreateType == "import" {
+ err := ci.Import(path, certBase, tmpDir, tmpKey, tmpCert)
+ if err != nil {
return err
}
- pwd := "pass:dummy"
- if ci.Passphrase != "" {
- pwd = "pass:" + strings.Replace(ci.Passphrase, " ", "\\\\", -1)
- }
-
- if out, err := exe_cmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil {
- if strings.Index(string(out), ":bad decrypt:") >= 0 {
- return errors.New("Incorrect password!")
- } else {
- return reportError(err)
- }
- } else {
- if _, err = exe_cmd("mv " + tmpKey + "-out " + tmpKey); err != nil {
- return reportError(err)
- }
- }
-
- if err := ioutil.WriteFile(tmpCert, []byte(ci.Certificate), 0644); err != nil {
+ } else if ci.CreateType == "upload" {
+ err := ci.Upload(path, certBase, tmpKey, tmpCert)
+ if err != nil {
return err
}
@@ -315,130 +514,9 @@ func (ci *CertificateInfo) Create(path string, certBase string) error {
// This is shared between pfx/zip upload and pem text upload
if ci.CreateType != "generate" {
- var rootCert string
- var rootKey string
- var issuerCert string
- var issuerKey string
-
- if ci.IsRoot {
- rootCert = filepath.Join(tmpDir, "root-ca.pem")
- rootKey = filepath.Join(tmpDir, "root-ca.key")
-
- if _, err := os.Stat(rootCert); os.IsNotExist(err) {
- return errors.New("File does not contain root-ca.pem!")
- }
- if _, err := os.Stat(rootKey); os.IsNotExist(err) {
- return errors.New("File does not contain root-ca.key!")
- }
- }
-
- issuerCert = filepath.Join(tmpDir, "ca-int.pem")
- issuerKey = filepath.Join(tmpDir, "ca-int.key")
-
- if _, err := os.Stat(issuerCert); os.IsNotExist(err) {
- if ci.IsRoot {
- issuerCert = ""
- } else {
- return errors.New("File does not contain ca-int.pem!")
- }
- }
- if _, err := os.Stat(issuerKey); os.IsNotExist(err) {
- if ci.IsRoot {
- issuerKey = ""
- } else {
- return errors.New("File does not contain ca-int.key!")
- }
- }
-
- var rootSubject string
- if (rootCert != "") && (rootKey != "") {
- r, err := exe_cmd("openssl x509 -noout -subject -in " + rootCert)
- if err != nil {
- return reportError(err)
- } else {
- rootSubject = string(r[0 : len(r)-1])
- fmt.Printf("Import root with subject '%s'\n", rootSubject)
- }
-
- r, err = exe_cmd("openssl pkey -noout -in " + rootKey)
- if err != nil {
- return reportError(err)
- } else {
- fmt.Println("Import root key")
- }
- }
-
- if (issuerCert != "") && (issuerKey != "") {
- if ci.IsRoot {
- if err := preCreateTasks(path + "issuer/"); err != nil {
- return err
- }
- }
-
- r, err := exe_cmd("openssl x509 -noout -subject -in " + issuerCert)
- if err != nil {
- return reportError(err)
- } else {
- fmt.Printf("Import issuer with subject '%s'\n", string(r[0:len(r)-1]))
- }
-
- r, err = exe_cmd("openssl x509 -noout -issuer -in " + issuerCert)
- if err != nil {
- return reportError(err)
- } else {
- issuerIssuer := string(r[0 : len(r)-1])
- fmt.Printf("Issuer certificate issued by CA '%s'\n", issuerIssuer)
-
- if rootSubject == "" {
- r, err := exe_cmd("openssl x509 -noout -subject -in data/root-ca.pem")
- if err != nil {
- return reportError(err)
- } else {
- rootSubject = string(r[0 : len(r)-1])
- }
- }
-
- issuerIssuer = strings.Replace(issuerIssuer, "issuer=", "", -1)
- rootSubject = strings.Replace(rootSubject, "subject=", "", -1)
- if issuerIssuer != rootSubject {
- return errors.New("Issuer not issued by our Root CA!")
- }
- }
-
- r, err = exe_cmd("openssl pkey -noout -in " + issuerKey)
- if err != nil {
- return reportError(err)
- } else {
- fmt.Println("Import issuer key")
- }
- }
-
- // All is good now, move files to their permanent location...
- if rootCert != "" {
- if _, err = exe_cmd("mv " + rootCert + " " + path); err != nil {
- return reportError(err)
- }
- }
- if rootKey != "" {
- if _, err = exe_cmd("mv " + rootKey + " " + path); err != nil {
- return reportError(err)
- }
- }
- if issuerCert != "" {
- if _, err = exe_cmd("mv " + issuerCert + " data/issuer/"); err != nil {
- return reportError(err)
- }
- }
- if issuerKey != "" {
- if _, err = exe_cmd("mv " + issuerKey + " data/issuer/"); err != nil {
- return reportError(err)
- }
- }
-
- if (issuerCert != "") && (issuerKey != "") && ci.IsRoot {
- if err := postCreateTasks(path+"issuer/", "ca-int"); err != nil {
- return err
- }
+ err := ci.Extract(path, certBase, tmpDir)
+ if err != nil {
+ return err
}
}
diff --git a/gui/dashboard.go b/gui/dashboard.go
index 6ae73f1..5748f9f 100644
--- a/gui/dashboard.go
+++ b/gui/dashboard.go
@@ -20,10 +20,7 @@ type Activity struct {
Class string
}
-func _parseLine(line string, loc *time.Location) Activity {
- var activity Activity
-
- // Remove ansi colors
+func _removeAnsiColors(line string) string {
b := make([]byte, len(line))
var bl int
for i := 0; i < len(line); i++ {
@@ -38,6 +35,14 @@ func _parseLine(line string, loc *time.Location) Activity {
line = strings.Replace(line, "[1m", "", -1)
line = strings.Replace(line, "[0m", "", -1)
+ return line
+}
+
+func _parseLine(line string, loc *time.Location) Activity {
+ var activity Activity
+
+ line = _removeAnsiColors(line)
+
re := regexp.MustCompile("^.*\\|\\s*(\\S)(\\S+) (\\S+) (\\S+) (.*)$")
result := re.FindStringSubmatch(line)
diff --git a/gui/main.go b/gui/main.go
index 3f53ea3..0f75984 100644
--- a/gui/main.go
+++ b/gui/main.go
@@ -70,21 +70,7 @@ type User struct {
Errors map[string]string
}
-func (reg *User) Validate(isNew bool, isChange bool) bool {
- reg.Errors = make(map[string]string)
-
- if strings.TrimSpace(reg.Name) == "" {
- reg.Errors["Name"] = "Please enter a user name"
- }
-
- if isNew || isChange {
- re := regexp.MustCompile(".+@.+\\..+")
- matched := re.Match([]byte(reg.Email))
- if matched == false {
- reg.Errors["Email"] = "Please enter a valid email address"
- }
- }
-
+func (reg *User) ValidatePassword(isNew bool, isChange bool) {
blacklist := []string{"labca", "acme", reg.Name}
if x := strings.Index(reg.Email, "@"); x > 0 {
blacklist = append(blacklist, reg.Email[:x])
@@ -131,6 +117,24 @@ func (reg *User) Validate(isNew bool, isChange bool) bool {
reg.Errors["Password"] = "Current password is not correct!"
}
}
+}
+
+func (reg *User) Validate(isNew bool, isChange bool) bool {
+ reg.Errors = make(map[string]string)
+
+ if strings.TrimSpace(reg.Name) == "" {
+ reg.Errors["Name"] = "Please enter a user name"
+ }
+
+ if isNew || isChange {
+ re := regexp.MustCompile(".+@.+\\..+")
+ matched := re.Match([]byte(reg.Email))
+ if matched == false {
+ reg.Errors["Email"] = "Please enter a valid email address"
+ }
+ }
+
+ reg.ValidatePassword(isNew, isChange)
return len(reg.Errors) == 0
}
@@ -754,6 +758,256 @@ func _decrypt(ciphertext string) ([]byte, error) {
return gcm.Open(nil, ct[:gcm.NonceSize()], ct[gcm.NonceSize():], nil)
}
+type Result struct {
+ Success bool
+ Message string
+ Timestamp string
+ TimestampRel string
+ Class string
+}
+
+func (res *Result) ManageComponents(w http.ResponseWriter, r *http.Request, action string) {
+ components := _parseComponents(getLog(w, r, "components"))
+ for i := 0; i < len(components); i++ {
+ if (components[i].Name == "NGINX Webserver" && (action == "nginx-reload" || action == "nginx-restart")) ||
+ (components[i].Name == "Host Service" && action == "svc-restart") ||
+ (components[i].Name == "Boulder (ACME)" && (action == "boulder-start" || action == "boulder-stop" || action == "boulder-restart")) ||
+ (components[i].Name == "LabCA Application" && action == "labca-restart") {
+ res.Timestamp = components[i].Timestamp
+ res.TimestampRel = components[i].TimestampRel
+ res.Class = components[i].Class
+ break
+ }
+ }
+}
+
+func _managePostDispatch(w http.ResponseWriter, r *http.Request, action string) bool {
+ if action == "backup-restore" || action == "backup-delete" || action == "backup-now" {
+ _backupHandler(w, r)
+ return true
+ }
+
+ if action == "cert-export" {
+ _exportHandler(w, r)
+ return true
+ }
+
+ if action == "update-account" {
+ _accountUpdateHandler(w, r)
+ return true
+ }
+
+ if action == "update-config" {
+ _configUpdateHandler(w, r)
+ return true
+ }
+
+ if action == "update-email" {
+ _emailUpdateHandler(w, r)
+ return true
+ }
+
+ if action == "send-email" {
+ _emailSendHandler(w, r)
+ return true
+ }
+
+ return false
+}
+
+func _managePost(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return
+ }
+
+ action := r.Form.Get("action")
+ actionKnown := false
+ for _, a := range []string {
+ "backup-restore",
+ "backup-delete",
+ "backup-now",
+ "cert-export",
+ "nginx-reload",
+ "nginx-restart",
+ "svc-restart",
+ "boulder-start",
+ "boulder-stop",
+ "boulder-restart",
+ "labca-restart",
+ "server-restart",
+ "server-shutdown",
+ "update-account",
+ "update-config",
+ "update-email",
+ "send-email",
+ } {
+ if a == action {
+ actionKnown = true
+ }
+ }
+ if !actionKnown {
+ errorHandler(w, r, errors.New(fmt.Sprintf("Unknown manage action '%s'", action)), http.StatusBadRequest)
+ return
+ }
+
+ if _managePostDispatch(w, r, action) {
+ return
+ }
+
+ res := &Result{ Success: true }
+ if !_hostCommand(w, r, action) {
+ res.Success = false
+ res.Message = "Command failed - see LabCA log for any details"
+ }
+
+ if action != "server-restart" && action != "server-shutdown" {
+ res.ManageComponents(w, r, action)
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(res)
+}
+
+func _manageGet(w http.ResponseWriter, r *http.Request) {
+ manageData := make(map[string]interface{})
+ manageData["RequestBase"] = r.Header.Get("X-Request-Base")
+
+ components := _parseComponents(getLog(w, r, "components"))
+ for i := 0; i < len(components); i++ {
+ if components[i].Name == "NGINX Webserver" {
+ components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/weberr"
+ components[i].LogTitle = "Web Error Log"
+
+ btn := make(map[string]interface{})
+ btn["Class"] = "btn-info"
+ btn["Id"] = "nginx-reload"
+ btn["Title"] = "Reload web server configuration with minimal impact to the users"
+ btn["Label"] = "Reload"
+ components[i].Buttons = append(components[i].Buttons, btn)
+
+ btn = make(map[string]interface{})
+ btn["Class"] = "btn-warning"
+ btn["Id"] = "nginx-restart"
+ btn["Title"] = "Restart the web server with some downtime for the users"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
+
+ if components[i].Name == "Host Service" {
+ components[i].LogUrl = ""
+ components[i].LogTitle = ""
+
+ btn := make(map[string]interface{})
+ btn["Class"] = "btn-warning"
+ btn["Id"] = "svc-restart"
+ btn["Title"] = "Restart the host service"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
+
+ if components[i].Name == "Boulder (ACME)" {
+ components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/boulder"
+ components[i].LogTitle = "ACME Log"
+
+ btn := make(map[string]interface{})
+ cls := "btn-success"
+ if components[i].TimestampRel != "stopped" {
+ cls = cls + " hidden"
+ }
+ btn["Class"] = cls
+ btn["Id"] = "boulder-start"
+ btn["Title"] = "Start the core ACME application"
+ btn["Label"] = "Start"
+ components[i].Buttons = append(components[i].Buttons, btn)
+
+ btn = make(map[string]interface{})
+ cls = "btn-warning"
+ if components[i].TimestampRel == "stopped" {
+ cls = cls + " hidden"
+ }
+ btn["Class"] = cls
+ btn["Id"] = "boulder-restart"
+ btn["Title"] = "Stop and restart the core ACME application"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
+
+ btn = make(map[string]interface{})
+ cls = "btn-danger"
+ if components[i].TimestampRel == "stopped" {
+ cls = cls + " hidden"
+ }
+ btn["Class"] = cls
+ btn["Id"] = "boulder-stop"
+ btn["Title"] = "Stop the core ACME application; users can no longer use ACME clients to interact with this instance"
+ btn["Label"] = "Stop"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
+
+ if components[i].Name == "LabCA Application" {
+ components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/labca"
+ components[i].LogTitle = "LabCA Log"
+
+ btn := make(map[string]interface{})
+ btn["Class"] = "btn-warning"
+ btn["Id"] = "labca-restart"
+ btn["Title"] = "Stop and restart this LabCA admin application"
+ btn["Label"] = "Restart"
+ components[i].Buttons = append(components[i].Buttons, btn)
+ }
+ }
+ manageData["Components"] = components
+
+ stats := _parseStats(getLog(w, r, "stats"))
+ for _, stat := range stats {
+ if stat.Name == "System Uptime" {
+ manageData["ServerTimestamp"] = stat.Hint
+ manageData["ServerTimestampRel"] = stat.Value
+ break
+ }
+ }
+
+ backupFiles := strings.Split(getLog(w, r, "backups"), "\n")
+ backupFiles = backupFiles[:len(backupFiles)-1]
+ manageData["BackupFiles"] = backupFiles
+
+ manageData["RootDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/root-ca.pem")
+ manageData["IssuerDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/issuer/ca-int.pem")
+
+ manageData["Fqdn"] = viper.GetString("labca.fqdn")
+ manageData["Organization"] = viper.GetString("labca.organization")
+ manageData["Dns"] = viper.GetString("labca.dns")
+ domain_mode := viper.GetString("labca.domain_mode")
+ manageData["DomainMode"] = domain_mode
+ if domain_mode == "lockdown" {
+ manageData["LockdownDomains"] = viper.GetString("labca.lockdown")
+ }
+ if domain_mode == "whitelist" {
+ manageData["WhitelistDomains"] = viper.GetString("labca.whitelist")
+ }
+
+ manageData["DoEmail"] = viper.GetBool("labca.email.enable")
+ manageData["Server"] = viper.GetString("labca.email.server")
+ manageData["Port"] = viper.GetInt("labca.email.port")
+ manageData["EmailUser"] = viper.GetString("labca.email.user")
+ manageData["EmailPwd"] = ""
+ if viper.Get("labca.email.pass") != nil {
+ pwd := viper.GetString("labca.email.pass")
+ result, err := _decrypt(pwd)
+ if err == nil {
+ manageData["EmailPwd"] = string(result)
+ } else {
+ log.Printf("WARNING: could not decrypt email password: %s!\n", err.Error())
+ }
+ }
+ manageData["From"] = viper.GetString("labca.email.from")
+
+ manageData["Name"] = viper.GetString("user.name")
+ manageData["Email"] = viper.GetString("user.email")
+
+ render(w, r, "manage", manageData)
+}
+
func manageHandler(w http.ResponseWriter, r *http.Request) {
if !viper.GetBool("config.complete") {
http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusFound)
@@ -761,232 +1015,9 @@ func manageHandler(w http.ResponseWriter, r *http.Request) {
}
if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
-
- action := r.Form.Get("action")
- switch action {
- case "backup-restore":
- case "backup-delete":
- case "backup-now":
- case "cert-export":
- case "nginx-reload":
- case "nginx-restart":
- case "svc-restart":
- case "boulder-start":
- case "boulder-stop":
- case "boulder-restart":
- case "labca-restart":
- case "server-restart":
- case "server-shutdown":
- case "update-account":
- case "update-config":
- case "update-email":
- case "send-email":
- default:
- errorHandler(w, r, errors.New(fmt.Sprintf("Unknown manage action '%s'", action)), http.StatusBadRequest)
- return
- }
-
- if action == "backup-restore" || action == "backup-delete" || action == "backup-now" {
- _backupHandler(w, r)
- return
- }
-
- if action == "cert-export" {
- _exportHandler(w, r)
- return
- }
-
- if action == "update-account" {
- _accountUpdateHandler(w, r)
- return
- }
-
- if action == "update-config" {
- _configUpdateHandler(w, r)
- return
- }
-
- if action == "update-email" {
- _emailUpdateHandler(w, r)
- return
- }
-
- if action == "send-email" {
- _emailSendHandler(w, r)
- return
- }
-
- res := struct {
- Success bool
- Message string
- Timestamp string
- TimestampRel string
- Class string
- }{Success: true}
- if !_hostCommand(w, r, action) {
- res.Success = false
- res.Message = "Command failed - see LabCA log for any details"
- }
-
- if action != "server-restart" && action != "server-shutdown" {
- components := _parseComponents(getLog(w, r, "components"))
- for i := 0; i < len(components); i++ {
- if (components[i].Name == "NGINX Webserver" && (action == "nginx-reload" || action == "nginx-restart")) ||
- (components[i].Name == "Host Service" && action == "svc-restart") ||
- (components[i].Name == "Boulder (ACME)" && (action == "boulder-start" || action == "boulder-stop" || action == "boulder-restart")) ||
- (components[i].Name == "LabCA Application" && action == "labca-restart") {
- res.Timestamp = components[i].Timestamp
- res.TimestampRel = components[i].TimestampRel
- res.Class = components[i].Class
- break
- }
- }
- }
-
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(res)
-
+ _managePost(w,r)
} else {
- manageData := make(map[string]interface{})
- manageData["RequestBase"] = r.Header.Get("X-Request-Base")
-
- components := _parseComponents(getLog(w, r, "components"))
- for i := 0; i < len(components); i++ {
- if components[i].Name == "NGINX Webserver" {
- components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/weberr"
- components[i].LogTitle = "Web Error Log"
-
- btn := make(map[string]interface{})
- btn["Class"] = "btn-info"
- btn["Id"] = "nginx-reload"
- btn["Title"] = "Reload web server configuration with minimal impact to the users"
- btn["Label"] = "Reload"
- components[i].Buttons = append(components[i].Buttons, btn)
-
- btn = make(map[string]interface{})
- btn["Class"] = "btn-warning"
- btn["Id"] = "nginx-restart"
- btn["Title"] = "Restart the web server with some downtime for the users"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
-
- if components[i].Name == "Host Service" {
- components[i].LogUrl = ""
- components[i].LogTitle = ""
-
- btn := make(map[string]interface{})
- btn["Class"] = "btn-warning"
- btn["Id"] = "svc-restart"
- btn["Title"] = "Restart the host service"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
-
- if components[i].Name == "Boulder (ACME)" {
- components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/boulder"
- components[i].LogTitle = "ACME Log"
-
- btn := make(map[string]interface{})
- cls := "btn-success"
- if components[i].TimestampRel != "stopped" {
- cls = cls + " hidden"
- }
- btn["Class"] = cls
- btn["Id"] = "boulder-start"
- btn["Title"] = "Start the core ACME application"
- btn["Label"] = "Start"
- components[i].Buttons = append(components[i].Buttons, btn)
-
- btn = make(map[string]interface{})
- cls = "btn-warning"
- if components[i].TimestampRel == "stopped" {
- cls = cls + " hidden"
- }
- btn["Class"] = cls
- btn["Id"] = "boulder-restart"
- btn["Title"] = "Stop and restart the core ACME application"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
-
- btn = make(map[string]interface{})
- cls = "btn-danger"
- if components[i].TimestampRel == "stopped" {
- cls = cls + " hidden"
- }
- btn["Class"] = cls
- btn["Id"] = "boulder-stop"
- btn["Title"] = "Stop the core ACME application; users can no longer use ACME clients to interact with this instance"
- btn["Label"] = "Stop"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
-
- if components[i].Name == "LabCA Application" {
- components[i].LogUrl = r.Header.Get("X-Request-Base") + "/logs/labca"
- components[i].LogTitle = "LabCA Log"
-
- btn := make(map[string]interface{})
- btn["Class"] = "btn-warning"
- btn["Id"] = "labca-restart"
- btn["Title"] = "Stop and restart this LabCA admin application"
- btn["Label"] = "Restart"
- components[i].Buttons = append(components[i].Buttons, btn)
- }
- }
- manageData["Components"] = components
-
- stats := _parseStats(getLog(w, r, "stats"))
- for _, stat := range stats {
- if stat.Name == "System Uptime" {
- manageData["ServerTimestamp"] = stat.Hint
- manageData["ServerTimestampRel"] = stat.Value
- break
- }
- }
-
- backupFiles := strings.Split(getLog(w, r, "backups"), "\n")
- backupFiles = backupFiles[:len(backupFiles)-1]
- manageData["BackupFiles"] = backupFiles
-
- manageData["RootDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/root-ca.pem")
- manageData["IssuerDetails"] = _doCmdOutput(w, r, "openssl x509 -noout -text -in data/issuer/ca-int.pem")
-
- manageData["Fqdn"] = viper.GetString("labca.fqdn")
- manageData["Organization"] = viper.GetString("labca.organization")
- manageData["Dns"] = viper.GetString("labca.dns")
- domain_mode := viper.GetString("labca.domain_mode")
- manageData["DomainMode"] = domain_mode
- if domain_mode == "lockdown" {
- manageData["LockdownDomains"] = viper.GetString("labca.lockdown")
- }
- if domain_mode == "whitelist" {
- manageData["WhitelistDomains"] = viper.GetString("labca.whitelist")
- }
-
- manageData["DoEmail"] = viper.GetBool("labca.email.enable")
- manageData["Server"] = viper.GetString("labca.email.server")
- manageData["Port"] = viper.GetInt("labca.email.port")
- manageData["EmailUser"] = viper.GetString("labca.email.user")
- manageData["EmailPwd"] = ""
- if viper.Get("labca.email.pass") != nil {
- pwd := viper.GetString("labca.email.pass")
- result, err := _decrypt(pwd)
- if err == nil {
- manageData["EmailPwd"] = string(result)
- } else {
- log.Printf("WARNING: could not decrypt email password: %s!\n", err.Error())
- }
- }
- manageData["From"] = viper.GetString("labca.email.from")
-
- manageData["Name"] = viper.GetString("user.name")
- manageData["Email"] = viper.GetString("user.email")
-
- render(w, r, "manage", manageData)
+ _manageGet(w,r)
}
}
@@ -1173,6 +1204,42 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
reader(ws)
}
+func _buildCI(r *http.Request, session *sessions.Session, isRoot bool) *CertificateInfo {
+ ci := &CertificateInfo{
+ IsRoot: isRoot,
+ CreateType: "generate",
+ CommonName: "Root CA",
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+ if !isRoot {
+ ci.CommonName = "CA"
+ }
+ ci.Initialize()
+
+ if session.Values["ct"] != nil {
+ ci.CreateType = session.Values["ct"].(string)
+ }
+ if session.Values["kt"] != nil {
+ ci.KeyType = session.Values["kt"].(string)
+ }
+ if session.Values["c"] != nil {
+ ci.Country = session.Values["c"].(string)
+ }
+ if session.Values["o"] != nil {
+ ci.Organization = session.Values["o"].(string)
+ }
+ if session.Values["ou"] != nil {
+ ci.OrgUnit = session.Values["ou"].(string)
+ }
+ if session.Values["cn"] != nil {
+ ci.CommonName = session.Values["cn"].(string)
+ ci.CommonName = strings.Replace(ci.CommonName, "Root", "", -1)
+ ci.CommonName = strings.Replace(ci.CommonName, " ", " ", -1)
+ }
+
+ return ci
+}
+
func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot bool) bool {
path := "data/"
if !isRoot {
@@ -1183,37 +1250,7 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot
session := getSession(w, r)
if r.Method == "GET" {
- ci := &CertificateInfo{
- IsRoot: isRoot,
- CreateType: "generate",
- CommonName: "Root CA",
- RequestBase: r.Header.Get("X-Request-Base"),
- }
- if !isRoot {
- ci.CommonName = "CA"
- }
- ci.Initialize()
-
- if session.Values["ct"] != nil {
- ci.CreateType = session.Values["ct"].(string)
- }
- if session.Values["kt"] != nil {
- ci.KeyType = session.Values["kt"].(string)
- }
- if session.Values["c"] != nil {
- ci.Country = session.Values["c"].(string)
- }
- if session.Values["o"] != nil {
- ci.Organization = session.Values["o"].(string)
- }
- if session.Values["ou"] != nil {
- ci.OrgUnit = session.Values["ou"].(string)
- }
- if session.Values["cn"] != nil {
- ci.CommonName = session.Values["cn"].(string)
- ci.CommonName = strings.Replace(ci.CommonName, "Root", "", -1)
- ci.CommonName = strings.Replace(ci.CommonName, " ", " ", -1)
- }
+ ci := _buildCI(r, session, isRoot)
render(w, r, "cert:manage", map[string]interface{}{"CertificateInfo": ci, "Progress": _progress(certBase), "HelpText": _helptext(certBase)})
return false
@@ -1491,13 +1528,128 @@ func _helptext(stage string) template.HTML {
"you can either generate a fresh certificate or import an existing one, as long as it is signed by\n",
"the Root CA from the previous step.
\n",
"If you want to generate a certificate, by default the same key type and strength is selected as\n",
- "was choosen in the previous step when generating the root, but you may choose a different one. By\n",
+ "was chosen in the previous step when generating the root, but you may choose a different one. By\n",
"default the common name is the same as the CN for the Root CA, minus the word 'Root'.
"))
} else {
return template.HTML("")
}
}
+func _setupAdminUser(w http.ResponseWriter, r *http.Request) bool {
+ if r.Method == "GET" {
+ reg := &User{
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+ render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
+ return false
+ } else if r.Method == "POST" {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
+
+ reg := &User{
+ Name: r.Form.Get("username"),
+ Email: r.Form.Get("email"),
+ Password: r.Form.Get("password"),
+ Confirm: r.Form.Get("confirm"),
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+
+ if reg.Validate(true, false) == false {
+ render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
+ return false
+ }
+
+ hash, err := bcrypt.GenerateFromPassword([]byte(reg.Password), bcrypt.MinCost)
+ if err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
+ viper.Set("user.name", reg.Name)
+ viper.Set("user.email", reg.Email)
+ viper.Set("user.password", string(hash))
+ viper.WriteConfig()
+
+ session := getSession(w, r)
+ session.Values["user"] = reg.Name
+ session.Save(r, w)
+
+ // Fake the method to GET as we need to continue in the setupHandler() function
+ r.Method = "GET"
+ } else {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
+ return false
+ }
+
+ return true
+}
+
+func _setupBaseConfig(w http.ResponseWriter, r *http.Request) bool {
+ if r.Method == "GET" {
+ domain := viper.GetString("labca.fqdn")
+ pos := strings.Index(domain, ".")
+ if pos > -1 {
+ pos = pos + 1
+ domain = domain[pos:]
+ }
+
+ cfg := &SetupConfig{
+ Fqdn: viper.GetString("labca.fqdn"),
+ DomainMode: "lockdown",
+ LockdownDomains: domain,
+ WhitelistDomains: domain,
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+
+ render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
+ return false
+ } else if r.Method == "POST" {
+ if err := r.ParseForm(); err != nil {
+ errorHandler(w, r, err, http.StatusInternalServerError)
+ return false
+ }
+
+ cfg := &SetupConfig{
+ Fqdn: r.Form.Get("fqdn"),
+ Dns: r.Form.Get("dns"),
+ DomainMode: r.Form.Get("domain_mode"),
+ LockdownDomains: r.Form.Get("lockdown_domains"),
+ WhitelistDomains: r.Form.Get("whitelist_domains"),
+ RequestBase: r.Header.Get("X-Request-Base"),
+ }
+
+ if cfg.Validate(false) == false {
+ render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
+ return false
+ }
+
+ matched, err := regexp.MatchString(":\\d+$", cfg.Dns)
+ if err == nil && !matched {
+ cfg.Dns += ":53"
+ }
+
+ viper.Set("labca.fqdn", cfg.Fqdn)
+ viper.Set("labca.dns", cfg.Dns)
+ viper.Set("labca.domain_mode", cfg.DomainMode)
+ if cfg.DomainMode == "lockdown" {
+ viper.Set("labca.lockdown", cfg.LockdownDomains)
+ }
+ if cfg.DomainMode == "whitelist" {
+ viper.Set("labca.whitelist", cfg.WhitelistDomains)
+ }
+ viper.WriteConfig()
+
+ // Fake the method to GET as we need to continue in the setupHandler() function
+ r.Method = "GET"
+ } else {
+ http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
+ return false
+ }
+
+ return true
+}
+
func setupHandler(w http.ResponseWriter, r *http.Request) {
if viper.GetBool("config.complete") == true {
render(w, r, "index:manage", map[string]interface{}{"Message": template.HTML("Setup already completed! Go home ")})
@@ -1506,113 +1658,14 @@ func setupHandler(w http.ResponseWriter, r *http.Request) {
// 1. Setup admin user
if viper.Get("user.password") == nil {
- if r.Method == "GET" {
- reg := &User{
- RequestBase: r.Header.Get("X-Request-Base"),
- }
- render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
- return
- } else if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
-
- reg := &User{
- Name: r.Form.Get("username"),
- Email: r.Form.Get("email"),
- Password: r.Form.Get("password"),
- Confirm: r.Form.Get("confirm"),
- RequestBase: r.Header.Get("X-Request-Base"),
- }
-
- if reg.Validate(true, false) == false {
- render(w, r, "register:manage", map[string]interface{}{"User": reg, "IsLogin": true, "Progress": _progress("register"), "HelpText": _helptext("register")})
- return
- }
-
- hash, err := bcrypt.GenerateFromPassword([]byte(reg.Password), bcrypt.MinCost)
- if err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
- viper.Set("user.name", reg.Name)
- viper.Set("user.email", reg.Email)
- viper.Set("user.password", string(hash))
- viper.WriteConfig()
-
- session := getSession(w, r)
- session.Values["user"] = reg.Name
- session.Save(r, w)
-
- // Fake the method to GET as we need to continue in the setupHandler() function
- r.Method = "GET"
- } else {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
+ if !_setupAdminUser(w, r) {
return
}
}
// 2. Setup essential configuration
if viper.Get("labca.dns") == nil {
- if r.Method == "GET" {
- domain := viper.GetString("labca.fqdn")
- pos := strings.Index(domain, ".")
- if pos > -1 {
- pos = pos + 1
- domain = domain[pos:]
- }
-
- cfg := &SetupConfig{
- Fqdn: viper.GetString("labca.fqdn"),
- DomainMode: "lockdown",
- LockdownDomains: domain,
- WhitelistDomains: domain,
- RequestBase: r.Header.Get("X-Request-Base"),
- }
-
- render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
- return
- } else if r.Method == "POST" {
- if err := r.ParseForm(); err != nil {
- errorHandler(w, r, err, http.StatusInternalServerError)
- return
- }
-
- cfg := &SetupConfig{
- Fqdn: r.Form.Get("fqdn"),
- Dns: r.Form.Get("dns"),
- DomainMode: r.Form.Get("domain_mode"),
- LockdownDomains: r.Form.Get("lockdown_domains"),
- WhitelistDomains: r.Form.Get("whitelist_domains"),
- RequestBase: r.Header.Get("X-Request-Base"),
- }
-
- if cfg.Validate(false) == false {
- render(w, r, "setup:manage", map[string]interface{}{"SetupConfig": cfg, "Progress": _progress("setup"), "HelpText": _helptext("setup")})
- return
- }
-
- matched, err := regexp.MatchString(":\\d+$", cfg.Dns)
- if err == nil && !matched {
- cfg.Dns += ":53"
- }
-
- viper.Set("labca.fqdn", cfg.Fqdn)
- viper.Set("labca.dns", cfg.Dns)
- viper.Set("labca.domain_mode", cfg.DomainMode)
- if cfg.DomainMode == "lockdown" {
- viper.Set("labca.lockdown", cfg.LockdownDomains)
- }
- if cfg.DomainMode == "whitelist" {
- viper.Set("labca.whitelist", cfg.WhitelistDomains)
- }
- viper.WriteConfig()
-
- // Fake the method to GET as we need to continue in the setupHandler() function
- r.Method = "GET"
- } else {
- http.Redirect(w, r, r.Header.Get("X-Request-Base")+"/setup", http.StatusSeeOther)
+ if !_setupBaseConfig(w, r) {
return
}
}
@@ -1935,23 +1988,15 @@ type navItem struct {
SubMenu []navItem
}
-func activeNav(active string, uri string, requestBase string) []navItem {
- isAcmeActive := (uri == "/accounts" || strings.HasPrefix(uri, "/accounts/") ||
- uri == "/orders" || strings.HasPrefix(uri, "/orders/") ||
- uri == "/authz" || strings.HasPrefix(uri, "/authz/") ||
- uri == "/challenges" || strings.HasPrefix(uri, "/challenges/") ||
- uri == "/certificates" || strings.HasPrefix(uri, "/certificates/") ||
- false)
+func _matchPrefix(uri string, prefix string) bool {
+ return (uri == prefix || strings.HasPrefix(uri, prefix + "/"))
+}
+
+func _acmeNav(active string, uri string, requestBase string) navItem {
+ isAcmeActive := _matchPrefix(uri, "/accounts") || _matchPrefix(uri, "/orders") ||
+ _matchPrefix(uri, "/authz") || _matchPrefix(uri, "/challenges" ) ||
+ _matchPrefix(uri, "/certificates") || false
- // create menu items
- home := navItem{
- Name: "Dashboard",
- Icon: "fa-dashboard",
- Attrs: map[template.HTMLAttr]string{
- "href": requestBase + "/",
- "title": "Main page with the status of the system",
- },
- }
accounts := navItem{
Name: "Accounts",
Icon: "fa-list-alt",
@@ -2002,6 +2047,35 @@ func activeNav(active string, uri string, requestBase string) []navItem {
IsActive: isAcmeActive,
SubMenu: []navItem{accounts, certificates, orders, authz, challenges},
}
+
+ // set active menu class
+ switch active {
+ case "accounts":
+ accounts.Attrs["class"] = "active"
+ case "certificates":
+ certificates.Attrs["class"] = "active"
+ case "orders":
+ orders.Attrs["class"] = "active"
+ case "authz":
+ authz.Attrs["class"] = "active"
+ case "challenges":
+ challenges.Attrs["class"] = "active"
+ }
+
+ return acme
+}
+
+func activeNav(active string, uri string, requestBase string) []navItem {
+ // create menu items
+ home := navItem{
+ Name: "Dashboard",
+ Icon: "fa-dashboard",
+ Attrs: map[template.HTMLAttr]string{
+ "href": requestBase + "/",
+ "title": "Main page with the status of the system",
+ },
+ }
+ acme := _acmeNav(active, uri, requestBase)
cert := navItem{
Name: "Web Certificate",
Icon: "fa-lock",
@@ -2057,6 +2131,7 @@ func activeNav(active string, uri string, requestBase string) []navItem {
"href": "#",
"title": "Log Files",
},
+ IsActive: strings.HasPrefix(uri, "/logs/"),
SubMenu: []navItem{cert, boulder, audit, labca, web, weberr},
}
manage := navItem{
@@ -2088,22 +2163,10 @@ func activeNav(active string, uri string, requestBase string) []navItem {
switch active {
case "about":
about.Attrs["class"] = "active"
- case "accounts":
- accounts.Attrs["class"] = "active"
- case "orders":
- orders.Attrs["class"] = "active"
- case "authz":
- authz.Attrs["class"] = "active"
- case "challenges":
- challenges.Attrs["class"] = "active"
- case "certificates":
- certificates.Attrs["class"] = "active"
case "index":
home.Attrs["class"] = "active"
case "manage":
manage.Attrs["class"] = "active"
- case "logs":
- logs.Attrs["class"] = "active"
}
return []navItem{home, acme, logs, manage, about, public}
From 653b023759bb7039b615acd928c61130e68beaa8 Mon Sep 17 00:00:00 2001
From: Arjan H
Date: Sat, 24 Nov 2018 10:53:06 +0100
Subject: [PATCH 3/3] Update datatables to fix issue with clicking on some rows
---
www/js/dataTables.bootstrap.min.js | 12 +-
www/js/jquery.dataTables.min.js | 170 +----------------------------
www/js/labca.js | 5 +-
3 files changed, 11 insertions(+), 176 deletions(-)
diff --git a/www/js/dataTables.bootstrap.min.js b/www/js/dataTables.bootstrap.min.js
index f0d09b9..c27dcb5 100644
--- a/www/js/dataTables.bootstrap.min.js
+++ b/www/js/dataTables.bootstrap.min.js
@@ -1,8 +1,4 @@
-/*!
- DataTables Bootstrap 3 integration
- ©2011-2014 SpryMedia Ltd - datatables.net/license
-*/
-(function(){var f=function(c,b){c.extend(!0,b.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>",renderer:"bootstrap"});c.extend(b.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"});b.ext.renderer.pageButton.bootstrap=function(g,f,p,k,h,l){var q=new b.Api(g),r=g.oClasses,i=g.oLanguage.oPaginate,d,e,o=function(b,f){var j,m,n,a,k=function(a){a.preventDefault();
-c(a.currentTarget).hasClass("disabled")||q.page(a.data.action).draw(!1)};j=0;for(m=f.length;j",{"class":r.sPageButton+" "+
-e,"aria-controls":g.sTableId,tabindex:g.iTabIndex,id:0===p&&"string"===typeof a?g.sTableId+"_"+a:null}).append(c("",{href:"#"}).html(d)).appendTo(b),g.oApi._fnBindAction(n,{action:a},k))}};o(c(f).empty().html('').children("ul"),k)};b.TableTools&&(c.extend(!0,b.TableTools.classes,{container:"DTTT btn-group",buttons:{normal:"btn btn-default",disabled:"disabled"},collection:{container:"DTTT_dropdown dropdown-menu",buttons:{normal:"",disabled:"disabled"}},print:{info:"DTTT_print_info"},
-select:{row:"active"}}),c.extend(!0,b.TableTools.DEFAULTS.oTags,{collection:{container:"ul",button:"li",liner:"a"}}))};"function"===typeof define&&define.amd?define(["jquery","datatables"],f):"object"===typeof exports?f(require("jquery"),require("datatables")):jQuery&&f(jQuery,jQuery.fn.dataTable)})(window,document);
+/*! DataTables Bootstrap 3 integration
+ * ©2011-2015 SpryMedia Ltd - datatables.net/license
+ */
+(function(a){if(typeof define==="function"&&define.amd){define(["jquery","datatables.net"],function(b){return a(b,window,document)})}else{if(typeof exports==="object"){module.exports=function(b,c){if(!b){b=window}if(!c||!c.fn.dataTable){c=require("datatables.net")(b,c).$}return a(c,b,b.document)}}else{a(jQuery,window,document)}}}(function(d,b,a,e){var c=d.fn.dataTable;d.extend(true,c.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-5'i><'col-sm-7'p>>",renderer:"bootstrap"});d.extend(c.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm",sProcessing:"dataTables_processing panel panel-default"});c.ext.renderer.pageButton.bootstrap=function(l,u,t,r,q,j){var o=new c.Api(l);var m=l.oClasses;var i=l.oLanguage.oPaginate;var s=l.oLanguage.oAria.paginate||{};var h,g,f=0;var n=function(w,A){var y,v,z,x;var B=function(C){C.preventDefault();if(!d(C.currentTarget).hasClass("disabled")&&o.page()!=C.data.action){o.page(C.data.action).draw("page")}};for(y=0,v=A.length;y0?"":" disabled");break;case"previous":h=i.sPrevious;g=x+(q>0?"":" disabled");break;case"next":h=i.sNext;g=x+(q",{"class":m.sPageButton+" "+g,id:t===0&&typeof x==="string"?l.sTableId+"_"+x:null}).append(d("",{href:"#","aria-controls":l.sTableId,"aria-label":s[x],"data-dt-idx":f,tabindex:l.iTabIndex}).html(h)).appendTo(w);l.oApi._fnBindAction(z,{action:x},B);f++}}}};var k;try{k=d(u).find(a.activeElement).data("dt-idx")}catch(p){}n(d(u).empty().html('').children("ul"),r);if(k!==e){d(u).find("[data-dt-idx="+k+"]").focus()}};return c}));
\ No newline at end of file
diff --git a/www/js/jquery.dataTables.min.js b/www/js/jquery.dataTables.min.js
index f127725..5a75991 100644
--- a/www/js/jquery.dataTables.min.js
+++ b/www/js/jquery.dataTables.min.js
@@ -1,166 +1,4 @@
-/*!
- DataTables 1.10.12
- ©2008-2015 SpryMedia Ltd - datatables.net/license
-*/
-(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(D){return h(D,window,document)}):"object"===typeof exports?module.exports=function(D,I){D||(D=window);I||(I="undefined"!==typeof window?require("jquery"):require("jquery")(D));return h(I,D,D.document)}:h(jQuery,window,document)})(function(h,D,I,k){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),
-d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function K(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),K(a[d],b[d],c)):b[d]=b[e]})}function Da(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords");
-a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX=
-a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("
").css({position:"absolute",top:1,left:1,
-width:100,overflow:"scroll"}).append(h("
").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function hb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&&
-(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ea(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:I.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);ja(a,d,h(b).data())}function ja(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=
-(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),K(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=Q(g),i=b.mRender?Q(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&
-(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return R(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):
-!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,d=b.length;cq[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;jb&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ca(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
-c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ia(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c ").appendTo(g));b=0;for(c=l.length;btr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH);
-if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=
--1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j ",{"class":e?d[0]:""}).append(h(" ",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ka(a),g,n,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ka(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter;
-c.bSort&&mb(a);d?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("
").insertBefore(c),d=a.oFeatures,e=h("
",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,t=0;t ")[0];
-n=f[t+1];if("'"==n||'"'==n){l="";for(q=2;f[t+q]!=n;)l+=f[t+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;t+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==j&&d.bFilter)g=pb(a);else if("r"==j&&d.bProcessing)g=qb(a);else if("t"==j)g=rb(a);else if("i"==j&&d.bInfo)g=sb(a);else if("p"==
-j&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q ',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("
",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h(" ").append(j)),f=function(){var b=!this.value?
-"":this.value;b!=e.sSearch&&(fa(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,O(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?Oa(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==I.activeElement&&i.val(e.sSearch)}catch(d){}});
-return b[0]}function fa(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ga(a);if("ssp"!=y(a)){wb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b=b.length)a.aiDisplay=f.slice();
-else{if(g||c||e.length>b.length||0!==b.indexOf(e)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Pa(a,b,c,d){a=b?a:Qa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function zb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,l=m.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<
-f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Cb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Cb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),
-g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Db(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Db(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/
-e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ga(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){nb(a);kb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Fa(a);b=0;for(c=e.length;b",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g ").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());O(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},b=h("
").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;
-d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;lf&&(d=0)):"first"==b?d=0:
-"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e ",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing",
-[a,b])}function rb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("
",{"class":f.sScrollWrapper}).append(h("
",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:x(d):"100%"}).append(h("
",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
-width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("
",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:x(d)}).append(b));l&&i.append(h("
",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:x(d):"100%"}).append(h("
",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===j?g:null).append(b.children("tfoot")))));
-var b=i.children(),k=b[0],f=b[1],t=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(t.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:ka,sName:"scrolling"});return i[0]}function ka(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),j=a.nScrollBody,l=h(j),q=j.style,t=h(a.nScrollFoot).children("div"),
-m=t.children("table"),o=h(a.nTHead),F=h(a.nTable),p=F[0],r=p.style,u=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,Ua=Eb.bScrollOversize,s=G(a.aoColumns,"nTh"),P,v,w,y,z=[],A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};v=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==v&&a.scrollBarVis!==k)a.scrollBarVis=v,Y(a);else{a.scrollBarVis=v;F.children("thead, tfoot").remove();u&&(w=u.clone().prependTo(F),P=u.find("tr"),w=
-w.find("tr"));y=o.clone().prependTo(F);o=o.find("tr");v=y.find("tr");y.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(qa(a,y),function(b,c){D=Z(a,b);c.style.width=a.aoColumns[D].sWidth});u&&J(function(a){a.style.width=""},w);f=F.outerWidth();if(""===c){r.width="100%";if(Ua&&(F.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(F.outerWidth()-b);f=F.outerWidth()}else""!==d&&(r.width=x(d),f=F.outerWidth());J(E,v);J(function(a){B.push(a.innerHTML);
-z.push(x(h(a).css("width")))},v);J(function(a,b){if(h.inArray(a,s)!==-1)a.style.width=z[b]},o);h(v).height(0);u&&(J(E,w),J(function(a){C.push(a.innerHTML);A.push(x(h(a).css("width")))},w),J(function(a,b){a.style.width=A[b]},P),h(w).height(0));J(function(a,b){a.innerHTML=''+B[b]+"
";a.style.width=z[b]},v);u&&J(function(a,b){a.innerHTML=''+C[b]+"
";a.style.width=
-A[b]},w);if(F.outerWidth()j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(Ua&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(P-b);(""===c||""!==d)&&L(a,1,"Possible column misalignment",6)}else P="100%";q.width=x(P);g.width=x(P);u&&(a.nScrollFoot.style.width=x(P));!e&&Ua&&(q.height=x(p.offsetHeight+b));c=F.outerWidth();n[0].style.width=x(c);i.width=x(c);d=F.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left":
-"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=x(c),t[0].style.width=x(c),t[0].style[e]=d?b+"px":"0px");F.children("colgroup").insertBefore(F.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function J(a,b,c){for(var d=0,e=0,f=b.length,g,j;e").appendTo(j.find("tbody"));
-j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=qa(a,j.find("thead")[0]);for(m=0;m ").css({width:o.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m ").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()").css("width",x(a)).appendTo(b||I.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h(" ").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;fd&&(d=c.length,e=f);return e}function x(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;ae?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return ce?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;jg?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f/g,
-"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0e?e+1:3));e=0;for(f=d.length;ee?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j=
-d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Na(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ya(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"),
-c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Xa)},"html-num":function(b){return za(b,a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Xa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[xa(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,
-b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new r(xa(this[v.iApiIndex])):new r(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):
-(""!==d.sX||""!==d.sY)&&ka(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,
-c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),
-[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return xa(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=
-function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"):
-o.renderer="jqueryui":h.extend(i,m.ext.classes,e.oClasses);q.addClass(i.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var r=o.oLanguage;h.extend(!0,r,e.oLanguage);""!==r.sUrl&&(h.ajax({dataType:"json",url:r.sUrl,success:function(a){Da(a);K(l.oLanguage,a);h.extend(true,
-r,a);ga(o)},error:function(){ga(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=o.asStripeClasses,v=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return v.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=g.slice());t=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(o.aoHeader,g[0]),t=qa(o));if(null===e.aoColumns){p=[];g=0;for(j=t.length;g ").appendTo(this));o.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h(" ").appendTo(this));o.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0 ").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):0/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,cc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(Qa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,
-"").replace(Ya[b],"."):a},Za=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:Za(a.replace(Aa,""),b,c)?!0:null},G=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e")[0],Zb=ua.textContent!==k,$b=/<.*?>/g,Oa=m.util.throttle,Tb=[],w=Array.prototype,dc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:
-null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};r=function(a,b){if(!(this instanceof r))return new r(a,b);var c=[],d=function(a){(a=dc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;ea?new r(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c ").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});
-p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var ec=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d=0?b:g.length+b];if(typeof a==="function"){var e=Ba(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(ec):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],
-10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[Z(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()",
-"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()",
-function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ha(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m=
-h.inArray(!0,G(f,"bVisible"),c+1);i=0;for(n=j.length;id;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:
-null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new r(c):c};m.camelToHungarian=K;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||
-(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new r(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return G(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,
-d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new r(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(D).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));b.aaSorting=[];b.aaSortingFixed=[];va(b);h(l).removeClass(b.asStripeClasses.join(" "));
-h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%
-p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,n){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,n)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=Q(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.12";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,
-sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,
-sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,
-fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===
-a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",
-sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",
-renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,
-bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],
-aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,
-fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=
-this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},
-header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
-sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
-sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",H=Ca+"ui-state-default",ia=Ca+"css_right ui-icon ui-icon-",Xb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,
-m.ext.classes,{sPageButton:"fg-button ui-button "+H,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:H+" sorting_asc",sSortDesc:H+" sorting_desc",sSortable:H+" sorting",sSortableAsc:H+" sorting_asc_disabled",sSortableDesc:H+" sorting_desc_disabled",sSortableNone:H+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s",
-sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+H,sScrollFoot:"dataTables_scrollFoot "+H,sHeaderTH:H,sFooterTH:H,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ya(a,
-b)]},simple_numbers:function(a,b){return["previous",ya(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ya(a,b),"next","last"]},_numbers:ya,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},k,l,m=0,p=function(b,d){var o,r,u,s,v=function(b){Ta(a,b.data.action,true)};o=0;for(r=d.length;o ").appendTo(b);p(u,s)}else{k=null;
-l="";switch(s){case "ellipsis":b.append('… ');break;case "first":k=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":k=j.sPrevious;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":k=j.sNext;l=s+(e",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"aria-label":i[s],
-"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(k).appendTo(b);Wa(u,{action:s},v);m++}}}},r;try{r=h(b).find(I.activeElement).data("dt-idx")}catch(o){}p(h(b).empty(),d);r&&h(b).find("[data-dt-idx="+r+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||M(a)?"date":
-null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Aa,""):""},string:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob,
-" "):a}});var za=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return ab?1:0},"string-desc":function(a,
-b){return ab?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("
").addClass(d.sSortJUIWrapper).append(b.contents()).append(h(" ").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,
-f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Yb=function(a){return"string"===typeof a?a.replace(/ /g,">").replace(/"/g,"""):a};m.render={number:function(a,
-b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Yb(f);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Yb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ea,_fnColumnOptions:ja,
-_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:Z,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:la,_fnColumnTypes:Ga,_fnApplyColumnDefs:ib,_fnHungarianMap:X,_fnCamelToHungarian:K,_fnLanguageCompat:Da,_fnBrowserDetect:gb,_fnAddData:N,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:jb,_fnSplitObjNotation:Ja,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:R,
-_fnGetDataMaster:Ka,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:Ia,_fnCreateTr:Ha,_fnBuildHead:kb,_fnDrawHead:ea,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:nb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:fa,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Pa,_fnEscapeRegex:Qa,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,_fnInfoMacros:Db,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob,
-_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:ka,_fnApplyToChildren:J,_fnCalculateColumnWidths:Fa,_fnThrottle:Oa,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:x,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Va,_fnSortAttachListener:Ma,_fnSortingClasses:va,_fnSortData:Ib,_fnSaveState:wa,_fnLoadState:Kb,_fnSettingsFromNode:xa,_fnLog:L,_fnMap:E,_fnBindAction:Wa,_fnCallbackReg:z,
-_fnCallbackFire:u,_fnLengthOverflow:Sa,_fnRenderer:Na,_fnDataSource:y,_fnRowAttributes:La,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable});
+/*! DataTables 1.10.18
+ * ©2008-2018 SpryMedia Ltd - datatables.net/license
+ */
+(function(a){if(typeof define==="function"&&define.amd){define(["jquery"],function(b){return a(b,window,document)})}else{if(typeof exports==="object"){module.exports=function(b,c){if(!b){b=window}if(!c){c=typeof window!=="undefined"?require("jquery"):require("jquery")(b)}return a(c,b,b.document)}}else{a(jQuery,window,document)}}}(function(bE,a5,v,aG){var L=function(bS){this.$=function(bW,bV){return this.api(true).$(bW,bV)};this._=function(bW,bV){return this.api(true).rows(bW,bV).data()};this.api=function(bV){return bV?new H(ak(this[G.iApiIndex])):new H(this)};this.fnAddData=function(bX,bY){var bV=this.api(true);var bW=bE.isArray(bX)&&(bE.isArray(bX[0])||bE.isPlainObject(bX[0]))?bV.rows.add(bX):bV.row.add(bX);if(bY===aG||bY){bV.draw()}return bW.flatten().toArray()};this.fnAdjustColumnSizing=function(bY){var bX=this.api(true).columns.adjust();var bW=bX.settings()[0];var bV=bW.oScroll;if(bY===aG||bY){bX.draw(false)}else{if(bV.sX!==""||bV.sY!==""){i(bW)}}};this.fnClearTable=function(bW){var bV=this.api(true).clear();if(bW===aG||bW){bV.draw()}};this.fnClose=function(bV){this.api(true).row(bV).child.hide()};this.fnDeleteRow=function(bZ,b1,b0){var bW=this.api(true);var bY=bW.rows(bZ);var bV=bY.settings()[0];var bX=bV.aoData[bY[0][0]];bY.remove();if(b1){b1.call(this,bV,bX)}if(b0===aG||b0){bW.draw()}return bX};this.fnDestroy=function(bV){this.api(true).destroy(bV)};this.fnDraw=function(bV){this.api(true).draw(bV)};this.fnFilter=function(bZ,bW,bX,b1,b0,bV){var bY=this.api(true);if(bW===null||bW===aG){bY.search(bZ,bX,b1,bV)}else{bY.column(bW).search(bZ,bX,b1,bV)}bY.draw()};this.fnGetData=function(bY,bV){var bX=this.api(true);if(bY!==aG){var bW=bY.nodeName?bY.nodeName.toLowerCase():"";return bV!==aG||bW=="td"||bW=="th"?bX.cell(bY,bV).data():bX.row(bY).data()||null}return bX.data().toArray()};this.fnGetNodes=function(bW){var bV=this.api(true);return bW!==aG?bV.row(bW).node():bV.rows().nodes().flatten().toArray()};this.fnGetPosition=function(bX){var bW=this.api(true);var bY=bX.nodeName.toUpperCase();if(bY=="TR"){return bW.row(bX).index()}else{if(bY=="TD"||bY=="TH"){var bV=bW.cell(bX).index();return[bV.row,bV.columnVisible,bV.column]}}return null};this.fnIsOpen=function(bV){return this.api(true).row(bV).child.isShown()};this.fnOpen=function(bW,bV,bX){return this.api(true).row(bW).child(bV,bX).show().child()[0]};this.fnPageChange=function(bV,bX){var bW=this.api(true).page(bV);if(bX===aG||bX){bW.draw(false)}};this.fnSetColumnVis=function(bW,bV,bY){var bX=this.api(true).column(bW).visible(bV);if(bY===aG||bY){bX.columns.adjust().draw()}};this.fnSettings=function(){return ak(this[G.iApiIndex])};this.fnSort=function(bV){this.api(true).order(bV).draw()};this.fnSortListener=function(bW,bV,bX){this.api(true).order.listener(bW,bV,bX)};this.fnUpdate=function(bZ,bY,bV,b0,bX){var bW=this.api(true);if(bV===aG||bV===null){bW.row(bY).data(bZ)}else{bW.cell(bY,bV).data(bZ)}if(bX===aG||bX){bW.columns.adjust()}if(b0===aG||b0){bW.draw()}return 0};this.fnVersionCheck=G.fnVersionCheck;var bT=this;var bR=bS===aG;var bQ=this.length;if(bR){bS={}}this.oApi=this.internal=G.internal;for(var bU in L.ext.internal){if(bU){this[bU]=af(bU)}}this.each(function(){var cc={};var b8=bQ>1?aU(cc,bS,true):bS;var ci=0,cg,ch,ck,cf,bV;var b4=this.getAttribute("id");var b2=false;var b7=L.defaults;var b3=bE(this);if(this.nodeName.toLowerCase()!="table"){aK(null,0,"Non-table node initialisation ("+this.nodeName+")",2);return}a1(b7);V(b7.column);Y(b7,b7,true);Y(b7.column,b7.column,true);Y(b7,bE.extend(b8,b3.data()));var bZ=L.settings;for(ci=0,cg=bZ.length;ci").appendTo(b3)}b0.nTHead=cq[0];var co=b3.children("tbody");if(co.length===0){co=bE(" ").appendTo(b3)}b0.nTBody=co[0];var cp=b3.children("tfoot");if(cp.length===0&&cn.length>0&&(b0.oScroll.sX!==""||b0.oScroll.sY!=="")){cp=bE(" ").appendTo(b3)}if(cp.length===0||cp.children().length===0){b3.addClass(b5.sNoFooter)}else{if(cp.length>0){b0.nTFoot=cp[0];at(b0.aoFooter,b0.nTFoot)}}if(b8.aaData){for(ci=0;ci/g;var bz=/^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/;var az=new RegExp("(\\"+["/",".","*","+","?","|","(",")","[","]","{","}","\\","$","^","-"].join("|\\")+")","g");var bF=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi;var by=function(bQ){return !bQ||bQ===true||bQ==="-"?true:false};var D=function(bR){var bQ=parseInt(bR,10);return !isNaN(bQ)&&isFinite(bR)?bQ:null};var bC=function(bR,bQ){if(!a6[bQ]){a6[bQ]=new RegExp(j(bQ),"g")}return typeof bR==="string"&&bQ!=="."?bR.replace(/\./g,"").replace(a6[bQ],"."):bR};var ah=function(bT,bQ,bS){var bR=typeof bT==="string";if(by(bT)){return true}if(bQ&&bR){bT=bC(bT,bQ)}if(bS&&bR){bT=bT.replace(bF,"")}return !isNaN(parseFloat(bT))&&isFinite(bT)};var bL=function(bQ){return by(bQ)||typeof bQ==="string"};var g=function(bT,bQ,bS){if(by(bT)){return true}var bR=bL(bT);return !bR?null:ah(B(bT),bQ,bS)?true:null};var aq=function(bR,bV,bU){var bS=[];var bT=0,bQ=bR.length;if(bU!==aG){for(;bT").css({position:"fixed",top:0,left:bE(a5).scrollLeft()*-1,height:1,width:1,overflow:"hidden"}).append(bE("
").css({position:"absolute",top:1,left:1,width:100,overflow:"scroll"}).append(bE("
").css({width:"100%",height:10}))).appendTo("body");var bS=bU.children();var bQ=bS.children();bR.barWidth=bS[0].offsetWidth-bS[0].clientWidth;bR.bScrollOversize=bQ[0].offsetWidth===100&&bS[0].clientWidth!==100;bR.bScrollbarLeft=Math.round(bQ.offset().left)!==1;bR.bBounding=bU[0].getBoundingClientRect().width?true:false;bU.remove()}bE.extend(bT.oBrowser,L.__browser);bT.oScroll.iBarWidth=L.__browser.barWidth}function aS(bU,bW,bY,bQ,bS,bR){var bT=bQ,bX,bV=false;if(bY!==aG){bX=bY;bV=true}while(bT!==bS){if(!bU.hasOwnProperty(bT)){continue}bX=bV?bW(bX,bU[bT],bT,bU):bU[bT];bV=true;bT+=bR}return bX}function M(bU,bT){var bV=L.defaults.column;var bQ=bU.aoColumns.length;var bS=bE.extend({},L.models.oColumn,bV,{nTh:bT?bT:v.createElement("th"),sTitle:bV.sTitle?bV.sTitle:bT?bT.innerHTML:"",aDataSort:bV.aDataSort?bV.aDataSort:[bQ],mData:bV.mData?bV.mData:bQ,idx:bQ});bU.aoColumns.push(bS);var bR=bU.aoPreSearchCols;bR[bQ]=bE.extend({},L.models.oSearch,bR[bQ]);aZ(bU,bQ,bE(bT).data())}function aZ(bS,b1,b0){var bW=bS.aoColumns[b1];var bQ=bS.oClasses;var bR=bE(bW.nTh);if(!bW.sWidthOrig){bW.sWidthOrig=bR.attr("width")||null;var b2=(bR.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);if(b2){bW.sWidthOrig=b2[1]}}if(b0!==aG&&b0!==null){V(b0);Y(L.defaults.column,b0);if(b0.mDataProp!==aG&&!b0.mData){b0.mData=b0.mDataProp}if(b0.sType){bW._sManualType=b0.sType}if(b0.className&&!b0.sClass){b0.sClass=b0.className}if(b0.sClass){bR.addClass(b0.sClass)}bE.extend(bW,b0);P(bW,b0,"sWidth","sWidthOrig");if(b0.iDataSort!==aG){bW.aDataSort=[b0.iDataSort]}P(bW,b0,"aDataSort")}var bZ=bW.mData;var bV=al(bZ);var bY=bW.mRender?al(bW.mRender):null;var bU=function(b3){return typeof b3==="string"&&b3.indexOf("@")!==-1};bW._bAttrSrc=bE.isPlainObject(bZ)&&(bU(bZ.sort)||bU(bZ.type)||bU(bZ.filter));bW._setter=null;bW.fnGetData=function(b5,b4,b6){var b3=bV(b5,b4,aG,b6);return bY&&b4?bY(b3,b4,b5,b6):b3};bW.fnSetData=function(b3,b5,b4){return au(bZ)(b3,b5,b4)};if(typeof bZ!=="number"){bS._rowReadObject=true}if(!bS.oFeatures.bSort){bW.bSortable=false;bR.addClass(bQ.sSortableNone)}var bT=bE.inArray("asc",bW.asSorting)!==-1;var bX=bE.inArray("desc",bW.asSorting)!==-1;if(!bW.bSortable||(!bT&&!bX)){bW.sSortingClass=bQ.sSortableNone;bW.sSortingClassJUI=""}else{if(bT&&!bX){bW.sSortingClass=bQ.sSortableAsc;bW.sSortingClassJUI=bQ.sSortJUIAscAllowed}else{if(!bT&&bX){bW.sSortingClass=bQ.sSortableDesc;bW.sSortingClassJUI=bQ.sSortJUIDescAllowed}else{bW.sSortingClass=bQ.sSortable;bW.sSortingClassJUI=bQ.sSortJUI}}}}function aF(bU){if(bU.oFeatures.bAutoWidth!==false){var bT=bU.aoColumns;bt(bU);for(var bS=0,bR=bT.length;bS =0;bX--){bQ=b1[bX];var bY=bQ.targets!==aG?bQ.targets:bQ.aTargets;if(!bE.isArray(bY)){bY=[bY]}for(bW=0,b2=bY.length;bW=0){while(bU.length<=bY[bW]){M(bR)}b0(bY[bW],bQ)}else{if(typeof bY[bW]==="number"&&bY[bW]<0){b0(bU.length+bY[bW],bQ)}else{if(typeof bY[bW]==="string"){for(bV=0,bZ=bU.length;bVbT){bR[bS]--}}}if(bU!=-1&&bV===aG){bR.splice(bU,1)}}function z(bT,bR,bQ,bU){var bZ=bT.aoData[bR];var bV,bX;var bS=function(b0,b1){while(b0.childNodes.length){b0.removeChild(b0.firstChild)}b0.innerHTML=bs(bT,bR,b1,"display")};if(bQ==="dom"||((!bQ||bQ==="auto")&&bZ.src==="dom")){bZ._aData=bd(bT,bZ,bU,bU===aG?aG:bZ._aData).data}else{var bY=bZ.anCells;if(bY){if(bU!==aG){bS(bY[bU],bU)}else{for(bV=0,bX=bY.length;bV").appendTo(bV)}for(bU=0,bZ=bR.length;bUtr").attr("role","row");bE(bV).find(">tr>th, >tr>td").addClass(bS.sHeaderTH);bE(bW).find(">tr>th, >tr>td").addClass(bS.sFooterTH);if(bW!==null){var b0=bQ.aoFooter[0];for(bU=0,bZ=b0.length;bU=0;bX--){if(!bT.aoColumns[bX].bVisible&&!b5){bS[bY].splice(bX,1)}}b0.push([])}for(bY=0,bV=bS.length;bY=bS.fnRecordsDisplay()?0:b2;bS.iInitDisplayStart=-1}var bR=bS._iDisplayStart;var bT=bS.fnDisplayEnd();if(bS.bDeferLoading){bS.bDeferLoading=false;bS.iDraw++;t(bS,false)}else{if(!ca){bS.iDraw++}else{if(!bS.bDestroying&&!ad(bS)){return}}}if(bV.length!==0){var bU=ca?0:bR;var bQ=ca?bS.aoData.length:bT;for(var b8=bU;b8",{"class":b5?b0[0]:""}).append(bE(" ",{valign:"top",colSpan:aN(bS),"class":bS.oClasses.sRowEmpty}).html(b4))[0]}J(bS,"aoHeaderCallback","header",[bE(bS.nTHead).children("tr")[0],bG(bS),bR,bT,bV]);J(bS,"aoFooterCallback","footer",[bE(bS.nTFoot).children("tr")[0],bG(bS),bR,bT,bV]);var bX=bE(bS.nTBody);bX.children().detach();bX.append(bE(bW));J(bS,"aoDrawCallback","draw",[bS]);bS.bSorted=false;bS.bFiltered=false;bS.bDrawing=false}function ag(bU,bR){var bT=bU.oFeatures,bQ=bT.bSort,bS=bT.bFilter;if(bQ){u(bU)}if(bS){r(bU,bU.oPreviousSearch)}else{bU.aiDisplay=bU.aiDisplayMaster.slice()}if(bR!==true){bU._iDisplayStart=0}bU._drawHold=bR;a0(bU);bU._drawHold=false}function f(bU){var b7=bU.oClasses;var b4=bE(bU.nTable);var bW=bE("
").insertBefore(b4);var bV=bU.oFeatures;var bR=bE("
",{id:bU.sTableId+"_wrapper","class":b7.sWrapper+(bU.nTFoot?"":" "+b7.sNoFooter)});bU.nHolding=bW[0];bU.nTableWrapper=bR[0];bU.nTableReinsertBefore=bU.nTable.nextSibling;var bX=bU.sDom.split("");var b2,bY,bT,b8,b6,b0;for(var b3=0;b3 ")[0];b8=bX[b3+1];if(b8=="'"||b8=='"'){b6="";b0=2;while(bX[b3+b0]!=b8){b6+=bX[b3+b0];b0++}if(b6=="H"){b6=b7.sJUIHeader}else{if(b6=="F"){b6=b7.sJUIFooter}}if(b6.indexOf(".")!=-1){var b1=b6.split(".");bT.id=b1[0].substr(1,b1[0].length-1);bT.className=b1[1]}else{if(b6.charAt(0)=="#"){bT.id=b6.substr(1,b6.length-1)}else{bT.className=b6}}b3+=b0}bR.append(bT);bR=bE(bT)}else{if(bY==">"){bR=bR.parent()}else{if(bY=="l"&&bV.bPaginate&&bV.bLengthChange){b2=aO(bU)}else{if(bY=="f"&&bV.bFilter){b2=n(bU)}else{if(bY=="r"&&bV.bProcessing){b2=bx(bU)}else{if(bY=="t"){b2=bw(bU)}else{if(bY=="i"&&bV.bInfo){b2=d(bU)}else{if(bY=="p"&&bV.bPaginate){b2=aw(bU)}else{if(L.ext.feature.length!==0){var b5=L.ext.feature;for(var bZ=0,bQ=b5.length;bZ ';var b0=bW.sSearch;b0=b0.match(/_INPUT_/)?b0.replace("_INPUT_",b1):b0+b1;var bQ=bE("
",{id:!bR.f?bS+"_filter":null,"class":bU.sFilter}).append(bE(" ").append(b0));var bZ=function(){var b3=bR.f;var b2=!this.value?"":this.value;if(b2!=bV.sSearch){r(bT,{sSearch:b2,bRegex:bV.bRegex,bSmart:bV.bSmart,bCaseInsensitive:bV.bCaseInsensitive});bT._iDisplayStart=0;a0(bT)}};var bY=bT.searchDelay!==null?bT.searchDelay:w(bT)==="ssp"?400:0;var bX=bE("input",bQ).val(bV.sSearch).attr("placeholder",bW.sSearchPlaceholder).on("keyup.DT search.DT input.DT paste.DT cut.DT",bY?aj(bZ,bY):bZ).on("keypress.DT",function(b2){if(b2.keyCode==13){return false}}).attr("aria-controls",bS);bE(bT.nTable).on("search.dt.DT",function(b3,b2){if(bT===b2){try{if(bX[0]!==v.activeElement){bX.val(bV.sSearch)}}catch(b4){}}});return bQ[0]}function r(bT,bX,bW){var bS=bT.oPreviousSearch;var bV=bT.aoPreSearchCols;var bU=function(bY){bS.sSearch=bY.sSearch;bS.bRegex=bY.bRegex;bS.bSmart=bY.bSmart;bS.bCaseInsensitive=bY.bCaseInsensitive};var bR=function(bY){return bY.bEscapeRegex!==aG?!bY.bEscapeRegex:bY.bRegex};s(bT);if(w(bT)!="ssp"){av(bT,bX.sSearch,bW,bR(bX),bX.bSmart,bX.bCaseInsensitive);bU(bX);for(var bQ=0;bQb0.length||b0.indexOf(bS)!==0||bR.bSorted){bR.aiDisplay=bW.slice()}bY=bR.aiDisplay;for(bV=0;bV")[0];var an=b.textContent!==aG;function aA(bR){var bT=bR.aoColumns;var bS;var bV,bU,bZ,bQ,bY,bW,b1;var b0=L.ext.type.search;var bX=false;for(bV=0,bZ=bR.aoData.length;bV",{"class":bR.oClasses.sInfo,id:!bQ?bS+"_info":null});if(!bQ){bR.aoDrawCallback.push({fn:ap,sName:"information"});bT.attr("role","status").attr("aria-live","polite");bE(bR.nTable).attr("aria-describedby",bS+"_info")}return bT[0]}function ap(bT){var bQ=bT.aanFeatures.i;if(bQ.length===0){return}var bS=bT.oLanguage,bR=bT._iDisplayStart+1,bU=bT.fnDisplayEnd(),bX=bT.fnRecordsTotal(),bW=bT.fnRecordsDisplay(),bV=bW?bS.sInfo:bS.sInfoEmpty;if(bW!==bX){bV+=" "+bS.sInfoFiltered}bV+=bS.sInfoPostFix;bV=bn(bT,bV);var bY=bS.fnInfoCallback;if(bY!==null){bV=bY.call(bT.oInstance,bT,bR,bU,bX,bW,bV)}bE(bQ).html(bV)}function bn(bT,bV){var bR=bT.fnFormatNumber,bW=bT._iDisplayStart+1,bQ=bT._iDisplayLength,bU=bT.fnRecordsDisplay(),bS=bQ===-1;return bV.replace(/_START_/g,bR.call(bT,bW)).replace(/_END_/g,bR.call(bT,bT.fnDisplayEnd())).replace(/_MAX_/g,bR.call(bT,bT.fnRecordsTotal())).replace(/_TOTAL_/g,bR.call(bT,bU)).replace(/_PAGE_/g,bR.call(bT,bS?1:Math.ceil(bW/bQ))).replace(/_PAGES_/g,bR.call(bT,bS?1:Math.ceil(bU/bQ)))}function e(bT){var bW,bS,bX=bT.iInitDisplayStart;var bV=bT.aoColumns,bU;var bR=bT.oFeatures;var bQ=bT.bDeferLoading;if(!bT.bInitialised){setTimeout(function(){e(bT)},200);return}f(bT);aI(bT);a4(bT,bT.aoHeader);a4(bT,bT.aoFooter);t(bT,true);if(bR.bAutoWidth){bt(bT)}for(bW=0,bS=bV.length;bW",{name:bT+"_length","aria-controls":bT,"class":bW.sLengthSelect});for(var bX=0,b0=bU.length;bX ").addClass(bW.sLength);if(!bV.aanFeatures.l){bR[0].id=bT+"_length"}bR.children().append(bV.oLanguage.sLengthMenu.replace("_MENU_",bZ[0].outerHTML));bE("select",bR).val(bV._iDisplayLength).on("change.DT",function(b1){aQ(bV,bE(this).val());a0(bV)});bE(bV.nTable).on("length.dt.DT",function(b3,b2,b1){if(bV===b2){bE("select",bR).val(b1)}});return bR[0]}function aw(bT){var bS=bT.sPaginationType,bV=L.ext.pager[bS],bR=typeof bV==="function",bW=function(bX){a0(bX)},bU=bE("
").addClass(bT.oClasses.sPaging+bS)[0],bQ=bT.aanFeatures;if(!bR){bV.fnInit(bT,bU,bW)}if(!bQ.p){bU.id=bT.sTableId+"_paginate";bT.aoDrawCallback.push({fn:function(b0){if(bR){var bX=b0._iDisplayStart,b2=b0._iDisplayLength,bY=b0.fnRecordsDisplay(),b5=b2===-1,b3=b5?0:Math.ceil(bX/b2),bZ=b5?1:Math.ceil(bY/b2),b4=bV(b3,bZ),b1,b6;for(b1=0,b6=bQ.p.length;b1bR){bV=0}}else{if(bT=="first"){bV=0}else{if(bT=="previous"){bV=bQ>=0?bV-bQ:0;if(bV<0){bV=0}}else{if(bT=="next"){if(bV+bQ ",{id:!bQ.aanFeatures.r?bQ.sTableId+"_processing":null,"class":bQ.oClasses.sProcessing}).html(bQ.oLanguage.sProcessing).insertBefore(bQ.nTable)[0]}function t(bR,bQ){if(bR.oFeatures.bProcessing){bE(bR.aanFeatures.r).css("display",bQ?"block":"none")}J(bR,null,"processing",[bR,bQ])}function bw(b4){var b3=bE(b4.nTable);b3.attr("role","grid");var bQ=b4.oScroll;if(bQ.sX===""&&bQ.sY===""){return b4.nTable}var bY=bQ.sX;var bX=bQ.sY;var b5=b4.oClasses;var b2=b3.children("caption");var bR=b2.length?b2[0]._captionSide:null;var bU=bE(b3[0].cloneNode(false));var b7=bE(b3[0].cloneNode(false));var bW=b3.children("tfoot");var bZ="
";var bV=function(b8){return !b8?null:bJ(b8)};if(!bW.length){bW=null}var b1=bE(bZ,{"class":b5.sScrollWrapper}).append(bE(bZ,{"class":b5.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:bY?bV(bY):"100%"}).append(bE(bZ,{"class":b5.sScrollHeadInner}).css({"box-sizing":"content-box",width:bQ.sXInner||"100%"}).append(bU.removeAttr("id").css("margin-left",0).append(bR==="top"?b2:null).append(b3.children("thead"))))).append(bE(bZ,{"class":b5.sScrollBody}).css({position:"relative",overflow:"auto",width:bV(bY)}).append(b3));if(bW){b1.append(bE(bZ,{"class":b5.sScrollFoot}).css({overflow:"hidden",border:0,width:bY?bV(bY):"100%"}).append(bE(bZ,{"class":b5.sScrollFootInner}).append(b7.removeAttr("id").css("margin-left",0).append(bR==="bottom"?b2:null).append(b3.children("tfoot")))))}var bS=b1.children();var b0=bS[0];var b6=bS[1];var bT=bW?bS[2]:null;if(bY){bE(b6).on("scroll.DT",function(b8){var b9=this.scrollLeft;b0.scrollLeft=b9;if(bW){bT.scrollLeft=b9}})}bE(b6).css(bX&&bQ.bCollapse?"max-height":"height",bX);b4.nScrollHead=b0;b4.nScrollBody=b6;b4.nScrollFoot=bT;b4.aoDrawCallback.push({fn:i,sName:"scrolling"});return b1[0]}function i(cr){var cn=cr.oScroll,bV=cn.sX,ch=cn.sXInner,bS=cn.sY,cf=cn.iBarWidth,ck=bE(cr.nScrollHead),b8=ck[0].style,bU=ck.children("div"),bR=bU[0].style,cu=bU.children("table"),b1=cr.nScrollBody,cd=bE(b1),b7=b1.style,co=bE(cr.nScrollFoot),cc=co.children("div"),b9=cc.children("table"),bZ=bE(cr.nTHead),cb=bE(cr.nTable),ce=cb[0],bX=ce.style,b6=cr.nTFoot?bE(cr.nTFoot):null,bY=cr.oBrowser,b3=bY.bScrollOversize,ca=aq(cr.aoColumns,"nTh"),bT,bW,cq,cs,b4,b2,cj=[],cl=[],cg=[],cv=[],ct,b0,cw,b5=function(cx){var cy=cx.style;cy.paddingTop="0";cy.paddingBottom="0";cy.borderTopWidth="0";cy.borderBottomWidth="0";cy.height=0};var cm=b1.scrollHeight>b1.clientHeight;if(cr.scrollBarVis!==cm&&cr.scrollBarVis!==aG){cr.scrollBarVis=cm;aF(cr);return}else{cr.scrollBarVis=cm}cb.children("thead, tfoot").remove();if(b6){b2=b6.clone().prependTo(cb);bW=b6.find("tr");cs=b2.find("tr")}b4=bZ.clone().prependTo(cb);bT=bZ.find("tr");cq=b4.find("tr");b4.find("th, td").removeAttr("tabindex");if(!bV){b7.width="100%";ck[0].style.width="100%"}bE.each(bf(cr,b4),function(cx,cy){ct=o(cr,cx);cy.style.width=cr.aoColumns[ct].sWidth});if(b6){a7(function(cx){cx.style.width=""},cs)}cw=cb.outerWidth();if(bV===""){bX.width="100%";if(b3&&(cb.find("tbody").height()>b1.offsetHeight||cd.css("overflow-y")=="scroll")){bX.width=bJ(cb.outerWidth()-cf)}cw=cb.outerWidth()}else{if(ch!==""){bX.width=bJ(ch);cw=cb.outerWidth()}}a7(b5,cq);a7(function(cx){cg.push(cx.innerHTML);cj.push(bJ(bE(cx).css("width")))},cq);a7(function(cy,cx){if(bE.inArray(cy,ca)!==-1){cy.style.width=cj[cx]}},bT);bE(cq).height(0);if(b6){a7(b5,cs);a7(function(cx){cv.push(cx.innerHTML);cl.push(bJ(bE(cx).css("width")))},cs);a7(function(cy,cx){cy.style.width=cl[cx]},bW);bE(cs).height(0)}a7(function(cy,cx){cy.innerHTML=''+cg[cx]+"
";cy.childNodes[0].style.height="0";cy.childNodes[0].style.overflow="hidden";cy.style.width=cj[cx]},cq);if(b6){a7(function(cy,cx){cy.innerHTML=''+cv[cx]+"
";cy.childNodes[0].style.height="0";cy.childNodes[0].style.overflow="hidden";cy.style.width=cl[cx]},cs)}if(cb.outerWidth()b1.offsetHeight||cd.css("overflow-y")=="scroll"))?cw+cf:cw;if(b3&&(b1.scrollHeight>b1.offsetHeight||cd.css("overflow-y")=="scroll")){bX.width=bJ(b0-cf)}if(bV===""||ch!==""){aK(cr,1,"Possible column misalignment",6)}}else{b0="100%"}b7.width=bJ(b0);b8.width=bJ(b0);if(b6){cr.nScrollFoot.style.width=bJ(b0)}if(!bS){if(b3){b7.height=bJ(ce.offsetHeight+cf)}}var bQ=cb.outerWidth();cu[0].style.width=bJ(bQ);bR.width=bJ(bQ);var ci=cb.height()>b1.clientHeight||cd.css("overflow-y")=="scroll";var cp="padding"+(bY.bScrollbarLeft?"Left":"Right");bR[cp]=ci?cf+"px":"0px";if(b6){b9[0].style.width=bJ(bQ);cc[0].style.width=bJ(bQ);cc[0].style[cp]=ci?cf+"px":"0px"}cb.children("colgroup").insertBefore(cb.children("thead"));cd.scroll();if((cr.bSorted||cr.bFiltered)&&!cr._drawHold){b1.scrollTop=0}}function a7(bV,bS,bR){var bT=0,bU=0,bQ=bS.length;var bX,bW;while(bU/g;function bt(bY){var cb=bY.nTable,bU=bY.aoColumns,bS=bY.oScroll,b6=bS.sY,b8=bS.sX,bX=bS.sXInner,cg=bU.length,cd=m(bY,"bVisible"),cc=bE("th",bY.nTHead),b3=cb.getAttribute("width"),b0=cb.parentNode,ce=false,ca,bV,ci,b5,bQ,ch=bY.oBrowser,b9=ch.bScrollOversize;var b2=cb.style.width;if(b2&&b2.indexOf("%")!==-1){b3=b2}for(ca=0;ca").appendTo(bZ.find("tbody"));bZ.find("thead, tfoot").remove();bZ.append(bE(bY.nTHead).clone()).append(bE(bY.nTFoot).clone());bZ.find("tfoot th, tfoot td").css("width","");cc=bf(bY,bZ.find("thead")[0]);for(ca=0;ca ").css({width:bV.sWidthOrig,margin:0,padding:0,border:0,height:1}))}}if(bY.aoData.length){for(ca=0;ca").css(b8||b6?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(bZ).appendTo(b0);if(b8&&bX){bZ.width(bX)}else{if(b8){bZ.css("width","auto");bZ.removeAttr("width");if(bZ.width()").css("width",bJ(bR)).appendTo(bQ||v.body);var bS=bT[0].offsetWidth;bT.remove();return bS}function aH(bR,bT){var bQ=Z(bR,bT);if(bQ<0){return null}var bS=bR.aoData[bQ];return !bS.nTr?bE(" ").html(bs(bR,bQ,bT,"display"))[0]:bS.anCells[bT]}function Z(bV,bW){var bU,bQ=-1,bS=-1;for(var bT=0,bR=bV.aoData.length;bT bQ){bQ=bU.length;bS=bT}}return bS}function bJ(bQ){if(bQ===null){return"0px"}if(typeof bQ=="number"){return bQ<0?"0px":bQ+"px"}return bQ.match(/\d$/)?bQ+"px":bQ}function aE(bT){var bY,bS,bV,bZ,bW=[],b1=[],b3=bT.aoColumns,bX,b2,bQ,b0,bU=bT.aaSortingFixed,b5=bE.isPlainObject(bU),bR=[],b4=function(b6){if(b6.length&&!bE.isArray(b6[0])){bR.push(b6)}else{bE.merge(bR,b6)}};if(bE.isArray(bU)){b4(bU)}if(b5&&bU.pre){b4(bU.pre)}b4(bT.aaSorting);if(b5&&bU.post){b4(bU.post)}for(bY=0;bYcj?1:0;if(ci!==0){return ce.dir==="asc"?ci:-ci}}cm=cc[cl];cj=cc[ck];return cmcj?1:0})}else{bS.sort(function(cn,cm){var co,cl,ce,cd,cj,cf,ck,cg=ca.length,ci=b1[cn]._aSortData,ch=b1[cm]._aSortData;for(ce=0;cecl?1:0})}}bV.bSorted=true}function bb(bU){var b0;var bZ;var bV=bU.aoColumns;var bW=aE(bU);var bX=bU.oLanguage.oAria;for(var bY=0,bS=bV.length;bY/g,"");var bQ=bR.nTh;bQ.removeAttribute("aria-sort");if(bR.bSortable){if(bW.length>0&&bW[0].col==bY){bQ.setAttribute("aria-sort",bW[0].dir=="asc"?"ascending":"descending");bZ=bT[bW[0].index+1]||bT[0]}else{bZ=bT[0]}b0=b1+(bZ==="asc"?bX.sSortAscending:bX.sSortDescending)}else{b0=b1}bQ.setAttribute("aria-label",b0)}}function bh(bT,bV,bQ,bZ){var bR=bT.aoColumns[bV];var bX=bT.aaSorting;var bU=bR.asSorting;var bY;var bW=function(b1,b2){var b0=b1._idx;if(b0===aG){b0=bE.inArray(b1[1],bU)}return b0+10&&b0.time<+new Date()-(b1*1000)){bX();return}if(b0.columns&&bT.length!==b0.columns.length){bX();return}bV.oLoadedState=bE.extend(true,{},b0);if(b0.start!==aG){bV._iDisplayStart=b0.start;bV.iInitDisplayStart=b0.start}if(b0.length!==aG){bV._iDisplayLength=b0.length}if(b0.order!==aG){bV.aaSorting=[];bE.each(b0.order,function(b3,b2){bV.aaSorting.push(b2[0]>=bT.length?[0,b2[1]]:b2)})}if(b0.search!==aG){bE.extend(bV.oPreviousSearch,aB(b0.search))}if(b0.columns){for(bU=0,bQ=b0.columns.length;bU=bR){bT=bR-bQ}bT-=(bT%bQ);if(bQ===-1||bT<0){bT=0}bS._iDisplayStart=bT}function Q(bR,bQ){var bT=bR.renderer;var bS=L.ext.renderer[bQ];if(bE.isPlainObject(bT)&&bT[bQ]){return bS[bT[bQ]]||bS._}else{if(typeof bT==="string"){return bS[bT]||bS._}}return bS._}function w(bQ){if(bQ.oFeatures.bServerSide){return"ssp"}else{if(bQ.ajax||bQ.sAjaxSource){return"ajax"}}return"dom"}var S=[];var l=Array.prototype;var bO=function(bS){var bQ,bU;var bT=L.settings;var bR=bE.map(bT,function(bW,bV){return bW.nTable});if(!bS){return[]}else{if(bS.nTable&&bS.oApi){return[bS]}else{if(bS.nodeName&&bS.nodeName.toLowerCase()==="table"){bQ=bE.inArray(bS,bR);return bQ!==-1?[bT[bQ]]:null}else{if(bS&&typeof bS.settings==="function"){return bS.settings().toArray()}else{if(typeof bS==="string"){bU=bE(bS)}else{if(bS instanceof bE){bU=bS}}}}}}if(bU){return bU.map(function(bV){bQ=bE.inArray(this,bR);return bQ!==-1?bT[bQ]:null}).toArray()}};H=function(bS,bU){if(!(this instanceof H)){return new H(bS,bU)}var bT=[];var bV=function(bX){var bW=bO(bX);if(bW){bT=bT.concat(bW)}};if(bE.isArray(bS)){for(var bR=0,bQ=bS.length;bRbQ?new H(bR[bQ],this[bQ]):null},filter:function(bT){var bR=[];if(l.filter){bR=l.filter.call(this,bT,this)}else{for(var bS=0,bQ=this.length;bS0){return bQ[0].json}});bi("ajax.params()",function(){var bQ=this.context;if(bQ.length>0){return bQ[0].oAjaxData}});bi("ajax.reload()",function(bR,bQ){return this.iterator("table",function(bS){F(bS,bQ===false,bR)})});bi("ajax.url()",function(bR){var bQ=this.context;if(bR===aG){if(bQ.length===0){return aG}bQ=bQ[0];return bQ.ajax?bE.isPlainObject(bQ.ajax)?bQ.ajax.url:bQ.ajax:bQ.sAjaxSource}return this.iterator("table",function(bS){if(bE.isPlainObject(bS.ajax)){bS.ajax.url=bR}else{bS.ajax=bR}})});bi("ajax.url().load()",function(bR,bQ){return this.iterator("table",function(bS){F(bS,bQ===false,bR)})});var am=function(bZ,bU,b0,bT,bQ){var bW=[],bY,b1,bX,b3,bV,bR,b2=typeof bU;if(!bU||b2==="string"||b2==="function"||bU.length===aG){bU=[bU]}for(bX=0,b3=bU.length;bX0){bS[0]=bS[bR];bS[0].length=1;bS.length=1;bS.context=[bS.context[bR]];return bS}}bS.length=0;return bS};var aM=function(bS,bQ){var bT,bZ,bU,bX=[],bY=bS.aiDisplay,bV=bS.aiDisplayMaster;var b1=bQ.search,bR=bQ.order,bW=bQ.page;if(w(bS)=="ssp"){return b1==="removed"?[]:be(0,bV.length)}else{if(bW=="current"){for(bT=bS._iDisplayStart,bZ=bS.fnDisplayEnd();bT=0&&b1=="applied")){bX.push(bT)}}}}}}}return bX};var C=function(bR,bQ,bS){var bT;var bU=function(bY){var b1=D(bY);var bZ,b3;var b2=bR.aoData;if(b1!==null&&!bS){return[b1]}if(!bT){bT=aM(bR,bS)}if(b1!==null&&bE.inArray(b1,bT)!==-1){return[b1]}else{if(bY===null||bY===aG||bY===""){return bT}}if(typeof bY==="function"){return bE.map(bT,function(b5){var b6=b2[b5];return bY(b5,b6._aData,b6.nTr)?b5:null})}if(bY.nodeName){var bX=bY._DT_RowIndex;var bV=bY._DT_CellIndex;if(bX!==aG){return b2[bX]&&b2[bX].nTr===bY?[bX]:[]}else{if(bV){return b2[bV.row]&&b2[bV.row].nTr===bY?[bV.row]:[]}else{var b4=bE(bY).closest("*[data-dt-row]");return b4.length?[b4.data("dt-row")]:[]}}}if(typeof bY==="string"&&bY.charAt(0)==="#"){var b0=bR.aIds[bY.replace(/^#/,"")];if(b0!==aG){return[b0.idx]}}var bW=ab(q(bR.aoData,bT,"nTr"));return bE(bW).filter(bY).map(function(){return this._DT_RowIndex}).toArray()};return am("row",bQ,bU,bR,bS)};bi("rows()",function(bQ,bR){if(bQ===aG){bQ=""}else{if(bE.isPlainObject(bQ)){bR=bQ;bQ=""}}bR=bD(bR);var bS=this.iterator("table",function(bT){return C(bT,bQ,bR)},1);bS.selector.rows=bQ;bS.selector.opts=bR;return bS});bi("rows().nodes()",function(){return this.iterator("row",function(bQ,bR){return bQ.aoData[bR].nTr||aG},1)});bi("rows().data()",function(){return this.iterator(true,"rows",function(bQ,bR){return q(bQ.aoData,bR,"_aData")},1)});ax("rows().cache()","row().cache()",function(bQ){return this.iterator("row",function(bR,bT){var bS=bR.aoData[bT];return bQ==="search"?bS._aFilterData:bS._aSortData},1)});ax("rows().invalidate()","row().invalidate()",function(bQ){return this.iterator("row",function(bR,bS){z(bR,bS,bQ)})});ax("rows().indexes()","row().index()",function(){return this.iterator("row",function(bQ,bR){return bR},1)});ax("rows().ids()","row().id()",function(bW){var bR=[];var bU=this.context;for(var bT=0,bQ=bU.length;bT0){bW._iRecordsDisplay--}bj(bW);var bT=bW.rowIdFn(bV._aData);if(bT!==aG){delete bW.aIds[bT]}});this.iterator("table",function(bT){for(var bS=0,bR=bT.aoData.length;bS ").addClass(bX);bE("td",b0).addClass(bX).html(bZ)[0].colSpan=aN(bS);bT.push(b0[0])}};bR(bU,bQ);if(bV._details){bV._details.detach()}bV._details=bE(bT);if(bV._detailsShow){bV._details.insertAfter(bV.nTr)}};var y=function(bS,bQ){var bR=bS.context;if(bR.length){var bT=bR[0].aoData[bQ!==aG?bQ:bS[0]];if(bT&&bT._details){bT._details.remove();bT._detailsShow=aG;bT._details=aG}}};var a9=function(bS,bR){var bQ=bS.context;if(bQ.length&&bS.length){var bT=bQ[0].aoData[bS[0]];if(bT._details){bT._detailsShow=bR;if(bR){bT._details.insertAfter(bT.nTr)}else{bT._details.detach()}bu(bQ[0])}}};var bu=function(bV){var bU=new H(bV);var bT=".dt.DT_details";var bS="draw"+bT;var bQ="column-visibility"+bT;var bR="destroy"+bT;var bW=bV.aoData;bU.off(bS+" "+bQ+" "+bR);if(aq(bW,"_details").length>0){bU.on(bS,function(bY,bX){if(bV!==bX){return}bU.rows({page:"current"}).eq(0).each(function(bZ){var b0=bW[bZ];if(b0._detailsShow){b0._details.insertAfter(b0.nTr)}})});bU.on(bQ,function(b2,bZ,bX,b1){if(bV!==bZ){return}var b4,b3=aN(bZ);for(var b0=0,bY=bW.length;b0=0?bZ:bS.length+bZ]}if(typeof b2==="function"){var b4=aM(bT,bU);return bE.map(bS,function(b6,b5){return b2(b5,aW(bT,b5,0,0,b4),bR[b5])?b5:null})}var b0=typeof b2==="string"?b2.match(a8):"";if(b0){switch(b0[2]){case"visIdx":case"visible":var bX=parseInt(b0[1],10);if(bX<0){var b1=bE.map(bS,function(b5,b6){return b5.bVisible?b6:null});return[b1[b1.length+bX]]}return[o(bT,bX)];case"name":return bE.map(bW,function(b5,b6){return b5===b0[1]?b6:null});default:return[]}}if(b2.nodeName&&b2._DT_CellIndex){return[b2._DT_CellIndex.column]}var bY=bE(bR).filter(b2).map(function(){return bE.inArray(this,bR)}).toArray();if(bY.length||!b2.nodeName){return bY}var b3=bE(b2).closest("*[data-dt-column]");return b3.length?[b3.data("dt-column")]:[]};return am("column",bQ,bV,bT,bU)};var K=function(bS,bT,bQ){var bY=bS.aoColumns,bR=bY[bT],bV=bS.aoData,b1,b0,bU,bZ,bX;if(bQ===aG){return bR.bVisible}if(bR.bVisible===bQ){return}if(bQ){var bW=bE.inArray(true,aq(bY,"bVisible"),bT+1);for(bU=0,bZ=bV.length;bUbV}return true};L.isDataTable=L.fnIsDataTable=function(bS){var bQ=bE(bS).get(0);var bR=false;if(bS instanceof L.Api){return true}bE.each(L.settings,function(bV,bW){var bU=bW.nScrollHead?bE("table",bW.nScrollHead)[0]:null;var bT=bW.nScrollFoot?bE("table",bW.nScrollFoot)[0]:null;if(bW.nTable===bQ||bU===bQ||bT===bQ){bR=true}});return bR};L.tables=L.fnTables=function(bS){var bR=false;if(bE.isPlainObject(bS)){bR=bS.api;bS=bS.visible}var bQ=bE.map(L.settings,function(bT){if(!bS||(bS&&bE(bT.nTable).is(":visible"))){return bT.nTable}});return bR?new H(bQ):bQ};L.camelToHungarian=Y;bi("$()",function(bQ,bS){var bT=this.rows(bS).nodes(),bR=bE(bT);return bE([].concat(bR.filter(bQ).toArray(),bR.find(bQ).toArray()))});bE.each(["on","one","off"],function(bR,bQ){bi(bQ+"()",function(){var bS=Array.prototype.slice.call(arguments);bS[0]=bE.map(bS[0].split(/\s/),function(bU){return !bU.match(/\.dt\b/)?bU+".dt":bU}).join(" ");var bT=bE(this.tables().nodes());bT[bQ].apply(bT,bS);return this})});bi("clear()",function(){return this.iterator("table",function(bQ){bg(bQ)})});bi("settings()",function(){return new H(this.context,this.context)});bi("init()",function(){var bQ=this.context;return bQ.length?bQ[0].oInit:null});bi("data()",function(){return this.iterator("table",function(bQ){return aq(bQ.aoData,"_aData")}).flatten()});bi("destroy()",function(bQ){bQ=bQ||false;return this.iterator("table",function(bR){var b1=bR.nTableWrapper.parentNode;var bS=bR.oClasses;var b3=bR.nTable;var bW=bR.nTBody;var bY=bR.nTHead;var bZ=bR.nTFoot;var b4=bE(b3);var bV=bE(bW);var bX=bE(bR.nTableWrapper);var b5=bE.map(bR.aoData,function(b6){return b6.nTr});var bU,b2;bR.bDestroying=true;J(bR,"aoDestroyCallback","destroy",[bR]);if(!bQ){new H(bR).columns().visible(true)}bX.off(".DT").find(":not(tbody *)").off(".DT");bE(a5).off(".DT-"+bR.sInstance);if(b3!=bY.parentNode){b4.children("thead").detach();b4.append(bY)}if(bZ&&b3!=bZ.parentNode){b4.children("tfoot").detach();b4.append(bZ)}bR.aaSorting=[];bR.aaSortingFixed=[];ac(bR);bE(b5).removeClass(bR.asStripeClasses.join(" "));bE("th, td",bY).removeClass(bS.sSortable+" "+bS.sSortableAsc+" "+bS.sSortableDesc+" "+bS.sSortableNone);bV.children().detach();bV.append(b5);var bT=bQ?"remove":"detach";b4[bT]();bX[bT]();if(!bQ&&b1){b1.insertBefore(b3,bR.nTableReinsertBefore);b4.css("width",bR.sDestroyWidth).removeClass(bS.sTable);b2=bR.asDestroyStripes.length;if(b2){bV.children().each(function(b6){bE(this).addClass(bR.asDestroyStripes[b6%b2])})}}var b0=bE.inArray(bR,L.settings);if(b0!==-1){L.settings.splice(b0,1)}})});bE.each(["column","row","cell"],function(bQ,bR){bi(bR+"s().every()",function(bT){var bU=this.selector.opts;var bS=this;return this.iterator(bR,function(bZ,bY,bX,bW,bV){bT.call(bS[bR](bY,bR==="cell"?bX:bU,bR==="cell"?bU:aG),bY,bX,bW,bV)})})});bi("i18n()",function(bT,bU,bS){var bR=this.context[0];var bQ=al(bT)(bR.oLanguage);if(bQ===aG){bQ=bU}if(bS!==aG&&bE.isPlainObject(bQ)){bQ=bQ[bS]!==aG?bQ[bS]:bQ._}return bQ.replace("%d",bS)});L.version="1.10.18";L.settings=[];L.models={};L.models.oSearch={bCaseInsensitive:true,sSearch:"",bRegex:false,bSmart:true};L.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};L.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:false,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};L.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:true,bDeferRender:false,bDestroy:false,bFilter:true,bInfo:true,bLengthChange:true,bPaginate:true,bProcessing:false,bRetrieve:false,bScrollCollapse:false,bServerSide:false,bSort:true,bSortMulti:true,bSortCellsTop:false,bSortClasses:true,bStateSave:false,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(bQ){return bQ.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(bQ){try{return JSON.parse((bQ.iStateDuration===-1?sessionStorage:localStorage).getItem("DataTables_"+bQ.sInstance+"_"+location.pathname))}catch(bR){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(bQ,bR){try{(bQ.iStateDuration===-1?sessionStorage:localStorage).setItem("DataTables_"+bQ.sInstance+"_"+location.pathname,JSON.stringify(bR))}catch(bS){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:bE.extend({},L.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null,rowId:"DT_RowId"};R(L.defaults);L.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:true,bSortable:true,bVisible:true,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};R(L.defaults.column);L.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:false,bScrollbarLeft:false,bBounding:false,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:false,bInitialised:false,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:true,jqXHR:null,json:aG,oAjaxData:aG,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:false,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,oClasses:{},bFiltered:false,bSorted:false,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return w(this)=="ssp"?this._iRecordsTotal*1:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return w(this)=="ssp"?this._iRecordsDisplay*1:this.aiDisplay.length},fnDisplayEnd:function(){var bQ=this._iDisplayLength,bV=this._iDisplayStart,bS=bV+bQ,bR=this.aiDisplay.length,bT=this.oFeatures,bU=bT.bPaginate;if(bT.bServerSide){return bU===false||bQ===-1?bV+bR:Math.min(bV+bQ,this._iRecordsDisplay)}else{return !bU||bS>bR||bQ===-1?bR:bS}},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};L.ext=G={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:L.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:L.version};bE.extend(G,{afnFiltering:G.search,aTypes:G.type.detect,ofnSearch:G.type.search,oSort:G.type.order,afnSortData:G.order,aoFeatures:G.feature,oApi:G.internal,oStdClasses:G.classes,oPagination:G.pager});bE.extend(L.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var bH=L.ext.pager;function bl(bV,bQ){var bR=[],bT=bH.numbers_length,bU=Math.floor(bT/2),bS=1;if(bQ<=bT){bR=be(0,bQ)}else{if(bV<=bU){bR=be(0,bT-2);bR.push("ellipsis");bR.push(bQ-1)}else{if(bV>=bQ-1-bU){bR=be(bQ-(bT-2),bQ);bR.splice(0,0,"ellipsis");bR.splice(0,0,0)}else{bR=be(bV-bU+2,bV+bU-1);bR.push("ellipsis");bR.push(bQ-1);bR.splice(0,0,"ellipsis");bR.splice(0,0,0)}}}bR.DT_el="span";return bR}bE.extend(bH,{simple:function(bR,bQ){return["previous","next"]},full:function(bR,bQ){return["first","previous","next","last"]},numbers:function(bR,bQ){return[bl(bR,bQ)]},simple_numbers:function(bR,bQ){return["previous",bl(bR,bQ),"next"]},full_numbers:function(bR,bQ){return["first","previous",bl(bR,bQ),"next","last"]},first_last_numbers:function(bR,bQ){return["first",bl(bR,bQ),"last"]},_numbers:bl,numbers_length:7});bE.extend(true,L.ext.renderer,{pageButton:{_:function(bW,b4,b3,b1,b0,bU){var bX=bW.oClasses;var bT=bW.oLanguage.oPaginate;var b2=bW.oLanguage.oAria.paginate||{};var bS,bR,bQ=0;var bY=function(b6,cb){var b9,b5,ca,b8;var cc=function(cd){aC(bW,cd.data.action,true)};for(b9=0,b5=cb.length;b9").appendTo(b6);bY(b7,b8)}else{bS=null;bR="";switch(b8){case"ellipsis":b6.append('… ');break;case"first":bS=bT.sFirst;bR=b8+(b0>0?"":" "+bX.sPageButtonDisabled);break;case"previous":bS=bT.sPrevious;bR=b8+(b0>0?"":" "+bX.sPageButtonDisabled);break;case"next":bS=bT.sNext;bR=b8+(b0",{"class":bX.sPageButton+" "+bR,"aria-controls":bW.sTableId,"aria-label":b2[b8],"data-dt-idx":bQ,tabindex:bW.iTabIndex,id:b3===0&&typeof b8==="string"?bW.sTableId+"_"+b8:null}).html(bS).appendTo(b6);ba(ca,{action:b8},cc);bQ++}}}};var bV;try{bV=bE(b4).find(v.activeElement).data("dt-idx")}catch(bZ){}bY(bE(b4).empty(),b1);if(bV!==aG){bE(b4).find("[data-dt-idx="+bV+"]").focus()}}}});bE.extend(L.ext.type.detect,[function(bS,bR){var bQ=bR.oLanguage.sDecimal;return ah(bS,bQ)?"num"+bQ:null},function(bS,bR){if(bS&&!(bS instanceof Date)&&!bz.test(bS)){return null}var bQ=Date.parse(bS);return(bQ!==null&&!isNaN(bQ))||by(bS)?"date":null},function(bS,bR){var bQ=bR.oLanguage.sDecimal;return ah(bS,bQ,true)?"num-fmt"+bQ:null},function(bS,bR){var bQ=bR.oLanguage.sDecimal;return g(bS,bQ)?"html-num"+bQ:null},function(bS,bR){var bQ=bR.oLanguage.sDecimal;return g(bS,bQ,true)?"html-num-fmt"+bQ:null},function(bR,bQ){return by(bR)||(typeof bR==="string"&&bR.indexOf("<")!==-1)?"html":null}]);bE.extend(L.ext.type.search,{html:function(bQ){return by(bQ)?bQ:typeof bQ==="string"?bQ.replace(U," ").replace(aR,""):""},string:function(bQ){return by(bQ)?bQ:typeof bQ==="string"?bQ.replace(U," "):bQ}});var O=function(bT,bQ,bS,bR){if(bT!==0&&(!bT||bT==="-")){return -Infinity}if(bQ){bT=bC(bT,bQ)}if(bT.replace){if(bS){bT=bT.replace(bS,"")}if(bR){bT=bT.replace(bR,"")}}return bT*1};function bp(bQ){bE.each({num:function(bR){return O(bR,bQ)},"num-fmt":function(bR){return O(bR,bQ,bF)},"html-num":function(bR){return O(bR,bQ,aR)},"html-num-fmt":function(bR){return O(bR,bQ,aR,bF)}},function(bR,bS){G.type.order[bR+bQ+"-pre"]=bS;if(bR.match(/^html\-/)){G.type.search[bR+bQ]=G.type.search.html}})}bE.extend(G.type.order,{"date-pre":function(bR){var bQ=Date.parse(bR);return isNaN(bQ)?-Infinity:bQ},"html-pre":function(bQ){return by(bQ)?"":bQ.replace?bQ.replace(/<.*?>/g,"").toLowerCase():bQ+""},"string-pre":function(bQ){return by(bQ)?"":typeof bQ==="string"?bQ.toLowerCase():!bQ.toString?"":bQ.toString()},"string-asc":function(bQ,bR){return((bQ bR)?1:0))},"string-desc":function(bQ,bR){return((bQ bR)?-1:0))}});bp("");bE.extend(true,L.ext.renderer,{header:{_:function(bT,bQ,bS,bR){bE(bT.nTable).on("order.dt.DT",function(bX,bU,bW,bV){if(bT!==bU){return}var bY=bS.idx;bQ.removeClass(bS.sSortingClass+" "+bR.sSortAsc+" "+bR.sSortDesc).addClass(bV[bY]=="asc"?bR.sSortAsc:bV[bY]=="desc"?bR.sSortDesc:bS.sSortingClass)})},jqueryui:function(bT,bQ,bS,bR){bE("
").addClass(bR.sSortJUIWrapper).append(bQ.contents()).append(bE(" ").addClass(bR.sSortIcon+" "+bS.sSortingClassJUI)).appendTo(bQ);bE(bT.nTable).on("order.dt.DT",function(bX,bU,bW,bV){if(bT!==bU){return}var bY=bS.idx;bQ.removeClass(bR.sSortAsc+" "+bR.sSortDesc).addClass(bV[bY]=="asc"?bR.sSortAsc:bV[bY]=="desc"?bR.sSortDesc:bS.sSortingClass);bQ.find("span."+bR.sSortIcon).removeClass(bR.sSortJUIAsc+" "+bR.sSortJUIDesc+" "+bR.sSortJUI+" "+bR.sSortJUIAscAllowed+" "+bR.sSortJUIDescAllowed).addClass(bV[bY]=="asc"?bR.sSortJUIAsc:bV[bY]=="desc"?bR.sSortJUIDesc:bS.sSortingClassJUI)})}}});var a2=function(bQ){return typeof bQ==="string"?bQ.replace(/ /g,">").replace(/"/g,"""):bQ};L.render={number:function(bS,bR,bQ,bT,bU){return{display:function(bZ){if(typeof bZ!=="number"&&typeof bZ!=="string"){return bZ}var bW=bZ<0?"-":"";var bY=parseFloat(bZ);if(isNaN(bY)){return a2(bZ)}bY=bY.toFixed(bQ);bZ=Math.abs(bY);var bX=parseInt(bZ,10);var bV=bQ?bR+(bZ-bX).toFixed(bQ).substring(2):"";return bW+(bT||"")+bX.toString().replace(/\B(?=(\d{3})+(?!\d))/g,bS)+bV+(bU||"")}}},text:function(){return{display:a2}}};function af(bQ){return function(){var bR=[ak(this[L.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return L.ext.internal[bQ].apply(this,bR)}}bE.extend(L.ext.internal,{_fnExternApiFunc:af,_fnBuildAjax:ar,_fnAjaxUpdate:ad,_fnAjaxParameters:bK,_fnAjaxUpdateDraw:T,_fnAjaxDataSrc:br,_fnAddColumn:M,_fnColumnOptions:aZ,_fnAdjustColumnSizing:aF,_fnVisibleToColumnIndex:o,_fnColumnIndexToVisible:bI,_fnVisbleColumns:aN,_fnGetColumns:m,_fnColumnTypes:s,_fnApplyColumnDefs:h,_fnHungarianMap:R,_fnCamelToHungarian:Y,_fnLanguageCompat:aP,_fnBrowserDetect:bc,_fnAddData:aL,_fnAddTr:bP,_fnNodeToDataIndex:bq,_fnNodeToColumnIndex:aX,_fnGetCellData:bs,_fnSetCellData:bk,_fnSplitObjNotation:ai,_fnGetObjectDataFn:al,_fnSetObjectDataFn:au,_fnGetDataMaster:bG,_fnClearTable:bg,_fnDeleteIndex:a3,_fnInvalidate:z,_fnGetRowElements:bd,_fnCreateTr:N,_fnBuildHead:aI,_fnDrawHead:a4,_fnDraw:a0,_fnReDraw:ag,_fnAddOptionsHtml:f,_fnDetectHeader:at,_fnGetUniqueThs:bf,_fnFeatureHtmlFilter:n,_fnFilterComplete:r,_fnFilterCustom:ao,_fnFilterColumn:W,_fnFilter:av,_fnFilterCreateSearch:aT,_fnEscapeRegex:j,_fnFilterData:aA,_fnFeatureHtmlInfo:d,_fnUpdateInfo:ap,_fnInfoMacros:bn,_fnInitialise:e,_fnInitComplete:ay,_fnLengthChange:aQ,_fnFeatureHtmlLength:aO,_fnFeatureHtmlPaginate:aw,_fnPageChange:aC,_fnFeatureHtmlProcessing:bx,_fnProcessingDisplay:t,_fnFeatureHtmlTable:bw,_fnScrollDraw:i,_fnApplyToChildren:a7,_fnCalculateColumnWidths:bt,_fnThrottle:aj,_fnConvertToWidth:ae,_fnGetWidestNode:aH,_fnGetMaxLenString:Z,_fnStringToCss:bJ,_fnSortFlatten:aE,_fnSort:u,_fnSortAria:bb,_fnSortListener:bh,_fnSortAttachListener:A,_fnSortingClasses:ac,_fnSortData:E,_fnSaveState:bm,_fnLoadState:bN,_fnSettingsFromNode:ak,_fnLog:aK,_fnMap:P,_fnBindAction:ba,_fnCallbackReg:bM,_fnCallbackFire:J,_fnLengthOverflow:bj,_fnRenderer:Q,_fnDataSource:w,_fnRowAttributes:aa,_fnExtend:aU,_fnCalculateEnd:function(){}});bE.fn.dataTable=L;L.$=bE;bE.fn.dataTableSettings=L.settings;bE.fn.dataTableExt=L.ext;bE.fn.DataTable=function(bQ){return bE(this).dataTable(bQ).api()};bE.each(L,function(bR,bQ){bE.fn.DataTable[bR]=bQ});return bE.fn.dataTable}));
\ No newline at end of file
diff --git a/www/js/labca.js b/www/js/labca.js
index 05cf8a2..428bfd1 100644
--- a/www/js/labca.js
+++ b/www/js/labca.js
@@ -9,7 +9,6 @@ function positionFooter() {
if (offset < 0 ) {
offset = 0;
}
- console.log("applying offset of " + offset);
$("#footer").parent().css("padding-top", offset + "px");
}
@@ -283,7 +282,9 @@ $(function() {
$('.rel_certificates_list tbody').on('click', 'tr', function () {
var data = table.row( this ).data();
- window.location = window.location + '/../../certificates/' + data[0]
+ if (data) {
+ window.location = window.location + '/../../certificates/' + data[0]
+ }
});
$(".datatable").on('draw.dt', positionFooter);