97a1ea659b63413ac748f546dbb4a73759e865a1
[debian/sudo] / src / utmp.c
1 /*
2  * Copyright (c) 2011 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #include <config.h>
18
19 #include <sys/types.h>
20 #include <sys/param.h>
21 #include <sys/time.h>
22 #include <sys/wait.h>
23 #include <stdio.h>
24 #ifdef STDC_HEADERS
25 # include <stdlib.h>
26 # include <stddef.h>
27 #else
28 # ifdef HAVE_STDLIB_H
29 #  include <stdlib.h>
30 # endif
31 #endif /* STDC_HEADERS */
32 #ifdef HAVE_STRING_H
33 # if defined(HAVE_MEMORY_H) && !defined(STDC_HEADERS)
34 #  include <memory.h>
35 # endif
36 # include <string.h>
37 #endif /* HAVE_STRING_H */
38 #ifdef HAVE_STRINGS_H
39 # include <strings.h>
40 #endif /* HAVE_STRINGS_H */
41 #ifdef HAVE_UNISTD_H
42 # include <unistd.h>
43 #endif /* HAVE_UNISTD_H */
44 #if TIME_WITH_SYS_TIME
45 # include <time.h>
46 #endif
47 #ifdef HAVE_UTMPX_H
48 # include <utmpx.h>
49 #else
50 # include <utmp.h>
51 #endif /* HAVE_UTMPX_H */
52 #ifdef HAVE_GETTTYENT
53 # include <ttyent.h>
54 #endif
55 #include <fcntl.h>
56 #include <signal.h>
57
58 #include "sudo.h"
59 #include "sudo_exec.h"
60
61 /*
62  * Simplify handling of utmp vs. utmpx
63  */
64 #if !defined(HAVE_GETUTXID) && defined(HAVE_GETUTID)
65 # define getutxline(u)  getutline(u)
66 # define pututxline(u)  pututline(u)
67 # define setutxent()    setutent()
68 # define endutxent()    endutent()
69 #endif /* !HAVE_GETUTXID && HAVE_GETUTID */
70
71 #ifdef HAVE_GETUTXID
72 typedef struct utmpx sudo_utmp_t;
73 #else
74 typedef struct utmp sudo_utmp_t;
75 /* Older systems have ut_name, not us_user */
76 # if !defined(HAVE_STRUCT_UTMP_UT_USER) && !defined(ut_user)
77 #  define ut_user ut_name
78 # endif
79 #endif
80
81 /* HP-UX has __e_termination and __e_exit, others lack the __ */
82 #if defined(HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION) || defined(HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION)
83 # undef  __e_termination
84 # define __e_termination        e_termination
85 # undef  __e_exit
86 # define __e_exit               e_exit
87 #endif
88
89 #if defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
90 /*
91  * Create ut_id from the new ut_line and the old ut_id.
92  */
93 static void
94 utmp_setid(sudo_utmp_t *old, sudo_utmp_t *new)
95 {
96     const char *line = new->ut_line;
97     size_t idlen;
98     debug_decl(utmp_setid, SUDO_DEBUG_UTMP)
99
100     /* Skip over "tty" in the id if old entry did too. */
101     if (old != NULL) {
102         if (strncmp(line, "tty", 3) == 0) {
103             idlen = MIN(sizeof(old->ut_id), 3);
104             if (strncmp(old->ut_id, "tty", idlen) != 0)
105                 line += 3;
106         }
107     }
108     
109     /* Store as much as will fit, skipping parts of the beginning as needed. */
110     idlen = strlen(line);
111     if (idlen > sizeof(new->ut_id)) {
112         line += idlen - sizeof(new->ut_id);
113         idlen = sizeof(new->ut_id);
114     }
115     strncpy(new->ut_id, line, idlen);
116
117     debug_return;
118 }
119 #endif /* HAVE_GETUTXID || HAVE_GETUTID */
120
121 /*
122  * Store time in utmp structure.
123  */
124 static void
125 utmp_settime(sudo_utmp_t *ut)
126 {
127     struct timeval tv;
128     debug_decl(utmp_settime, SUDO_DEBUG_UTMP)
129
130     gettimeofday(&tv, NULL);
131
132 #if defined(HAVE_STRUCT_UTMP_UT_TV) || defined(HAVE_STRUCT_UTMPX_UT_TV)
133     ut->ut_tv.tv_sec = tv.tv_sec;
134     ut->ut_tv.tv_usec = tv.tv_usec;
135 #else
136     ut->ut_time = tv.tv_sec;
137 #endif
138
139     debug_return;
140 }
141
142 /*
143  * Fill in a utmp entry, using an old entry as a template if there is one.
144  */
145 static void
146 utmp_fill(const char *line, const char *user, sudo_utmp_t *ut_old,
147     sudo_utmp_t *ut_new)
148 {
149     debug_decl(utmp_file, SUDO_DEBUG_UTMP)
150
151     if (ut_old == NULL) {
152         memset(ut_new, 0, sizeof(*ut_new));
153         if (user == NULL) {
154             strncpy(ut_new->ut_user, user_details.username,
155                 sizeof(ut_new->ut_user));
156         }
157     } else if (ut_old != ut_new) {
158         memcpy(ut_new, ut_old, sizeof(*ut_new));
159     }
160     if (user != NULL)
161         strncpy(ut_new->ut_user, user, sizeof(ut_new->ut_user));
162     strncpy(ut_new->ut_line, line, sizeof(ut_new->ut_line));
163 #if defined(HAVE_STRUCT_UTMPX_UT_ID) || defined(HAVE_STRUCT_UTMP_UT_ID)
164     utmp_setid(ut_old, ut_new);
165 #endif
166 #if defined(HAVE_STRUCT_UTMPX_UT_PID) || defined(HAVE_STRUCT_UTMP_UT_PID)
167     ut_new->ut_pid = getpid();
168 #endif
169     utmp_settime(ut_new);
170 #if defined(HAVE_STRUCT_UTMPX_UT_TYPE) || defined(HAVE_STRUCT_UTMP_UT_TYPE)
171     ut_new->ut_type = USER_PROCESS;
172 #endif
173     debug_return;
174 }
175
176 /*
177  * There are two basic utmp file types:
178  *
179  *  POSIX:  sequential access with new entries appended to the end.
180  *          Manipulated via {get,put}utent()/{get,put}getutxent().
181  *
182  *  Legacy: sparse file indexed by ttyslot() * sizeof(struct utmp)
183  */
184 #if defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
185 bool
186 utmp_login(const char *from_line, const char *to_line, int ttyfd,
187     const char *user)
188 {
189     sudo_utmp_t utbuf, *ut_old = NULL;
190     bool rval = false;
191     debug_decl(utmp_login, SUDO_DEBUG_UTMP)
192
193     /* Strip off /dev/ prefix from line as needed. */
194     if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
195         to_line += sizeof(_PATH_DEV) - 1;
196     setutxent();
197     if (from_line != NULL) {
198         if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
199             from_line += sizeof(_PATH_DEV) - 1;
200
201         /* Lookup old line. */
202         memset(&utbuf, 0, sizeof(utbuf));
203         strncpy(utbuf.ut_line, from_line, sizeof(utbuf.ut_line));
204         ut_old = getutxline(&utbuf);
205     }
206     utmp_fill(to_line, user, ut_old, &utbuf);
207     if (pututxline(&utbuf) != NULL)
208         rval = true;
209     endutxent();
210
211     debug_return_bool(rval);
212 }
213
214 bool
215 utmp_logout(const char *line, int status)
216 {
217     bool rval = false;
218     sudo_utmp_t *ut, utbuf;
219     debug_decl(utmp_logout, SUDO_DEBUG_UTMP)
220
221     /* Strip off /dev/ prefix from line as needed. */
222     if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
223         line += sizeof(_PATH_DEV) - 1;
224    
225     memset(&utbuf, 0, sizeof(utbuf));
226     strncpy(utbuf.ut_line, line, sizeof(utbuf.ut_line));
227     if ((ut = getutxline(&utbuf)) != NULL) {
228         memset(ut->ut_user, 0, sizeof(ut->ut_user));
229 # if defined(HAVE_STRUCT_UTMPX_UT_TYPE) || defined(HAVE_STRUCT_UTMP_UT_TYPE)
230         ut->ut_type = DEAD_PROCESS;
231 # endif
232 # if defined(HAVE_STRUCT_UTMPX_UT_EXIT) || defined(HAVE_STRUCT_UTMP_UT_EXIT)
233         ut->ut_exit.__e_exit = WEXITSTATUS(status);
234         ut->ut_exit.__e_termination = WIFEXITED(status) ? WEXITSTATUS(status) : 0;
235 # endif
236         utmp_settime(ut);
237         if (pututxline(ut) != NULL)
238             rval = true;
239     }
240     debug_return_bool(rval);
241 }
242
243 #else /* !HAVE_GETUTXID && !HAVE_GETUTID */
244
245 /*
246  * Find the slot for the specified line (tty name and file descriptor).
247  * Returns a slot suitable for seeking into utmp on success or <= 0 on error.
248  * If getttyent() is available we can use that to compute the slot.
249  */
250 # ifdef HAVE_GETTTYENT
251 static int
252 utmp_slot(const char *line, int ttyfd)
253 {
254     int slot = 1;
255     struct ttyent *tty;
256     debug_decl(utmp_slot, SUDO_DEBUG_UTMP)
257
258     setttyent();
259     while ((tty = getttyent()) != NULL) {
260         if (strcmp(line, tty->ty_name) == 0)
261             break;
262         slot++;
263     }
264     endttyent();
265     debug_return_int(tty ? slot : 0);
266 }
267 # else
268 static int
269 utmp_slot(const char *line, int ttyfd)
270 {
271     int sfd, slot;
272     debug_decl(utmp_slot, SUDO_DEBUG_UTMP)
273
274     /*
275      * Temporarily point stdin to the tty since ttyslot()
276      * doesn't take an argument.
277      */
278     if ((sfd = dup(STDIN_FILENO)) == -1)
279         error(1, _("unable to save stdin"));
280     if (dup2(ttyfd, STDIN_FILENO) == -1)
281         error(1, _("unable to dup2 stdin"));
282     slot = ttyslot();
283     if (dup2(sfd, STDIN_FILENO) == -1)
284         error(1, _("unable to restore stdin"));
285     close(sfd);
286
287     debug_return_int(slot);
288 }
289 # endif /* HAVE_GETTTYENT */
290
291 bool
292 utmp_login(const char *from_line, const char *to_line, int ttyfd,
293     const char *user)
294 {
295     sudo_utmp_t utbuf, *ut_old = NULL;
296     bool rval = false;
297     int slot;
298     FILE *fp;
299     debug_decl(utmp_login, SUDO_DEBUG_UTMP)
300
301     /* Strip off /dev/ prefix from line as needed. */
302     if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
303         to_line += sizeof(_PATH_DEV) - 1;
304
305     /* Find slot for new entry. */
306     slot = utmp_slot(to_line, ttyfd);
307     if (slot <= 0)
308         goto done;
309
310     if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
311         goto done;
312
313     if (from_line != NULL) {
314         if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
315             from_line += sizeof(_PATH_DEV) - 1;
316
317         /* Lookup old line. */
318         while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
319 # ifdef HAVE_STRUCT_UTMP_UT_ID
320             if (utbuf.ut_type != LOGIN_PROCESS && utbuf.ut_type != USER_PROCESS)
321                 continue;
322 # endif
323             if (utbuf.ut_user[0] &&
324                 !strncmp(utbuf.ut_line, from_line, sizeof(utbuf.ut_line))) {
325                 ut_old = &utbuf;
326                 break;
327             }
328         }
329     }
330     utmp_fill(to_line, user, ut_old, &utbuf);
331 #ifdef HAVE_FSEEKO
332     if (fseeko(fp, slot * (off_t)sizeof(utbuf), SEEK_SET) == 0) {
333 #else
334     if (fseek(fp, slot * (long)sizeof(utbuf), SEEK_SET) == 0) {
335 #endif
336         if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
337             rval = true;
338     }
339     fclose(fp);
340
341 done:
342     debug_return_bool(rval);
343 }
344
345 bool
346 utmp_logout(const char *line, int status)
347 {
348     sudo_utmp_t utbuf;
349     bool rval = false;
350     FILE *fp;
351     debug_decl(utmp_logout, SUDO_DEBUG_UTMP)
352
353     if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
354         debug_return_int(rval);
355
356     /* Strip off /dev/ prefix from line as needed. */
357     if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
358         line += sizeof(_PATH_DEV) - 1;
359    
360     while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
361         if (!strncmp(utbuf.ut_line, line, sizeof(utbuf.ut_line))) {
362             memset(utbuf.ut_user, 0, sizeof(utbuf.ut_user));
363 # if defined(HAVE_STRUCT_UTMP_UT_TYPE)
364             utbuf.ut_type = DEAD_PROCESS;
365 # endif
366             utmp_settime(&utbuf);
367             /* Back up and overwrite record. */
368 #ifdef HAVE_FSEEKO
369             if (fseeko(fp, (off_t)0 - (off_t)sizeof(utbuf), SEEK_CUR) == 0) {
370 #else
371             if (fseek(fp, 0L - (long)sizeof(utbuf), SEEK_CUR) == 0) {
372 #endif
373                 if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
374                     rval = true;
375             }
376             break;
377         }
378     }
379     fclose(fp);
380
381     debug_return_bool(rval);
382 }
383 #endif /* HAVE_GETUTXID || HAVE_GETUTID */