diff --git a/debian/changelog b/debian/changelog index 13938e2..5d5b667 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,19 @@ +pitchfork (1.9.5+i91) stable; urgency=medium + + * #82 Documentation work. + * #84 Page_Show -> PageShow. + * #88 SQL Cleanup. + * #72 Add dep for pitchfork-data package. + * (PF59) Make iptrk, jet timeout and login attempts config variables. + * (T97) Group Member Vouch overview shows wrong affiliation. + * (T93) Add a note in UI and email about the characters used in the recovery token + * (PF Merge 163) Typo Fix by wesdawg + * (PF Merge 160) Fix PGP download link by wesdawg + * Support ARF files. + * (Issue 91) Fix toggle of sysadmin. + + -- Ben April Sun, 24 Sep 2017 00:46:15 -0400 + pitchfork (1.9.4) stable; urgency=medium * (PF65) Update login min length to be a config value along with example username. diff --git a/lib/cfg.go b/lib/cfg.go index d956c40..70e7b83 100755 --- a/lib/cfg.go +++ b/lib/cfg.go @@ -5,6 +5,7 @@ import ( "bufio" "encoding/json" "errors" + "fmt" "net" "os" "strings" @@ -12,48 +13,51 @@ import ( // PfConfig contains the configuration details for the system, as loaded from the configuration file type PfConfig struct { - Conf_root string `` /* From command line option or default setting */ - File_roots []string `json:"file_roots"` /* Where we look for files */ - Var_root string `json:"var_root"` /* Where variable files are stored */ - Tmp_roots []string `json:"tmp_roots"` /* Templates */ - LogFile string `json:"logfile"` /* Where to write our log file (with logrotate support) */ - Token_prv interface{} `` // Private portion of the JWT Token - Token_pub interface{} `` // Public portion of the JWT Token - UserAgent string `json:"useragent"` // The HTTP and SMTP/Email user agent to use when contacting other servers - CSS []string `json:"css"` // The CSS files to load (HTML meta header) - Javascript []string `json:"javascript"` // The javascript libraries to load (HTML meta header) - CSP string `json:"csp"` // The Content-Security-Protection HTTP header we include in our output - XFF []string `json:"xff_trusted_cidr"` // The CIDR prefixes that are trusted X-Forwarded-For networks - XFFc []*net.IPNet `` // Cached parsed version of X-Forward-For configuration - Db_host string `json:"db_host"` // The database hostname - Db_port string `json:"db_port"` // The database port - Db_name string `json:"db_name"` // The database name - Db_user string `json:"db_user"` // The database user - Db_pass string `json:"db_pass"` // The database password - Db_ssl_mode string `json:"db_ssl_mode"` // The database SSL mode (require|ignore) - Db_admin_db string `json:"db_admin_db"` // The database name used for administrative actions - Db_admin_user string `json:"db_admin_user"` // The database user used for administrative actions - Db_admin_pass string `json:"db_admin_pass"` // The database password used for administrative actions - Nodename string `json:"nodename"` // Name of this node (typically matches the hostname and automatically set by program) - Http_host string `json:"http_host"` // The Host on which we serve HTTP - Http_port string `json:"http_port"` // The port on which we serve HTTP - JWT_prv string `json:"jwt_key_prv"` // Private portion of the JWT Token - JWT_pub string `json:"jwt_key_pub"` // Public portion of the JWT Token - Application interface{} `json:"application"` // Application specific configuration see GetAppConfig() / GetAppConfigBool() - Username_regexp string `json:"username_regexp"` // Regular expression for filtering/rejecting usernames - UserHomeLinks bool `json:"user_home_links"` // If User Home Links are active - SMTP_host string `json:"smtp_host"` // SMTP Host to use for outbound emails - SMTP_port string `json:"smtp_port"` // SMTP Port to use for outbound emails - SMTP_SSL string `json:"smtp_ssl"` // Whether to require SSL for outbound emails (ignore|require) - Msg_mon_from string `json:"msg_monitor_from"` // Email address used for From: for monitoring messages (messages module) - Msg_mon_to string `json:"msg_monitor_to"` // Email address used for To: for monitoring messages (messages module) - TimeFormat string `json:"timeformat"` // Time Format - DateFormat string `json:"dateformat"` // Date Format - PW_WeakDicts []string `json:"pw_weakdicts"` // List of filenames containing password dictionaries - CFG_UserMinLen string `json:"username_min_length"` // Minimum Username length - CFG_UserExample string `json:"username_example"` // Username Example - TransDefault string `json:"translation_default"` // Translation - Default Language - TransLanguages []string `json:"translation_languages"` // Translation - Available Languages + Conf_root string `` /* From command line option or default setting */ + File_roots []string `json:"file_roots"` /* Where we look for files */ + Var_root string `json:"var_root"` /* Where variable files are stored */ + Tmp_roots []string `json:"tmp_roots"` /* Templates */ + LogFile string `json:"logfile"` /* Where to write our log file (with logrotate support) */ + Token_prv interface{} `` // Private portion of the JWT Token + Token_pub interface{} `` // Public portion of the JWT Token + UserAgent string `json:"useragent"` // The HTTP and SMTP/Email user agent to use when contacting other servers + CSS []string `json:"css"` // The CSS files to load (HTML meta header) + Javascript []string `json:"javascript"` // The javascript libraries to load (HTML meta header) + CSP string `json:"csp"` // The Content-Security-Protection HTTP header we include in our output + XFF []string `json:"xff_trusted_cidr"` // The CIDR prefixes that are trusted X-Forwarded-For networks + XFFc []*net.IPNet `` // Cached parsed version of X-Forward-For configuration + Db_host string `json:"db_host"` // The database hostname + Db_port string `json:"db_port"` // The database port + Db_name string `json:"db_name"` // The database name + Db_user string `json:"db_user"` // The database user + Db_pass string `json:"db_pass"` // The database password + Db_ssl_mode string `json:"db_ssl_mode"` // The database SSL mode (require|ignore) + Db_admin_db string `json:"db_admin_db"` // The database name used for administrative actions + Db_admin_user string `json:"db_admin_user"` // The database user used for administrative actions + Db_admin_pass string `json:"db_admin_pass"` // The database password used for administrative actions + Nodename string `json:"nodename"` // Name of this node (typically matches the hostname and automatically set by program) + Http_host string `json:"http_host"` // The Host on which we serve HTTP + Http_port string `json:"http_port"` // The port on which we serve HTTP + JWT_prv string `json:"jwt_key_prv"` // Private portion of the JWT Token + JWT_pub string `json:"jwt_key_pub"` // Public portion of the JWT Token + Application interface{} `json:"application"` // Application specific configuration see GetAppConfig() / GetAppConfigBool() + Username_regexp string `json:"username_regexp"` // Regular expression for filtering/rejecting usernames + UserHomeLinks bool `json:"user_home_links"` // If User Home Links are active + SMTP_host string `json:"smtp_host"` // SMTP Host to use for outbound emails + SMTP_port string `json:"smtp_port"` // SMTP Port to use for outbound emails + SMTP_SSL string `json:"smtp_ssl"` // Whether to require SSL for outbound emails (ignore|require) + Msg_mon_from string `json:"msg_monitor_from"` // Email address used for From: for monitoring messages (messages module) + Msg_mon_to string `json:"msg_monitor_to"` // Email address used for To: for monitoring messages (messages module) + TimeFormat string `json:"timeformat"` // Time Format + DateFormat string `json:"dateformat"` // Date Format + PW_WeakDicts []string `json:"pw_weakdicts"` // List of filenames containing password dictionaries + CFG_UserMinLen string `json:"username_min_length"` // Minimum Username length + CFG_UserExample string `json:"username_example"` // Username Example + TransDefault string `json:"translation_default"` // Translation - Default Language + TransLanguages []string `json:"translation_languages"` // Translation - Available Languages + IPTrkMax int `json:"iptrk_max"` /* Maximum IPTrk count, before being locked out */ + JWTTimeout int `json:"jwt_timeout"` /* JWT Timeout in minutes */ + LoginAttemptsMax int `json:"loginattempts_max"` /* Maximum Login attempts (tracked and checked per-account) */ } /* SMTP_SSL = ignore | require */ @@ -143,6 +147,9 @@ func (cfg *PfConfig) Load(toolname string, confroot string) (err error) { /* Defaults */ Config.Conf_root = confroot Config.UserHomeLinks = true + Config.IPTrkMax = 100 + Config.JWTTimeout = 30 + Config.LoginAttemptsMax = 5 /* Open the configuration file */ fn := Config.Conf_root + toolname + ".conf" @@ -271,7 +278,24 @@ func (cfg *PfConfig) Load(toolname string, confroot string) (err error) { Config.DateFormat = "2006-01-02" } - /* Check that the configuration is sane */ + /* Verify IPtrk count is minimum value */ + if Config.IPTrkMax <= 1 { + err = fmt.Errorf("iptrk_max set to %d but that would lock everybody out after one failed attempt, minimum is 1", Config.IPTrkMax) + return + } + + /* Verify JWT is at least long enough for people to be logged in for a bit */ + if Config.JWTTimeout < 5 { + err = fmt.Errorf("jwt_timeout set to %d which is too short for a useable session", Config.JWTTimeout) + return + } + + if Config.LoginAttemptsMax < 1 { + err = fmt.Errorf("loginattempts_max set to %d which would mean nobody could ever login, please configure it above 1", Config.LoginAttemptsMax) + return + } + + /* Check that XFF are sane & pre-parse it */ for _, x := range Config.XFF { var xc *net.IPNet diff --git a/lib/ctx.go b/lib/ctx.go index 9a6fb2c..34b5e19 100755 --- a/lib/ctx.go +++ b/lib/ctx.go @@ -772,7 +772,7 @@ func (ctx *PfCtxS) IsLoggedIn() bool { } // IsGroupMember can be used to check if the selected user -// is a member of the selected group and wether the user +// is a member of the selected group and whether the user // can see the group. func (ctx *PfCtxS) IsGroupMember() bool { if !ctx.HasSelectedUser() { @@ -798,7 +798,7 @@ func (ctx *PfCtxS) IsGroupMember() bool { return true } - /* Normal group users, it depends on wether they can see them */ + /* Normal group users, it depends on whether they can see them */ return state.can_see } diff --git a/lib/file.go b/lib/file.go old mode 100755 new mode 100644 index a87c739..bda5cfb --- a/lib/file.go +++ b/lib/file.go @@ -320,6 +320,7 @@ func file_mimetype(path string) (mt string, err error) { /* Quick lookup of our own to guarantee that these types are supported */ types := map[string]string{ + "arf": "application/octet-stream", "doc": "application/msword", "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "html": "text/html", diff --git a/lib/group.go b/lib/group.go index 076f9da..f16c131 100644 --- a/lib/group.go +++ b/lib/group.go @@ -2,6 +2,7 @@ package pitchfork import ( "errors" + pfpgp "trident.li/pitchfork/lib/pgp" ) const ( @@ -23,7 +24,7 @@ type PfGroup interface { Select(ctx PfCtx, group_name string, perms Perm) (err error) GetGroups(ctx PfCtx, username string) (groups []PfGroupMember, err error) GetGroupsAll() (groups []PfGroupMember, err error) - GetKeys(ctx PfCtx, keyset map[[16]byte][]byte) (err error) + GetKeys(ctx PfCtx, keyset *pfpgp.IndexedKeySet) (err error) IsMember(user string) (ismember bool, isadmin bool, out PfMemberState, err error) ListGroupMembersTot(search string) (total int, err error) ListGroupMembers(search string, username string, offset int, max int, nominated bool, inclhidden bool, exact bool) (members []PfGroupMember, err error) @@ -226,7 +227,7 @@ func (grp *PfGroupS) GetGroupsAll() (members []PfGroupMember, err error) { } // GetKeys returns the keyfile for a group -func (grp *PfGroupS) GetKeys(ctx PfCtx, keyset map[[16]byte][]byte) (err error) { +func (grp *PfGroupS) GetKeys(ctx PfCtx, keyset *pfpgp.IndexedKeySet) (err error) { var ml PfML mls, err := ml.ListWithUser(ctx, grp, ctx.SelectedUser()) if err != nil { @@ -251,8 +252,8 @@ func (grp *PfGroupS) GetKeys(ctx PfCtx, keyset map[[16]byte][]byte) (err error) mlist := ctx.SelectedML() - /* Get the ML List Key */ - err = mlist.GetKey(ctx, keyset) + /* Get the ML List Keys */ + err = mlist.GetKeys(ctx, keyset) if err != nil { return err } diff --git a/lib/ml.go b/lib/ml.go index 50d80d1..cd9bdc5 100755 --- a/lib/ml.go +++ b/lib/ml.go @@ -2,7 +2,6 @@ package pitchfork import ( - "crypto/md5" "errors" pfpgp "trident.li/pitchfork/lib/pgp" @@ -307,7 +306,7 @@ func (ml *PfML) ListWithUser(ctx PfCtx, grp PfGroup, user PfUser) (mls []PfML, e } // GetKeys returns the keys for a given mailinglist. -func (ml *PfML) GetKey(ctx PfCtx, keyset map[[16]byte][]byte) (err error) { +func (ml *PfML) GetKeys(ctx PfCtx, keyset *pfpgp.IndexedKeySet) (err error) { var key string q := "SELECT COALESCE(pubkey, '') " + @@ -322,11 +321,11 @@ func (ml *PfML) GetKey(ctx PfCtx, keyset map[[16]byte][]byte) (err error) { /* Only append a list key when it exists */ if key != "" { - keyset[md5.Sum([]byte(key))] = []byte(key) + keyset.Add(key) } /* List active members/collect keys */ - err = ListKeys(ctx, keyset, ml.GroupName, ml.ListName) + err = ml.ListKeys(ctx, keyset) if err != nil { return } @@ -402,7 +401,7 @@ func ml_member_list(ctx PfCtx, args []string) (err error) { } // ListKeys returns the keys for a mailinglist. -func ListKeys(ctx PfCtx, keyset map[[16]byte][]byte, gr_name string, ml_name string) (err error) { +func (ml *PfML) ListKeys(ctx PfCtx, keyset *pfpgp.IndexedKeySet) (err error) { q := "SELECT me.keyring " + "FROM member_email me, " + "member_mailinglist ml, " + @@ -415,7 +414,7 @@ func ListKeys(ctx PfCtx, keyset map[[16]byte][]byte, gr_name string, ml_name str " AND ml.trustgroup = $1 " + " AND ml.lhs = $2" - rows, err := DB.Query(q, gr_name, ml_name) + rows, err := DB.Query(q, ml.GroupName, ml.ListName) if err != nil { return } @@ -429,7 +428,8 @@ func ListKeys(ctx PfCtx, keyset map[[16]byte][]byte, gr_name string, ml_name str if err != nil { return } - keyset[md5.Sum([]byte(key))] = []byte(key) + + keyset.Add(key) } return } diff --git a/lib/pgp/keyset.go b/lib/pgp/keyset.go new file mode 100644 index 0000000..964c249 --- /dev/null +++ b/lib/pgp/keyset.go @@ -0,0 +1,49 @@ +package pfpgp + +import ( + "crypto/sha256" +) + +// The index is SHA256 hashed, thus the index is 16 bytes wide. +const IndexedKeySetHashSize = sha256.Size + +// IndexedKeySet is used to sort PGP keys and add them in a +// unique way to avoid keys to be listed multiple times. +// +// We SHA256 each key to only allow a key to be added once. +type IndexedKeySet struct { + keys map[[IndexedKeySetHashSize]byte][]byte +} + +// NewIndexedKeySet creates a new IndexedKeySet. +func NewIndexedKeySet() *IndexedKeySet { + return &IndexedKeySet{make(map[[IndexedKeySetHashSize]byte][]byte)} +} + +// Add can be used to add a key to the IndexedKeySet. +// +// The 'key' provided is a full ASCII formatted PGP PUBLIC KEY block. +// +// A SHA256 is used to hash the key to avoid duplicates of the exact same key. +func (keyset *IndexedKeySet) Add(key string) { + bkey := []byte(key) + + keyset.keys[sha256.Sum256(bkey)] = bkey +} + +// ToBytes converts the keyset into ASCII output. +// +// Unix-style newlines (LF-only) are added in between +// the keys to keep them separate in the output. +func (keyset *IndexedKeySet) ToBytes() (output []byte) { + // Add each key in the set + for k := range keyset.keys { + // Add the key + output = append(output, keyset.keys[k][:]...) + + // Add a \n (LF) + output = append(output, byte(0x0a)) + } + + return +} diff --git a/lib/setup.go b/lib/setup.go index 5b99584..0c9bbcb 100755 --- a/lib/setup.go +++ b/lib/setup.go @@ -49,10 +49,10 @@ func Setup(toolname string, confroot string, verbosedb bool, app_schema_version // Starts starts background services. func Starts() { /* Start IP Tracker -- against brute force login attempts */ - Iptrk_start(5, 10*time.Hour, "1 hour") + Iptrk_start(Config.IPTrkMax, 10*time.Hour, "1 hour") /* Start JWT Invalidation caching/clearing */ - JwtInv_start(30 * time.Minute) + JwtInv_start(time.Duration(Config.JWTTimeout) * time.Minute) } // Stops stops background services, should be matching and thus deferred after a Starts() call. diff --git a/lib/user.go b/lib/user.go index fb52b3d..5954313 100755 --- a/lib/user.go +++ b/lib/user.go @@ -9,6 +9,7 @@ import ( "time" "github.com/pborman/uuid" + pfpgp "trident.li/pitchfork/lib/pgp" ) // Standardized error messages @@ -61,7 +62,7 @@ type PfUser interface { SharedGroups(ctx PfCtx, otheruser PfUser) (ok bool, err error) GetImage(ctx PfCtx) (img []byte, err error) GetHideEmail() (hide_email bool) - GetKeys(ctx PfCtx, keyset map[[16]byte][]byte) (err error) + GetKeys(ctx PfCtx, keyset *pfpgp.IndexedKeySet) (err error) GetDetails() (details []PfUserDetail, err error) GetLanguages() (languages []PfUserLanguage, err error) Get(what string) (val string, err error) @@ -100,7 +101,7 @@ type PfUserS struct { Telephone string `label:"Telephone" pftype:"tel" pfset:"self" pfget:"user_view" pfcol:"tel_info" hint:"The phone number where to contact the user using voice messages"` Airport string `label:"Airport" min:"3" max:"3" pfset:"self" pfget:"user_view" hint:"Closest airport for this user"` Biography string `label:"Biography" pftype:"text" pfset:"self" pfget:"user_view" pfcol:"bio_info" hint:"Biography for this user"` - IsSysadmin bool `label:"System Administrator" pfset:"sysadmin" pfget:"group_admin" pfskipfailperm:"yes" pfcol:"sysadmin" hint:"Wether the user is a System Administrator"` + IsSysadmin bool `label:"System Administrator" pfset:"sysadmin" pfget:"group_admin" pfskipfailperm:"yes" pfcol:"sysadmin" hint:"Whether the user is a System Administrator"` CanBeSysadmin bool `label:"Can Be System Administrator" pfset:"nobody" pfget:"nobody" pfskipfailperm:"yes" pfcol:"sysadmin" hint:"If the user can toggle between Regular and SysAdmin usermode"` LoginAttempts int `label:"Number of failed Login Attempts" pfset:"self,group_admin" pfget:"group_admin" pfskipfailperm:"yes" pfcol:"login_attempts" hint:"How many failed login attempts have been registered"` No_email bool `label:"Email Disabled" pfset:"sysadmin" pfget:"self,group_admin" pfskipfailperm:"yes" hint:"Email address is disabled due to SMTP errors"` @@ -437,7 +438,7 @@ func (user *PfUserS) GetHideEmail() (hide_email bool) { return user.Hide_email } -func (user *PfUserS) GetKeys(ctx PfCtx, keyset map[[16]byte][]byte) (err error) { +func (user *PfUserS) GetKeys(ctx PfCtx, keyset *pfpgp.IndexedKeySet) (err error) { groups, err := user.GetGroups(ctx) if err != nil { return @@ -585,7 +586,7 @@ func (user *PfUserS) CheckAuth(ctx PfCtx, username string, password string, twof return } - if user.LoginAttempts > 5 { + if user.LoginAttempts > Config.LoginAttemptsMax { err = errors.New("Too many login attempts for this account") return } diff --git a/share/templates/misc/recover.tmpl b/share/templates/misc/recover.tmpl deleted file mode 100644 index 8c82f00..0000000 --- a/share/templates/misc/recover.tmpl +++ /dev/null @@ -1,7 +0,0 @@ -{{template "inc/header.tmpl" .}} - - {{ .Intro }} - - {{ pfform .UI .Recover . true }} - -{{template "inc/footer.tmpl" .}} diff --git a/share/templates/user/email/edit.tmpl b/share/templates/user/email/edit.tmpl index a37633d..4b903a2 100644 --- a/share/templates/user/email/edit.tmpl +++ b/share/templates/user/email/edit.tmpl @@ -82,7 +82,7 @@ Unverified - +
Key ID:{{ .Email.PgpKeyID }}
Expires: {{ fmt_time .Email.PgpKeyExpire }}
Download:{{ .Email.PgpKeyID }}.asc
Download:{{ .Email.PgpKeyID }}.asc
{{ else }} Not defined diff --git a/ui/cmd.go b/ui/cmd.go index 8d41522..1253996 100644 --- a/ui/cmd.go +++ b/ui/cmd.go @@ -17,7 +17,7 @@ func h_api(cui PfUI) { err = cui.Cmd(cui.GetPath()) if err != nil { - cui.OutLn("An error occured: %s", err.Error()) + cui.OutLn("An error occurred: %s", err.Error()) } } @@ -50,7 +50,7 @@ func h_cli(cui PfUI) { } if err != nil { - out += "An error occured: " + out += "An error occurred: " out += err.Error() + "\n" } diff --git a/ui/group.go b/ui/group.go index 7051ef0..2c72f8f 100755 --- a/ui/group.go +++ b/ui/group.go @@ -4,6 +4,7 @@ import ( "strconv" pf "trident.li/pitchfork/lib" + pfpgp "trident.li/pitchfork/lib/pgp" ) // h_group_add handles group additions @@ -280,8 +281,7 @@ func H_group_member_profile(cui PfUI) { // h_group_pgp_keys returns a file containing the group's PGP keys func h_group_pgp_keys(cui PfUI) { - var output []byte - keyset := make(map[[16]byte][]byte) + keyset := pfpgp.NewIndexedKeySet() grp := cui.SelectedGroup() err := grp.GetKeys(cui, keyset) @@ -291,13 +291,9 @@ func h_group_pgp_keys(cui PfUI) { return } + output := keyset.ToBytes() fname := grp.GetGroupName() + ".asc" - for k := range keyset { - output = append(output, keyset[k][:]...) - output = append(output, byte(0x0a)) - } - cui.SetContentType("application/pgp-keys") cui.SetFileName(fname) cui.SetExpires(60) diff --git a/ui/login.go b/ui/login.go index 09e86bb..c748af3 100755 --- a/ui/login.go +++ b/ui/login.go @@ -85,7 +85,7 @@ func h_loginui(cui PfUI, msg string, err error) { pp, err = cui.(*PfUIS).f_uiloginoverride(cui, &p) if err != nil { - cui.Errf("Overriden Login failed: %s", err.Error()) + cui.Errf("Overridden Login failed: %s", err.Error()) H_error(cui, StatusInternalServerError) } } else { diff --git a/ui/ml.go b/ui/ml.go index f0dd854..9775a2c 100755 --- a/ui/ml.go +++ b/ui/ml.go @@ -4,6 +4,7 @@ import ( "strconv" pf "trident.li/pitchfork/lib" + pfpgp "trident.li/pitchfork/lib/pgp" ) // h_ml_new allows creation of a new Mailinglist @@ -59,23 +60,18 @@ func h_ml_new(cui PfUI) { // h_ml_pgp returns the PGP key of a group func h_ml_pgp(cui PfUI) { - var output []byte - keyset := make(map[[16]byte][]byte) + keyset := pfpgp.NewIndexedKeySet() grp := cui.SelectedGroup() ml := cui.SelectedML() - err := ml.GetKey(cui, keyset) + err := ml.GetKeys(cui, keyset) if err != nil { H_error(cui, StatusNotFound) return } - for k := range keyset { - output = append(output, keyset[k][:]...) - output = append(output, byte(0x0a)) - } - + output := keyset.ToBytes() fname := grp.GetGroupName() + "-" + ml.ListName + ".asc" cui.SetContentType("application/pgp-keys") diff --git a/ui/ui.go b/ui/ui.go index 8d71e99..d165757 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -564,7 +564,7 @@ func (cui *PfUIS) NoSubs() bool { // so that it can be called by test functions. // // It returns the ip address as a net.IP and as a string -// along with an error if any occured, which should be rare. +// along with an error if any occurred, which should be rare. // // The incoming xff is sanitized to make it more standardized. // This as some separate by space, others by comma while the @@ -1556,11 +1556,11 @@ func (cui *PfUIS) HandleFormS(cmd string, autoop bool, args []string, obj interf if msg != "" { msg += ", " } - msg += strconv.Itoa(nomods) + " fields where not modified" + msg += strconv.Itoa(nomods) + " fields were not modified" } if msg == "" { - msg = "No fields where modified" + msg = "No fields were modified" } return diff --git a/ui/user.go b/ui/user.go index 2357284..5dfce49 100755 --- a/ui/user.go +++ b/ui/user.go @@ -6,6 +6,7 @@ import ( "trident.li/keyval" pf "trident.li/pitchfork/lib" + pfpgp "trident.li/pitchfork/lib/pgp" ) // h_user_username handles the username modification page @@ -212,25 +213,18 @@ func h_user_index(cui PfUI) { // h_user_pgp_keys returns the PGP keys of all the groups of a user func h_user_pgp_keys(cui PfUI) { - var err error - var output []byte - - keyset := make(map[[16]byte][]byte) + keyset := pfpgp.NewIndexedKeySet() user := cui.SelectedUser() - err = user.GetKeys(cui, keyset) + err := user.GetKeys(cui, keyset) if err != nil { /* Temp redirect to unknown */ H_NoAccess(cui) return } + output := keyset.ToBytes() fname := user.GetUserName() + ".asc" - for k := range keyset { - output = append(output, keyset[k][:]...) - output = append(output, byte(0x0a)) - } - cui.SetContentType("application/pgp-keys") cui.SetFileName(fname) cui.SetExpires(60)