1// Copyright 2010 Jonas mg 2// 3// This Source Code Form is subject to the terms of the Mozilla Public 4// License, v. 2.0. If a copy of the MPL was not distributed with this 5// file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7package user 8 9import ( 10 "fmt" 11 "os" 12 "reflect" 13 "strings" 14) 15 16type gshadowField int 17 18// Field names for shadowed group database. 19const ( 20 GS_NAME gshadowField = 1 << iota 21 GS_PASSWD 22 GS_ADMIN 23 GS_MEMBER 24 25 GS_ALL 26) 27 28func (f gshadowField) String() string { 29 switch f { 30 case GS_NAME: 31 return "Name" 32 case GS_PASSWD: 33 return "Passwd" 34 case GS_ADMIN: 35 return "Admin" 36 case GS_MEMBER: 37 return "Member" 38 } 39 return "ALL" 40} 41 42// A GShadow represents the format of the shadowed information for a group account. 43type GShadow struct { 44 // Group name. (Unique) 45 // 46 // It must be a valid group name, which exist on the system. 47 Name string 48 49 // Hashed password 50 // 51 // If the password field contains some string that is not a valid result of 52 // crypt, for instance "!" or "*", users will not be able to use a unix 53 // password to access the group (but group members do not need the password). 54 // 55 // The password is used when an user who is not a member of the group wants 56 // to gain the permissions of this group (see "newgrp(1)"). 57 // 58 // This field may be empty, in which case only the group members can gain 59 // the group permissions. 60 // 61 // A password field which starts with a exclamation mark means that the 62 // password is locked. The remaining characters on the line represent the 63 // password field before the password was locked. 64 // 65 // This password supersedes any password specified in '/etc/group'. 66 password string 67 68 // Group administrator list 69 // 70 // It must be a comma-separated list of user names. 71 // 72 // Administrators can change the password or the members of the group. 73 // Administrators also have the same permissions as the members (see below). 74 AdminList []string 75 76 // Group member list 77 // 78 // It must be a comma-separated list of user names. 79 // 80 // Members can access the group without being prompted for a password. 81 // You should use the same list of users as in /etc/group. 82 UserList []string 83} 84 85// NewGShadow returns a new GShadow. 86func NewGShadow(username string, members ...string) *GShadow { 87 return &GShadow{ 88 Name: username, 89 UserList: members, 90 } 91} 92 93func (gs *GShadow) filename() string { return _GSHADOW_FILE } 94 95func (gs *GShadow) String() string { 96 return fmt.Sprintf("%s:%s:%s:%s\n", 97 gs.Name, gs.password, strings.Join(gs.AdminList, ","), strings.Join(gs.UserList, ",")) 98} 99 100// parseGShadow parses the row of a group shadow. 101func parseGShadow(row string) (*GShadow, error) { 102 fields := strings.Split(row, ":") 103 if len(fields) != 4 { 104 return nil, rowError{_GSHADOW_FILE, row} 105 } 106 107 return &GShadow{ 108 fields[0], 109 fields[1], 110 strings.Split(fields[2], ","), 111 strings.Split(fields[3], ","), 112 }, nil 113} 114 115// == Lookup 116// 117 118// lookUp parses the shadowed group line searching a value into the field. 119// Returns nil if it isn't found. 120func (*GShadow) lookUp(line string, f field, value interface{}) interface{} { 121 _field := f.(gshadowField) 122 _value := value.(string) 123 allField := strings.Split(line, ":") 124 arrayField := make(map[int][]string) 125 126 arrayField[2] = strings.Split(allField[2], ",") 127 arrayField[3] = strings.Split(allField[3], ",") 128 129 // Check fields 130 var isField bool 131 if GS_NAME&_field != 0 && allField[0] == _value { 132 isField = true 133 } else if GS_PASSWD&_field != 0 && allField[1] == _value { 134 isField = true 135 } else if GS_ADMIN&_field != 0 && checkGroup(arrayField[2], _value) { 136 isField = true 137 } else if GS_MEMBER&_field != 0 && checkGroup(arrayField[3], _value) { 138 isField = true 139 } else if GS_ALL&_field != 0 { 140 isField = true 141 } 142 143 if isField { 144 return &GShadow{ 145 allField[0], 146 allField[1], 147 arrayField[2], 148 arrayField[3], 149 } 150 } 151 return nil 152} 153 154// LookupGShadow looks up a shadowed group by name. 155func LookupGShadow(name string) (*GShadow, error) { 156 entries, err := LookupInGShadow(GS_NAME, name, 1) 157 if err != nil { 158 return nil, err 159 } 160 161 return entries[0], err 162} 163 164// LookupInGShadow looks up a shadowed group by the given values. 165// 166// The count determines the number of fields to return: 167// n > 0: at most n fields 168// n == 0: the result is nil (zero fields) 169// n < 0: all fields 170func LookupInGShadow(field gshadowField, value string, n int) ([]*GShadow, error) { 171 checkRoot() 172 173 iEntries, err := lookUp(&GShadow{}, field, value, n) 174 if err != nil { 175 return nil, err 176 } 177 178 // == Convert to type GShadow 179 valueSlice := reflect.ValueOf(iEntries) 180 entries := make([]*GShadow, valueSlice.Len()) 181 182 for i := 0; i < valueSlice.Len(); i++ { 183 entries[i] = valueSlice.Index(i).Interface().(*GShadow) 184 } 185 186 return entries, err 187} 188 189// == Editing 190// 191 192// Add adds a new shadowed group. 193// If the key is not nil, generates a hashed password. 194// 195// It is created a backup before of modify the original file. 196func (gs *GShadow) Add(key []byte) (err error) { 197 loadConfig() 198 199 gshadow, err := LookupGShadow(gs.Name) 200 if err != nil { 201 if _, ok := err.(NoFoundError); !ok { 202 return 203 } 204 } 205 if gshadow != nil { 206 return ErrGroupExist 207 } 208 209 if gs.Name == "" { 210 return RequiredError("Name") 211 } 212 213 // Backup 214 if err = backup(_GSHADOW_FILE); err != nil { 215 return 216 } 217 218 db, err := openDBFile(_GSHADOW_FILE, os.O_WRONLY|os.O_APPEND) 219 if err != nil { 220 return 221 } 222 defer func() { 223 e := db.close() 224 if e != nil && err == nil { 225 err = e 226 } 227 }() 228 229 if key != nil { 230 gs.password, _ = config.crypter.Generate(key, nil) 231 } else { 232 gs.password = "*" // Password disabled. 233 } 234 235 _, err = db.file.WriteString(gs.String()) 236 return 237} 238