/
main.go
155 lines (135 loc) · 3.77 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// +build linux,amd64
package main
/*
#include <shadow.h>
#include <stddef.h>
#include <stdlib.h>
*/
import "C"
import (
"bufio"
"fmt"
"io"
"math"
"os"
"strings"
"time"
"unsafe"
)
const dateFormat = "Jan 02, 2006"
type ExpirationInfo struct {
PasswordLastChanged time.Time
PasswordInactive time.Time
PasswordExpires time.Time
AccountExpired time.Time
Min int
Max int
Warning int
Expirable bool
}
type ExpirableLogin struct {
Login string
Expiration *ExpirationInfo
}
func (e *ExpirableLogin) ShouldWarnNow() bool {
if e.Expiration.Max == 99999 {
return false
}
// how many days since last password changed
elapsed := time.Since(e.Expiration.PasswordLastChanged)
days := int(math.Ceil((elapsed.Seconds() / 86400)))
// if warning days is satisfied but min value not reached yet
// in effect the user got warning but not really able to change password
// so we need to ensure both warning days satiesfied and reach the min days
if days <= e.Expiration.Warning && days >= e.Expiration.Min {
return true
}
return false
}
func daysDuration(d int) time.Duration {
return time.Duration(d) * time.Duration(24*time.Hour)
}
func neverMarker() time.Time {
return time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local)
}
func timeFromEpoch(v int) time.Time {
if v == -1 {
return neverMarker()
}
dt := time.Date(1970, 1, 1, 0, 0, 0, 0, time.Local)
dt = dt.Add(daysDuration(v))
return dt
}
func getLoginExpiration(login string) *ExpirationInfo {
cs := C.CString(login)
defer C.free(unsafe.Pointer(cs))
sp := C.getspnam(cs)
if unsafe.Pointer(sp) == nil {
return nil
}
exp := &ExpirationInfo{
PasswordLastChanged: timeFromEpoch(int(sp.sp_lstchg)),
PasswordInactive: timeFromEpoch(int(sp.sp_inact)),
AccountExpired: timeFromEpoch(int(sp.sp_expire)),
Warning: int(sp.sp_warn),
Max: int(sp.sp_max),
Min: int(sp.sp_min),
Expirable: int(sp.sp_expire) > -1,
}
if exp.Max == 99999 {
exp.PasswordExpires = neverMarker()
exp.PasswordInactive = neverMarker()
} else {
// last changed + max ( in days )
exp.PasswordExpires = exp.PasswordLastChanged.Add(daysDuration(exp.Max))
}
return exp
}
func listExpirableUsers() []*ExpirableLogin {
f, err := os.Open("/etc/shadow")
if err != nil {
return nil
}
users := []*ExpirableLogin{}
reader := bufio.NewReader(f)
for {
line, err := reader.ReadString('\n')
if err != nil && err == io.EOF {
break
}
line = strings.TrimSpace(line)
parts := strings.SplitN(line, ":", 2)
expInfo := getLoginExpiration(parts[0])
if expInfo != nil {
expLogin := &ExpirableLogin{parts[0], expInfo}
if expInfo.Expirable {
users = append(users, expLogin)
}
}
}
return users
}
func main() {
// TODO: I have no idea the correct solution
// List only the users that will get the warning during login *AND* they could
// do the password change.
// Because if we set the "warning" earlier than "min", the user will get the
// warning but not really able to change the password
for _, u := range listExpirableUsers() {
passwordExpires := u.Expiration.PasswordExpires.Format(dateFormat)
if u.Expiration.PasswordExpires == neverMarker() {
passwordExpires = "Never"
}
lastPasswordChange := u.Expiration.PasswordLastChanged.Format(dateFormat)
if u.Expiration.PasswordLastChanged == neverMarker() {
lastPasswordChange = "Never"
}
fmt.Printf("Login: %s\n", u.Login)
fmt.Printf("Last password change: %s\n", lastPasswordChange)
fmt.Printf("Password expires: %s\n", passwordExpires)
fmt.Printf("Warning: %d\n", u.Expiration.Warning)
fmt.Printf("Min password change allowed: %d\n", u.Expiration.Min)
fmt.Println("Is it effective to notify now? ", u.ShouldWarnNow())
fmt.Println()
}
}