diff options
Diffstat (limited to 'dht/Presence/UTmp.hs')
-rw-r--r-- | dht/Presence/UTmp.hs | 259 |
1 files changed, 259 insertions, 0 deletions
diff --git a/dht/Presence/UTmp.hs b/dht/Presence/UTmp.hs new file mode 100644 index 00000000..fcfe529a --- /dev/null +++ b/dht/Presence/UTmp.hs | |||
@@ -0,0 +1,259 @@ | |||
1 | {-# LANGUAGE TemplateHaskell #-} | ||
2 | {-# LANGUAGE RankNTypes #-} | ||
3 | module UTmp | ||
4 | ( users | ||
5 | , users2 | ||
6 | , utmp_file | ||
7 | , UserName | ||
8 | , Tty | ||
9 | , ProcessID | ||
10 | , UtmpRecord(..) | ||
11 | , UT_Type(..) | ||
12 | ) where | ||
13 | |||
14 | import qualified Data.ByteString as S | ||
15 | import qualified Data.ByteString.Char8 as C | ||
16 | import qualified Data.ByteString.Lazy.Char8 as L | ||
17 | import Data.BitSyntax | ||
18 | import Data.Functor.Identity | ||
19 | import Data.Maybe | ||
20 | import Data.String | ||
21 | import System.Posix.Process | ||
22 | import System.Posix.Signals | ||
23 | import System.Posix.Types | ||
24 | import System.Posix.User | ||
25 | import Control.Monad | ||
26 | import Data.Word | ||
27 | import Data.Int | ||
28 | import Control.Monad.Error.Class | ||
29 | import System.IO.Error | ||
30 | import qualified Paths | ||
31 | import Data.Text ( Text ) | ||
32 | import Unsafe.Coerce ( unsafeCoerce ) | ||
33 | import Network.Socket ( SockAddr(..) ) | ||
34 | import qualified Data.Text.Encoding as Text | ||
35 | import SockAddr () | ||
36 | |||
37 | |||
38 | utmp_file :: IsString s => s | ||
39 | utmp_file = fromString $ Paths.utmp -- "/var/run/utmp" | ||
40 | |||
41 | utmp_bs :: IO C.ByteString | ||
42 | utmp_bs = S.readFile utmp_file | ||
43 | |||
44 | decode_utmp_bytestring :: | ||
45 | C.ByteString | ||
46 | -> (Word32, | ||
47 | Word32, | ||
48 | C.ByteString, | ||
49 | C.ByteString, | ||
50 | C.ByteString, | ||
51 | C.ByteString, | ||
52 | Word16, | ||
53 | Word16, | ||
54 | Word32, | ||
55 | C.ByteString, | ||
56 | Word32, | ||
57 | Word32, | ||
58 | Word32, | ||
59 | Word32) | ||
60 | decode_utmp_bytestring = | ||
61 | runIdentity | ||
62 | . $(bitSyn [ UnsignedLE 4 -- type | ||
63 | , UnsignedLE 4 -- pid | ||
64 | , Fixed 32 -- tty | ||
65 | , Fixed 4 -- inittab id | ||
66 | , Fixed 32 -- username | ||
67 | , Fixed 256 -- remote host | ||
68 | , UnsignedLE 2 -- termination status | ||
69 | , UnsignedLE 2 -- exit status (int) | ||
70 | , UnsignedLE 4 -- session id (int) | ||
71 | , Fixed 8 -- time entry was made | ||
72 | , Unsigned 4 -- remote addr v6 addr[0] | ||
73 | , Unsigned 4 -- remote addr v6 addr[1] | ||
74 | , Unsigned 4 -- remote addr v6 addr[2] | ||
75 | , Unsigned 4 -- remote addr v6 addr[3] | ||
76 | , Skip 20 -- reserved | ||
77 | ]) | ||
78 | |||
79 | utmp_size :: Int | ||
80 | utmp_size = 384 -- 768 | ||
81 | |||
82 | |||
83 | utmp_records :: C.ByteString -> [C.ByteString] | ||
84 | utmp_records bs | S.length bs >= utmp_size | ||
85 | = u:utmp_records us | ||
86 | where | ||
87 | (u,us) = S.splitAt utmp_size bs | ||
88 | |||
89 | utmp_records bs = [bs] | ||
90 | |||
91 | utmp :: | ||
92 | IO | ||
93 | [(Word32, | ||
94 | Word32, | ||
95 | C.ByteString, | ||
96 | C.ByteString, | ||
97 | C.ByteString, | ||
98 | C.ByteString, | ||
99 | Word16, | ||
100 | Word16, | ||
101 | Word32, | ||
102 | C.ByteString, | ||
103 | Word32, | ||
104 | Word32, | ||
105 | Word32, | ||
106 | Word32)] | ||
107 | utmp = fmap (map decode_utmp_bytestring . utmp_records) utmp_bs | ||
108 | |||
109 | toStr :: C.ByteString -> [Char] | ||
110 | toStr = takeWhile (/='\0') . C.unpack | ||
111 | |||
112 | interp_utmp_record :: | ||
113 | forall t t1 t2 t3 t4 t5 t6 t7 t8 a. | ||
114 | Integral a => | ||
115 | (a, | ||
116 | Word32, | ||
117 | C.ByteString, | ||
118 | t, | ||
119 | C.ByteString, | ||
120 | C.ByteString, | ||
121 | t1, | ||
122 | t2, | ||
123 | t3, | ||
124 | t4, | ||
125 | t5, | ||
126 | t6, | ||
127 | t7, | ||
128 | t8) | ||
129 | -> (UT_Type, [Char], [Char], CPid, [Char]) | ||
130 | interp_utmp_record (typ,pid,tty,inittab,user,hostv4,term,exit,session,time | ||
131 | ,addr0,addr1,addr2,addr3) = | ||
132 | ( (toEnum . fromIntegral) typ :: UT_Type | ||
133 | , toStr user, toStr tty, processId pid, toStr hostv4 ) | ||
134 | where | ||
135 | processId = CPid . coerceToSigned | ||
136 | |||
137 | coerceToSigned :: Word32 -> Int32 | ||
138 | coerceToSigned = unsafeCoerce | ||
139 | |||
140 | |||
141 | data UT_Type | ||
142 | = EMPTY -- No valid user accounting information. */ | ||
143 | |||
144 | | RUN_LVL -- The system's runlevel. */ | ||
145 | | BOOT_TIME -- Time of system boot. */ | ||
146 | | NEW_TIME -- Time after system clock changed. */ | ||
147 | | OLD_TIME -- Time when system clock changed. */ | ||
148 | |||
149 | | INIT_PROCESS -- Process spawned by the init process. */ | ||
150 | | LOGIN_PROCESS -- Session leader of a logged in user. */ | ||
151 | | USER_PROCESS -- Normal process. */ | ||
152 | | DEAD_PROCESS -- Terminated process. */ | ||
153 | |||
154 | | ACCOUNTING | ||
155 | |||
156 | deriving (Enum,Show,Eq,Ord,Read) | ||
157 | |||
158 | processAlive :: ProcessID -> IO Bool | ||
159 | processAlive pid = do | ||
160 | catchError (do { signalProcess nullSignal pid ; return True }) | ||
161 | $ \e -> do { return (not ( isDoesNotExistError e)); } | ||
162 | |||
163 | type UserName = L.ByteString | ||
164 | type Tty = L.ByteString | ||
165 | |||
166 | users :: IO [(UserName, Tty, ProcessID)] | ||
167 | users = utmp_users `catchIOError` \_ -> do | ||
168 | -- If we can't read utmp file, then return a list with only the current | ||
169 | -- user. | ||
170 | uname <- getLoginName | ||
171 | pid <- getProcessID -- TODO: XXX: Does this make sense as a fallback? | ||
172 | return [(L.pack uname,L.empty,pid)] | ||
173 | where | ||
174 | utmp_users = fmap (map only3) $ do | ||
175 | us <- utmp | ||
176 | let us' = map interp_utmp_record us | ||
177 | us'' = mapMaybe user_proc us' | ||
178 | user_proc (USER_PROCESS, u,tty,pid, hostv4) | ||
179 | = Just (L.pack u,L.pack tty,pid,hostv4) | ||
180 | user_proc _ = Nothing | ||
181 | onThrd f (_,_,pid,_) = f pid | ||
182 | us3 <- filterM (onThrd processAlive) us'' | ||
183 | return us3 | ||
184 | |||
185 | only3 :: forall t t1 t2 t3. (t1, t2, t3, t) -> (t1, t2, t3) | ||
186 | only3 (a,b,c,_) = (a,b,c) | ||
187 | |||
188 | data UtmpRecord = UtmpRecord | ||
189 | { utmpType :: UT_Type | ||
190 | , utmpUser :: Text | ||
191 | , utmpTty :: Text | ||
192 | , utmpPid :: CPid | ||
193 | , utmpHost :: Text | ||
194 | , utmpSession :: Int32 | ||
195 | , utmpRemoteAddr :: Maybe SockAddr | ||
196 | } | ||
197 | deriving ( Show, Eq, Ord ) | ||
198 | |||
199 | toText :: C.ByteString -> Text | ||
200 | toText bs = Text.decodeUtf8 $ C.takeWhile (/='\0') bs | ||
201 | |||
202 | interp_utmp_record2 :: | ||
203 | forall t t1 t2 t3 a. | ||
204 | Integral a => | ||
205 | (a, | ||
206 | Word32, | ||
207 | C.ByteString, | ||
208 | t, | ||
209 | C.ByteString, | ||
210 | C.ByteString, | ||
211 | t1, | ||
212 | t2, | ||
213 | Word32, | ||
214 | t3, | ||
215 | Word32, | ||
216 | Word32, | ||
217 | Word32, | ||
218 | Word32) | ||
219 | -> UtmpRecord | ||
220 | interp_utmp_record2 (typ,pid,tty,inittab,user,hostv4 | ||
221 | ,term,exit,session,time,addr0,addr1,addr2,addr3) = | ||
222 | UtmpRecord | ||
223 | { utmpType = toEnum (fromIntegral typ) :: UT_Type | ||
224 | , utmpUser = toText user | ||
225 | , utmpTty = toText tty | ||
226 | , utmpPid = processId pid | ||
227 | , utmpHost = toText hostv4 | ||
228 | , utmpSession = coerceToSigned session | ||
229 | , utmpRemoteAddr = | ||
230 | if all (==0) [addr1,addr2,addr3] | ||
231 | then do guard (addr0/=0) | ||
232 | Just $ SockAddrInet6 0 0 (0,0,0xFFFF,addr0) 0 | ||
233 | else Just $ SockAddrInet6 0 0 (addr0,addr1,addr2,addr3) 0 | ||
234 | } | ||
235 | where | ||
236 | processId = CPid . coerceToSigned | ||
237 | |||
238 | users2 :: IO [UtmpRecord] | ||
239 | users2 = do | ||
240 | us <- utmp | ||
241 | let us' = map interp_utmp_record2 us | ||
242 | us3 <- filterM (processAlive . utmpPid) us' | ||
243 | return us3 | ||
244 | |||
245 | {- | ||
246 | - This is how the w command reports idle time: | ||
247 | /* stat the device file to get an idle time */ | ||
248 | static time_t idletime(const char *restrict const tty) | ||
249 | { | ||
250 | struct stat sbuf; | ||
251 | if (stat(tty, &sbuf) != 0) | ||
252 | return 0; | ||
253 | return time(NULL) - sbuf.st_atime; | ||
254 | } | ||
255 | - THis might be useful fo rimplementing | ||
256 | - xep-0012 Last Activity | ||
257 | - iq get {jabber:iq:last}query | ||
258 | - | ||
259 | -} | ||