Merge commit 'upstream/1.8.1p2'
[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
57 #include "sudo.h"
58 #include "sudo_exec.h"
59
60 /*
61  * Simplify handling of utmp vs. utmpx
62  */
63 #if !defined(HAVE_GETUTXID) && defined(HAVE_GETUTID)
64 # define getutxline(u)  getutline(u)
65 # define pututxline(u)  pututline(u)
66 # define setutxent      setutent(u)
67 # define endutxent      endutent(u)
68 #endif /* !HAVE_GETUTXID && HAVE_GETUTID */
69
70 #ifdef HAVE_GETUTXID
71 typedef struct utmpx sudo_utmp_t;
72 #else
73 typedef struct utmp sudo_utmp_t;
74 /* Older systems have ut_name, not us_user */
75 # if !defined(HAVE_STRUCT_UTMP_UT_USER) && !defined(ut_user)
76 #  define ut_user ut_name
77 # endif
78 #endif
79
80 /* HP-UX has __e_termination and __e_exit, others lack the __ */
81 #if defined(HAVE_STRUCT_UTMPX_UT_EXIT_E_TERMINATION) || defined(HAVE_STRUCT_UTMP_UT_EXIT_E_TERMINATION)
82 # undef  __e_termination
83 # define __e_termination        e_termination
84 # undef  __e_exit
85 # define __e_exit               e_exit
86 #endif
87
88 #if defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
89 /*
90  * Create ut_id from the new ut_line and the old ut_id.
91  */
92 static void
93 utmp_setid(sudo_utmp_t *old, sudo_utmp_t *new)
94 {
95     const char *line = new->ut_line;
96     size_t idlen;
97
98     /* Skip over "tty" in the id if old entry did too. */
99     if (strncmp(line, "tty", 3) == 0) {
100         idlen = MIN(sizeof(old->ut_id), 3);
101         if (strncmp(old->ut_id, "tty", idlen) != 0)
102             line += 3;
103     }
104     
105     /* Store as much as will fit, skipping parts of the beginning as needed. */
106     idlen = strlen(line);
107     if (idlen > sizeof(new->ut_id)) {
108         line += idlen - sizeof(new->ut_id);
109         idlen = sizeof(new->ut_id);
110     }
111     strncpy(new->ut_id, line, idlen);
112 }
113 #endif /* HAVE_GETUTXID || HAVE_GETUTID */
114
115 /*
116  * Store time in utmp structure.
117  */
118 static void
119 utmp_settime(sudo_utmp_t *ut)
120 {
121     struct timeval tv;
122
123     gettimeofday(&tv, NULL);
124
125 #if defined(HAVE_STRUCT_UTMP_UT_TV) || defined(HAVE_STRUCT_UTMPX_UT_TV)
126     ut->ut_tv.tv_sec = tv.tv_sec;
127     ut->ut_tv.tv_usec = tv.tv_usec;
128 #else
129     ut->ut_time = tv.tv_sec;
130 #endif
131 }
132
133 /*
134  * Fill in a utmp entry, using an old entry as a template if there is one.
135  */
136 static void
137 utmp_fill(const char *line, const char *user, sudo_utmp_t *ut_old,
138     sudo_utmp_t *ut_new)
139 {
140     if (ut_old == NULL) {
141         memset(ut_new, 0, sizeof(*ut_new));
142         if (user == NULL) {
143             strncpy(ut_new->ut_user, user_details.username,
144                 sizeof(ut_new->ut_user));
145         }
146     } else if (ut_old != ut_new) {
147         memcpy(ut_new, ut_old, sizeof(*ut_new));
148     }
149     if (user != NULL)
150         strncpy(ut_new->ut_user, user, sizeof(ut_new->ut_user));
151     strncpy(ut_new->ut_line, line, sizeof(ut_new->ut_line));
152 #if defined(HAVE_STRUCT_UTMPX_UT_ID) || defined(HAVE_STRUCT_UTMP_UT_ID)
153     utmp_setid(ut_old, ut_new);
154 #endif
155 #if defined(HAVE_STRUCT_UTMPX_UT_PID) || defined(HAVE_STRUCT_UTMP_UT_PID)
156     ut_new->ut_pid = getpid();
157 #endif
158     utmp_settime(ut_new);
159 #if defined(HAVE_STRUCT_UTMPX_UT_TYPE) || defined(HAVE_STRUCT_UTMP_UT_TYPE)
160     ut_new->ut_type = USER_PROCESS;
161 #endif
162 }
163
164 /*
165  * There are two basic utmp file types:
166  *
167  *  POSIX:  sequential access with new entries appended to the end.
168  *          Manipulated via {get,put}utent()/{get,put}getutxent().
169  *
170  *  Legacy: sparse file indexed by ttyslot() * sizeof(struct utmp)
171  */
172 #if defined(HAVE_GETUTXID) || defined(HAVE_GETUTID)
173 int
174 utmp_login(const char *from_line, const char *to_line, int ttyfd,
175     const char *user)
176 {
177     sudo_utmp_t utbuf, *ut_old = NULL;
178     int rval = FALSE;
179
180     /* Strip off /dev/ prefix from line as needed. */
181     if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
182         to_line += sizeof(_PATH_DEV) - 1;
183     setutxent();
184     if (from_line != NULL) {
185         if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
186             from_line += sizeof(_PATH_DEV) - 1;
187
188         /* Lookup old line. */
189         memset(&utbuf, 0, sizeof(utbuf));
190         strncpy(utbuf.ut_line, from_line, sizeof(utbuf.ut_line));
191         ut_old = getutxline(&utbuf);
192     }
193     utmp_fill(to_line, user, ut_old, &utbuf);
194     if (pututxline(&utbuf) != NULL)
195         rval = TRUE;
196     endutxent();
197
198     return rval;
199 }
200
201 int
202 utmp_logout(const char *line, int status)
203 {
204     int rval = FALSE;
205     sudo_utmp_t *ut, utbuf;
206
207     /* Strip off /dev/ prefix from line as needed. */
208     if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
209         line += sizeof(_PATH_DEV) - 1;
210    
211     memset(&utbuf, 0, sizeof(utbuf));
212     strncpy(utbuf.ut_line, line, sizeof(utbuf.ut_line));
213     if ((ut = getutxline(&utbuf)) != NULL) {
214         memset(ut->ut_user, 0, sizeof(ut->ut_user));
215 # if defined(HAVE_STRUCT_UTMPX_UT_TYPE) || defined(HAVE_STRUCT_UTMP_UT_TYPE)
216         ut->ut_type = DEAD_PROCESS;
217 # endif
218 # if defined(HAVE_STRUCT_UTMPX_UT_EXIT) || defined(HAVE_STRUCT_UTMP_UT_EXIT)
219         ut->ut_exit.__e_exit = WEXITSTATUS(status);
220         ut->ut_exit.__e_termination = WIFEXITED(status) ? WEXITSTATUS(status) : 0;
221 # endif
222         utmp_settime(ut);
223         if (pututxline(ut) != NULL)
224             rval = TRUE;
225     }
226     return rval;
227 }
228
229 #else /* !HAVE_GETUTXID && !HAVE_GETUTID */
230
231 /*
232  * Find the slot for the specified line (tty name and file descriptor).
233  * Returns a slot suitable for seeking into utmp on success or <= 0 on error.
234  * If getttyent() is available we can use that to compute the slot.
235  */
236 # ifdef HAVE_GETTTYENT
237 static int
238 utmp_slot(const char *line, int ttyfd)
239 {
240     int slot = 1;
241     struct ttyent *tty;
242
243     setttyent();
244     while ((tty = getttyent()) != NULL) {
245         if (strcmp(line, tty->ty_name) == 0)
246             break;
247         slot++;
248     }
249     endttyent();
250     return tty ? slot : 0;
251 }
252 # else
253 static int
254 utmp_slot(const char *line, int ttyfd)
255 {
256     int sfd, slot;
257
258     /*
259      * Temporarily point stdin to the tty since ttyslot()
260      * doesn't take an argument.
261      */
262     if ((sfd = dup(STDIN_FILENO)) == -1)
263         error(1, "Can't save stdin");
264     if (dup2(ttyfd, STDIN_FILENO) == -1)
265         error(1, "Can't dup2 stdin");
266     slot = ttyslot();
267     if (dup2(sfd, STDIN_FILENO) == -1)
268         error(1, "Can't restore stdin");
269     close(sfd);
270
271     return slot;
272 }
273 # endif /* HAVE_GETTTYENT */
274
275 int
276 utmp_login(const char *from_line, const char *to_line, int ttyfd,
277     const char *user)
278 {
279     sudo_utmp_t utbuf, *ut_old = NULL;
280     int slot, rval = FALSE;
281     FILE *fp;
282
283     /* Strip off /dev/ prefix from line as needed. */
284     if (strncmp(to_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
285         to_line += sizeof(_PATH_DEV) - 1;
286
287     /* Find slot for new entry. */
288     slot = utmp_slot(to_line, ttyfd);
289     if (slot <= 0)
290         goto done;
291
292     if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
293         goto done;
294
295     if (from_line != NULL) {
296         if (strncmp(from_line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
297             from_line += sizeof(_PATH_DEV) - 1;
298
299         /* Lookup old line. */
300         while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
301 # ifdef HAVE_STRUCT_UTMP_UT_ID
302             if (utbuf.ut_type != LOGIN_PROCESS && utbuf.ut_type != USER_PROCESS)
303                 continue;
304 # endif
305             if (utbuf.ut_user[0] &&
306                 !strncmp(utbuf.ut_line, from_line, sizeof(utbuf.ut_line))) {
307                 ut_old = &utbuf;
308                 break;
309             }
310         }
311     }
312     utmp_fill(to_line, user, ut_old, &utbuf);
313     if (fseek(fp, slot * (long)sizeof(utbuf), SEEK_SET) == 0) {
314         if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
315             rval = TRUE;
316     }
317     fclose(fp);
318
319 done:
320     return rval;
321 }
322
323 int
324 utmp_logout(const char *line, int status)
325 {
326     sudo_utmp_t utbuf;
327     int rval = FALSE;
328     FILE *fp;
329
330     if ((fp = fopen(_PATH_UTMP, "r+")) == NULL)
331         return rval;
332
333     /* Strip off /dev/ prefix from line as needed. */
334     if (strncmp(line, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
335         line += sizeof(_PATH_DEV) - 1;
336    
337     while (fread(&utbuf, sizeof(utbuf), 1, fp) == 1) {
338         if (!strncmp(utbuf.ut_line, line, sizeof(utbuf.ut_line))) {
339             memset(utbuf.ut_user, 0, sizeof(utbuf.ut_user));
340 # if defined(HAVE_STRUCT_UTMP_UT_TYPE)
341             utbuf.ut_type = DEAD_PROCESS;
342 # endif
343             utmp_settime(&utbuf);
344             /* Back up and overwrite record. */
345             if (fseek(fp, 0L - (long)sizeof(utbuf), SEEK_CUR) == 0) {
346                 if (fwrite(&utbuf, sizeof(utbuf), 1, fp) == 1)
347                     rval = TRUE;
348             }
349             break;
350         }
351     }
352     fclose(fp);
353
354     return rval;
355 }
356 #endif /* HAVE_GETUTXID || HAVE_GETUTID */