Initial commit for Tizen
[profile/extras/shadow-utils.git] / lib / port.c
1 /*
2  * Copyright (c) 1989 - 1994, Julianne Frances Haugh
3  * Copyright (c) 1996 - 1997, Marek Michałkiewicz
4  * Copyright (c) 2005       , Tomasz Kłoczko
5  * Copyright (c) 2008       , Nicolas François
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the copyright holders or contributors may not be used to
17  *    endorse or promote products derived from this software without
18  *    specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include <config.h>
34
35 #ident "$Id: port.c 2130 2008-06-13 18:11:09Z nekral-guest $"
36
37 #include <stdio.h>
38 #include <ctype.h>
39 #include <errno.h>
40 #include "defines.h"
41 #include "prototypes.h"
42 #include "port.h"
43
44 static FILE *ports;
45
46 /*
47  * portcmp - compare the name of a port to a /etc/porttime entry
48  *
49  *      portcmp works like strcmp, except that if the last character
50  *      in a failing match is a '*', the match is considered to have
51  *      passed.  The "*" match is suppressed whenever the port is "SU",
52  *      which is the token the "su" command uses to validate access.
53  *      A match returns 0, failure returns non-zero.
54  */
55
56 static int portcmp (const char *pattern, const char *port)
57 {
58         const char *orig = port;
59
60         while (('\0' != *pattern) && (*pattern == *port)) {
61                 pattern++;
62                 port++;
63         }
64
65         if (('\0' == *pattern) && ('\0' == *port)) {
66                 return 0;
67         }
68         if (('S' == orig[0]) && ('U' == orig[1]) && ('\0' == orig[2])) {
69                 return 1;
70         }
71
72         return (*pattern == '*') ? 0 : 1;
73 }
74
75 /*
76  * setportent - open /etc/porttime file or rewind
77  *
78  *      the /etc/porttime file is rewound if already open, or
79  *      opened for reading.
80  */
81
82 static void setportent (void)
83 {
84         if (NULL != ports) {
85                 rewind (ports);
86         } else {
87                 ports = fopen (PORTS, "r");
88         }
89 }
90
91 /*
92  * endportent - close the /etc/porttime file
93  *
94  *      the /etc/porttime file is closed and the ports variable set
95  *      to NULL to indicate that the /etc/porttime file is no longer
96  *      open.
97  */
98
99 static void endportent (void)
100 {
101         if (NULL != ports) {
102                 (void) fclose (ports);
103         }
104
105         ports = (FILE *) 0;
106 }
107
108 /*
109  * getportent - read a single entry from /etc/porttime
110  *
111  *      the next line in /etc/porttime is converted to a (struct port)
112  *      and a pointer to a static (struct port) is returned to the
113  *      invoker.  NULL is returned on either EOF or error.  errno is
114  *      set to EINVAL on error to distinguish the two conditions.
115  */
116
117 static struct port *getportent (void)
118 {
119         static struct port port;        /* static struct to point to         */
120         static char buf[BUFSIZ];        /* some space for stuff              */
121         static char *ttys[PORT_TTY + 1];        /* some pointers to tty names     */
122         static char *users[PORT_IDS + 1];       /* some pointers to user ids     */
123         static struct pt_time ptimes[PORT_TIMES + 1];   /* time ranges         */
124         char *cp;               /* pointer into line                 */
125         int dtime;              /* scratch time of day               */
126         int i, j;
127         int saveerr = errno;    /* errno value on entry              */
128
129         /*
130          * If the ports file is not open, open the file.  Do not rewind
131          * since we want to search from the beginning each time.
132          */
133
134         if (NULL == ports) {
135                 setportent ();
136         }
137
138         if (NULL == ports) {
139                 errno = saveerr;
140                 return 0;
141         }
142
143         /*
144          * Common point for beginning a new line -
145          *
146          *      - read a line, and NUL terminate
147          *      - skip lines which begin with '#'
148          *      - parse off the tty names
149          *      - parse off a list of user names
150          *      - parse off a list of days and times
151          */
152
153       again:
154
155         /*
156          * Get the next line and remove the last character, which
157          * is a '\n'.  Lines which begin with '#' are all ignored.
158          */
159
160         if (fgets (buf, (int) sizeof buf, ports) == 0) {
161                 errno = saveerr;
162                 return 0;
163         }
164         if ('#' == buf[0]) {
165                 goto again;
166         }
167
168         /*
169          * Get the name of the TTY device.  It is the first colon
170          * separated field, and is the name of the TTY with no
171          * leading "/dev".  The entry '*' is used to specify all
172          * TTY devices.
173          */
174
175         buf[strlen (buf) - 1] = 0;
176
177         port.pt_names = ttys;
178         for (cp = buf, j = 0; j < PORT_TTY; j++) {
179                 port.pt_names[j] = cp;
180                 while (('\0' != *cp) && (':' != *cp) && (',' != *cp)) {
181                         cp++;
182                 }
183
184                 if ('\0' == *cp) {
185                         goto again;     /* line format error */
186                 }
187
188                 if (':' == *cp) {       /* end of tty name list */
189                         break;
190                 }
191
192                 if (',' == *cp) {       /* end of current tty name */
193                         *cp++ = '\0';
194                 }
195         }
196         *cp = '\0';
197         cp++;
198         port.pt_names[j + 1] = (char *) 0;
199
200         /*
201          * Get the list of user names.  It is the second colon
202          * separated field, and is a comma separated list of user
203          * names.  The entry '*' is used to specify all usernames.
204          * The last entry in the list is a (char *) 0 pointer.
205          */
206
207         if (':' != *cp) {
208                 port.pt_users = users;
209                 port.pt_users[0] = cp;
210
211                 for (j = 1; ':' != *cp; cp++) {
212                         if ((',' == *cp) && (j < PORT_IDS)) {
213                                 *cp = '\0';
214                                 cp++;
215                                 port.pt_users[j] = cp;
216                                 j++;
217                         }
218                 }
219                 port.pt_users[j] = 0;
220         } else {
221                 port.pt_users = 0;
222         }
223
224         if (':' != *cp) {
225                 goto again;
226         }
227
228         *cp = '\0';
229         cp++;
230
231         /*
232          * Get the list of valid times.  The times field is the third
233          * colon separated field and is a list of days of the week and
234          * times during which this port may be used by this user.  The
235          * valid days are 'Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', and 'Sa'.
236          *
237          * In addition, the value 'Al' represents all 7 days, and 'Wk'
238          * represents the 5 weekdays.
239          *
240          * Times are given as HHMM-HHMM.  The ending time may be before
241          * the starting time.  Days are presumed to wrap at 0000.
242          */
243
244         if ('\0' == *cp) {
245                 port.pt_times = 0;
246                 return &port;
247         }
248
249         port.pt_times = ptimes;
250
251         /*
252          * Get the next comma separated entry
253          */
254
255         for (j = 0; ('\0' != *cp) && (j < PORT_TIMES); j++) {
256
257                 /*
258                  * Start off with no days of the week
259                  */
260
261                 port.pt_times[j].t_days = 0;
262
263                 /*
264                  * Check each two letter sequence to see if it is
265                  * one of the abbreviations for the days of the
266                  * week or the other two values.
267                  */
268
269                 for (i = 0;
270                      ('\0' != cp[i]) && ('\0' != cp[i + 1]) && isalpha (cp[i]);
271                      i += 2) {
272                         switch ((cp[i] << 8) | (cp[i + 1])) {
273                         case ('S' << 8) | 'u':
274                                 port.pt_times[j].t_days |= 01;
275                                 break;
276                         case ('M' << 8) | 'o':
277                                 port.pt_times[j].t_days |= 02;
278                                 break;
279                         case ('T' << 8) | 'u':
280                                 port.pt_times[j].t_days |= 04;
281                                 break;
282                         case ('W' << 8) | 'e':
283                                 port.pt_times[j].t_days |= 010;
284                                 break;
285                         case ('T' << 8) | 'h':
286                                 port.pt_times[j].t_days |= 020;
287                                 break;
288                         case ('F' << 8) | 'r':
289                                 port.pt_times[j].t_days |= 040;
290                                 break;
291                         case ('S' << 8) | 'a':
292                                 port.pt_times[j].t_days |= 0100;
293                                 break;
294                         case ('W' << 8) | 'k':
295                                 port.pt_times[j].t_days |= 076;
296                                 break;
297                         case ('A' << 8) | 'l':
298                                 port.pt_times[j].t_days |= 0177;
299                                 break;
300                         default:
301                                 errno = EINVAL;
302                                 return 0;
303                         }
304                 }
305
306                 /*
307                  * The default is 'Al' if no days were seen.
308                  */
309
310                 if (0 == i) {
311                         port.pt_times[j].t_days = 0177;
312                 }
313
314                 /*
315                  * The start and end times are separated from each
316                  * other by a '-'.  The times are four digit numbers
317                  * representing the times of day.
318                  */
319
320                 for (dtime = 0; ('\0' != cp[i]) && isdigit (cp[i]); i++) {
321                         dtime = dtime * 10 + cp[i] - '0';
322                 }
323
324                 if (('-' != cp[i]) || (dtime > 2400) || ((dtime % 100) > 59)) {
325                         goto again;
326                 }
327                 port.pt_times[j].t_start = dtime;
328                 cp = cp + i + 1;
329
330                 for (dtime = 0, i = 0;
331                      ('\0' != cp[i]) && isdigit (cp[i]);
332                      i++) {
333                         dtime = dtime * 10 + cp[i] - '0';
334                 }
335
336                 if (   ((',' != cp[i]) && ('\0' != cp[i]))
337                     || (dtime > 2400)
338                     || ((dtime % 100) > 59)) {
339                         goto again;
340                 }
341
342                 port.pt_times[j].t_end = dtime;
343                 cp = cp + i + 1;
344         }
345
346         /*
347          * The end of the list is indicated by a pair of -1's for the
348          * start and end times.
349          */
350
351         port.pt_times[j].t_start = port.pt_times[j].t_end = -1;
352
353         return &port;
354 }
355
356 /*
357  * getttyuser - get ports information for user and tty
358  *
359  *      getttyuser() searches the ports file for an entry with a TTY
360  *      and user field both of which match the supplied TTY and
361  *      user name.  The file is searched from the beginning, so the
362  *      entries are treated as an ordered list.
363  */
364
365 static struct port *getttyuser (const char *tty, const char *user)
366 {
367         int i, j;
368         struct port *port;
369
370         setportent ();
371
372         while ((port = getportent ()) != NULL) {
373                 if (   (0 == port->pt_names)
374                     || (0 == port->pt_users)) {
375                         continue;
376                 }
377
378                 for (i = 0; NULL != port->pt_names[i]; i++) {
379                         if (portcmp (port->pt_names[i], tty) == 0) {
380                                 break;
381                         }
382                 }
383
384                 if (port->pt_names[i] == 0) {
385                         continue;
386                 }
387
388                 for (j = 0; NULL != port->pt_users[j]; j++) {
389                         if (   (strcmp (user, port->pt_users[j]) == 0)
390                             || (strcmp (port->pt_users[j], "*") == 0)) {
391                                 break;
392                         }
393                 }
394
395                 if (port->pt_users[j] != 0) {
396                         break;
397                 }
398         }
399         endportent ();
400         return port;
401 }
402
403 /*
404  * isttytime - tell if a given user may login at a particular time
405  *
406  *      isttytime searches the ports file for an entry which matches
407  *      the user name and TTY given.
408  */
409
410 bool isttytime (const char *id, const char *port, time_t when)
411 {
412         int i;
413         int dtime;
414         struct port *pp;
415         struct tm *tm;
416
417         /*
418          * Try to find a matching entry for this user.  Default to
419          * letting the user in - there are plenty of ways to have an
420          * entry to match all users.
421          */
422
423         pp = getttyuser (port, id);
424         if (NULL == pp) {
425                 return true;
426         }
427
428         /*
429          * The entry is there, but has no time entries - don't
430          * ever let them login.
431          */
432
433         if (0 == pp->pt_times) {
434                 return false;
435         }
436
437         /*
438          * The current time is converted to HHMM format for
439          * comparison against the time values in the TTY entry.
440          */
441
442         tm = localtime (&when);
443         dtime = tm->tm_hour * 100 + tm->tm_min;
444
445         /*
446          * Each time entry is compared against the current
447          * time.  For entries with the start after the end time,
448          * the comparison is made so that the time is between
449          * midnight and either the start or end time.
450          */
451
452         for (i = 0; pp->pt_times[i].t_start != -1; i++) {
453                 if (!(pp->pt_times[i].t_days & PORT_DAY (tm->tm_wday))) {
454                         continue;
455                 }
456
457                 if (pp->pt_times[i].t_start <= pp->pt_times[i].t_end) {
458                         if (   (dtime >= pp->pt_times[i].t_start)
459                             && (dtime <= pp->pt_times[i].t_end)) {
460                                 return true;
461                         }
462                 } else {
463                         if (   (dtime >= pp->pt_times[i].t_start)
464                             || (dtime <= pp->pt_times[i].t_end)) {
465                                 return true;
466                         }
467                 }
468         }
469
470         /*
471          * No matching time entry was found, user shouldn't
472          * be let in right now.
473          */
474
475         return false;
476 }
477