Upload Tizen:Base source
[framework/base/util-linux-ng.git] / login-utils / checktty.c
1 /* checktty.c - linked into login, checks user against /etc/usertty
2    Created 25-Aug-95 by Peter Orbaek <poe@daimi.aau.dk>
3    Fixed by JDS June 1996 to clear lists and close files
4
5    1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
6    - added Native Language Support
7
8 */
9
10 #include <sys/types.h>
11 #include <sys/param.h>
12
13 #include <pwd.h>
14 #include <grp.h>
15 #include <string.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <time.h>
20 #include <sys/stat.h>
21 #include <malloc.h>
22 #include <netdb.h>
23 #include <sys/syslog.h>
24 #include <ctype.h>
25 #include "nls.h"
26
27 #include <sys/sysmacros.h>
28 #ifdef HAVE_LINUX_MAJOR_H
29 #include <linux/major.h>
30 #endif
31
32 #include "pathnames.h"
33 #include "login.h"
34 #include "xstrncpy.h"
35
36 #ifndef TTY_MAJOR
37 #define TTY_MAJOR 4
38 #endif
39
40 static gid_t mygroups[NGROUPS];
41 static int   num_groups;
42
43 #define NAMELEN 128
44
45 /* linked list of names */
46 struct grplist {
47     struct grplist *next;
48     char name[NAMELEN];
49 };
50         
51 enum State { StateUsers, StateGroups, StateClasses };
52
53 #define CLASSNAMELEN 32
54
55 struct ttyclass {
56     struct grplist *first;
57     struct ttyclass *next;
58     char classname[CLASSNAMELEN];
59 };
60
61 struct ttyclass *ttyclasses = NULL;
62
63 static int
64 am_in_group(char *group)
65 {
66         struct group *g;
67         gid_t *ge;
68         
69         g = getgrnam(group);
70         if (g) {
71                 for (ge = mygroups; ge < mygroups + num_groups; ge++) {
72                         if (g->gr_gid== *ge) return 1;
73                 }
74         }
75         return 0;
76 }
77
78 static void
79 find_groups(gid_t defgrp, const char *user)
80 {
81         num_groups = getgroups(NGROUPS, mygroups);
82 }
83
84 static struct ttyclass *
85 new_class(char *class)
86 {
87     struct ttyclass *tc;
88
89     tc = (struct ttyclass *)malloc(sizeof(struct ttyclass));
90     if (tc == NULL) {
91         printf(_("login: memory low, login may fail\n"));
92         syslog(LOG_WARNING, _("can't malloc for ttyclass"));
93         return NULL;
94     }
95
96     tc->next = ttyclasses;
97     tc->first = NULL;
98     xstrncpy(tc->classname, class, CLASSNAMELEN);
99     ttyclasses = tc;
100     return tc;
101 }
102
103 static void
104 add_to_class(struct ttyclass *tc, char *tty)
105 {
106     struct grplist *ge;
107
108     if (tc == NULL) return;
109
110     ge = (struct grplist *)malloc(sizeof(struct grplist));
111     if (ge == NULL) {
112         printf(_("login: memory low, login may fail\n"));
113         syslog(LOG_WARNING, _("can't malloc for grplist"));
114         return;
115     }
116
117     ge->next = tc->first;
118     xstrncpy(ge->name, tty, NAMELEN);
119     tc->first = ge;
120 }
121
122
123 /* return true if tty is a pty. Very linux dependent */
124 static int
125 isapty(const char *tty)
126 {
127 #ifdef __linux__
128     char devname[100];
129     struct stat stb;
130
131     /* avoid snprintf - old systems do not have it */
132     if (strlen(tty) + 6 > sizeof(devname))
133             return 0;
134     sprintf(devname, "/dev/%s", tty);
135
136     if((stat(devname, &stb) >= 0) && S_ISCHR(stb.st_mode)) {
137             int majordev = major(stb.st_rdev);
138
139             /* this is for linux versions before 1.3: use major 4 */
140             if(majordev == TTY_MAJOR && minor(stb.st_rdev) >= 192)
141                     return 1;
142
143 #if defined(PTY_SLAVE_MAJOR)
144             /* this is for linux 1.3 and newer: use major 3 */
145             if(majordev == PTY_SLAVE_MAJOR)
146                     return 1;
147 #endif
148
149 #if defined(UNIX98_PTY_SLAVE_MAJOR) && defined(UNIX98_PTY_MAJOR_COUNT)
150             /* this is for linux 2.1.116 and newer: use majors 136-143 */
151             if(majordev >= UNIX98_PTY_SLAVE_MAJOR &&
152                majordev < UNIX98_PTY_SLAVE_MAJOR + UNIX98_PTY_MAJOR_COUNT)
153                     return 1;
154 #endif
155
156     }
157 #endif  /* __linux__ */
158     return 0;
159 }
160
161
162 /* IPv4 -- pattern is x.x.x.x/y.y.y.y (net/mask)*/
163 static int
164 hnmatch_ip4(const char *pat)
165 {
166         int x1, x2, x3, x4, y1, y2, y3, y4;
167         unsigned long p, mask, a;
168         unsigned char *ha;
169
170         /* pattern is an IP QUAD address and a mask x.x.x.x/y.y.y.y */
171         if (sscanf(pat, "%d.%d.%d.%d/%d.%d.%d.%d",
172                         &x1, &x2, &x3, &x4, &y1, &y2, &y3, &y4) < 8)
173                 return 0;
174
175         p = (((unsigned long)x1<<24)+((unsigned long)x2<<16)
176              +((unsigned long)x3<<8)+((unsigned long)x4));
177         mask = (((unsigned long)y1<<24)+((unsigned long)y2<<16)
178                 +((unsigned long)y3<<8)+((unsigned long)y4));
179
180         if (hostaddress[0] == 0)
181                 return 0;
182
183         ha = (unsigned char *)hostaddress;
184         a = (((unsigned long)ha[0]<<24)+((unsigned long)ha[1]<<16)
185              +((unsigned long)ha[2]<<8)+((unsigned long)ha[3]));
186         return ((p & mask) == (a & mask));
187 }
188
189 /* IPv6 -- pattern is [hex:hex:....]/number ([net]/mask) */
190 static int
191 hnmatch_ip6(const char *pat)
192 {
193         char *patnet;
194         char *patmask;
195         struct in6_addr addr;
196         struct addrinfo hints, *res;
197         struct sockaddr_in6 net;
198         int mask_len, i = 0;
199         char *p;
200
201         if (pat == NULL || *pat != '[')
202                 return 0;
203
204         memcpy(&addr, hostaddress, sizeof(addr));
205
206         memset(&hints, 0, sizeof(hints));
207         hints.ai_family = AF_INET6;
208         hints.ai_socktype = SOCK_STREAM;
209         hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
210
211         patnet = strdup(pat);
212
213         /* match IPv6 address against [netnumber]/prefixlen */
214         if (!(p = strchr(patnet, ']')))
215                 goto mismatch;
216
217         *p++ = '\0';
218
219         if (! (*p == '/' && isdigit((unsigned char) *(p + 1))))
220                 goto mismatch;
221
222         patmask = p + 1;
223
224         /* prepare net address */
225         if (getaddrinfo(patnet + 1, NULL, &hints, &res) != 0)
226                 goto mismatch;
227
228         memcpy(&net, res->ai_addr, sizeof(net));
229         freeaddrinfo(res);
230
231         /* convert mask to number */
232         if ((mask_len = atoi(patmask)) < 0 || mask_len > 128)
233                 goto mismatch;
234
235         /* compare */
236         while (mask_len > 0) {
237                 if (mask_len < 32) {
238                         u_int32_t mask = htonl(~(0xffffffff >> mask_len));
239
240                         if ((*(u_int32_t *)&addr.s6_addr[i] & mask) !=
241                             (*(u_int32_t *)&net.sin6_addr.s6_addr[i] & mask))
242                                 goto mismatch;
243                         break;
244                 }
245                 if (*(u_int32_t *)&addr.s6_addr[i] !=
246                     *(u_int32_t *)&net.sin6_addr.s6_addr[i])
247                         goto mismatch;
248                 i += 4;
249                 mask_len -= 32;
250         }
251
252         free(patnet);
253         return 1;
254
255 mismatch:
256         free(patnet);
257         return 0;
258 }
259
260 /* match the hostname hn against the pattern pat */
261 static int
262 hnmatch(const char *hn, const char *pat)
263 {
264
265         if ((hn == NULL) && (strcmp(pat, "localhost") == 0))
266                 return 1;
267         if ((hn == NULL) || *hn == '\0')
268                 return 0;
269
270         if (*pat >= '0' && *pat <= '9')
271                 return hostfamily == AF_INET ? hnmatch_ip4(pat) : 0;
272         else if (*pat == '[')
273                 return hostfamily == AF_INET6 ? hnmatch_ip6(pat) : 0;
274         else {
275                 /* pattern is a suffix of a FQDN */
276                 int     n = strlen(pat),
277                         m = strlen(hn);
278
279                 if (n > m)
280                         return 0;
281                 return (strcasecmp(pat, hn + m - n) == 0);
282         }
283 }
284
285 #ifdef MAIN_TEST_CHECKTTY
286
287 char    hostaddress[16];
288 sa_family_t hostfamily;
289 char    *hostname;
290 void    sleepexit(int eval) {}          /* dummy for this test */
291 void    badlogin(const char *s) {}      /* dummy for this test */
292
293 int
294 main(int argc, char **argv)
295 {
296         struct addrinfo hints, *info = NULL;
297         struct addrexp {
298                 const char *range;
299                 const char *ip;
300         } alist[] = {
301                 { "130.225.16.0/255.255.254.0", "130.225.16.1" },
302                 { "130.225.16.0/255.255.254.0", "10.20.30.1" },
303                 { "130.225.0.0/255.254.0.0",    "130.225.16.1" },
304                 { "130.225.0.0/255.254.0.0",    "130.225.17.1" },
305                 { "130.225.0.0/255.254.0.0",    "150.160.170.180" },
306                 { "[3ffe:505:2:1::]/64",        "3ffe:505:2:1::" },
307                 { "[3ffe:505:2:1::]/64",        "3ffe:505:2:2::" },
308                 { "[3ffe:505:2:1::]/64",        "3ffe:505:2:1:ffff:ffff::" },
309                 { NULL, NULL }
310         }, *item;
311
312         memset(&hints, 0, sizeof(hints));
313         hints.ai_family = AF_UNSPEC;
314         hints.ai_flags = AI_NUMERICHOST |  AI_PASSIVE | AI_ADDRCONFIG;
315         hints.ai_socktype = SOCK_STREAM;
316
317         for (item = alist; item->range; item++) {
318
319                 printf("hnmatch() on %-30s <-- %-15s: ", item->range, item->ip);
320
321                 if (getaddrinfo(item->ip, NULL, &hints, &info)==0 && info) {
322                         if (info->ai_family == AF_INET) {
323                             struct sockaddr_in *sa =
324                                         (struct sockaddr_in *) info->ai_addr;
325                             memcpy(hostaddress, &(sa->sin_addr),
326                                         sizeof(sa->sin_addr));
327                         }
328                         else if (info->ai_family == AF_INET6) {
329                             struct sockaddr_in6 *sa =
330                                         (struct sockaddr_in6 *) info->ai_addr;
331                             memcpy(hostaddress, &(sa->sin6_addr),
332                                         sizeof(sa->sin6_addr));
333                         }
334                         hostfamily = info->ai_family;
335                         freeaddrinfo(info);
336                         printf("%s\n", hnmatch("dummy", item->range) ?
337                                                 "match" : "mismatch");
338                 }
339                 else
340                         printf("getaddrinfo() failed\n");
341
342         }
343         return 0;
344 }
345 #endif /* MAIN_TEST_CHECKTTY */
346
347 static char *wdays[] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
348
349 /* example timespecs:
350
351    mon:tue:wed:8-17
352
353    meaning monday, tuesday or wednesday between 8:00 and 17:59
354
355    4:5:13:fri
356
357    meaning fridays from 4:00 to 5:59 and from 13:00 to 13:59
358 */
359 static int
360 timeok(struct tm *t, char *spec)
361 {
362     char *p, *q;
363     int dayok = 0;
364     int hourok = 0;
365     int h, h2;
366     char *sp;
367
368     sp = spec;
369     while ((p = strsep(&sp, ":"))) {
370         if (*p >= '0' && *p <= '9') {
371             h = atoi(p);
372             if (h == t->tm_hour) hourok = 1;
373             if ((q = strchr(p, '-')) && (q[1] >= '0' && q[1] <= '9')) {
374                 h2 = atoi(q+1);
375                 if (h <= t->tm_hour && t->tm_hour <= h2) hourok = 1;
376             }
377         } else if (strcasecmp(wdays[t->tm_wday], p) == 0) {
378             dayok = 1;
379         }
380     }
381
382     return (dayok && hourok);
383 }
384
385 /* return true if tty equals class or is in the class defined by class.
386    Also return true if hostname matches the hostname pattern, class
387    or a pattern in the class named by class. */
388 static int
389 in_class(const char *tty, char *class)
390 {
391     struct ttyclass *tc;
392     struct grplist *ge;
393     time_t t;
394     char *p;
395     char timespec[256];
396     struct tm *tm;
397     char *n;
398
399     time(&t);
400     tm = localtime(&t);
401
402     if (class[0] == '[') {
403         if ((p = strchr(class, ']'))) {
404             *p = 0;
405             xstrncpy(timespec, class+1, sizeof(timespec));
406             *p = ']';
407             if(!timeok(tm, timespec)) return 0;
408             class = p+1;
409         }
410         /* really ought to warn about syntax error */
411     }
412
413     if (strcmp(tty, class) == 0) return 1;
414
415     if ((class[0] == '@') && isapty(tty)
416         && hnmatch(hostname, class+1)) return 1;
417
418     for (tc = ttyclasses; tc; tc = tc->next) {
419         if (strcmp(tc->classname, class) == 0) {
420             for (ge = tc->first; ge; ge = ge->next) {
421
422                 n = ge->name;
423                 if (n[0] == '[') {
424                     if ((p = strchr(n, ']'))) {
425                         *p = 0;
426                         xstrncpy(timespec, n+1, sizeof(timespec));
427                         *p = ']';
428                         if(!timeok(tm, timespec)) continue;
429                         n = p+1;
430                     }
431                     /* really ought to warn about syntax error */
432                 }
433
434                 if (strcmp(n, tty) == 0) return 1;
435
436                 if ((n[0] == '@') && isapty(tty)
437                     && hnmatch(hostname, n+1)) return 1;
438             }
439             return 0;
440         }
441     }
442     return 0;
443 }
444
445 /* start JDS - SBA */
446 static void 
447 free_group(struct grplist *ge)
448 {
449     if (ge) {
450         memset(ge->name, 0, NAMELEN);
451         free_group(ge->next);
452         free(ge->next);
453         ge->next = NULL;
454     }
455 }
456
457 static void 
458 free_class(struct ttyclass *tc)
459 {
460     if (tc) {
461         memset(tc->classname, 0, CLASSNAMELEN);
462         free_group(tc->first);
463         tc->first = NULL;
464         free_class(tc->next);
465         free(tc->next);
466         tc->next = NULL;
467     }
468 }
469
470 static void 
471 free_all(void)
472 {
473     free_class(ttyclasses);
474     ttyclasses = NULL;
475 }
476 /* end JDS - SBA */
477
478 void
479 checktty(const char *user, const char *tty, struct passwd *pwd)
480 {
481     FILE *f;
482     char buf[256], defaultbuf[256];
483     char *ptr;
484     enum State state = StateUsers;
485     int found_match = 0;
486
487     /* no /etc/usertty, default to allow access */
488     if (!(f = fopen(_PATH_USERTTY, "r"))) return;
489
490     if (pwd == NULL) {
491         fclose(f);
492         return;  /* misspelled username handled elsewhere */
493     }
494
495     find_groups(pwd->pw_gid, user);
496
497     defaultbuf[0] = 0;
498     while(fgets(buf, 255, f)) {
499
500         /* strip comments */
501         for(ptr = buf; ptr < buf + 256; ptr++) 
502           if(*ptr == '#') *ptr = 0;
503
504         if (buf[0] == '*') {
505             xstrncpy(defaultbuf, buf, 256);
506             continue;
507         }
508
509         if (strncmp("GROUPS", buf, 6) == 0) {
510             state = StateGroups;
511             continue;
512         } else if (strncmp("USERS", buf, 5) == 0) {
513             state = StateUsers;
514             continue;
515         } else if (strncmp("CLASSES", buf, 7) == 0) {
516             state = StateClasses;
517             continue;
518         }
519
520         strtok(buf, " \t");
521         if((state == StateUsers && (strncmp(user, buf, 8) == 0))
522            || (state == StateGroups && am_in_group(buf))) {
523             found_match = 1;  /* we found a line matching the user */
524             while((ptr = strtok(NULL, "\t\n "))) {
525                 if (in_class(tty, ptr)) {
526                     fclose(f);
527                     free_all(); /* JDS */
528                     return;
529                 }
530             }
531         } else if (state == StateClasses) {
532             /* define a new tty/host class */
533             struct ttyclass *tc = new_class(buf);
534
535             while ((ptr = strtok(NULL, "\t\n "))) {
536                 add_to_class(tc, ptr);
537             }
538         }
539     }
540     fclose(f);
541
542     /* user is not explicitly mentioned in /etc/usertty, if there was
543        a default rule, use that */
544     if (defaultbuf[0]) {
545         strtok(defaultbuf, " \t");
546         while((ptr = strtok(NULL, "\t\n "))) {
547             if (in_class(tty, ptr)) {
548                 free_all(); /* JDS */
549                 return;
550             }
551         }
552
553         /* there was a default rule, but user didn't match, reject! */
554         printf(_("Login on %s from %s denied by default.\n"), tty, hostname);
555         badlogin(user);
556         sleepexit(1);
557     }
558
559     if (found_match) {
560         /* if we get here, /etc/usertty exists, there's a line
561            matching our username, but it doesn't contain the
562            name of the tty where the user is trying to log in.
563            So deny access! */
564
565         printf(_("Login on %s from %s denied.\n"), tty, hostname);
566         badlogin(user);
567         sleepexit(1);
568     }
569
570     /* users not matched in /etc/usertty are by default allowed access
571        on all tty's */
572     free_all(); /* JDS */
573 }