summaryrefslogtreecommitdiff
path: root/grp/compat-initgroups.c
blob: ff10e247f4c776e942fb855bd75cec820d0f0d87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/* Prototype for the setgrent functions we use here.  */
typedef enum nss_status (*set_function) (void);

/* Prototype for the endgrent functions we use here.  */
typedef enum nss_status (*end_function) (void);

/* Prototype for the setgrent functions we use here.  */
typedef enum nss_status (*get_function) (struct group *, char *,
					 size_t, int *);


static enum nss_status
compat_call (service_user *nip, const char *user, gid_t group, long int *start,
	     long int *size, gid_t **groupsp, long int limit, int *errnop)
{
  struct group grpbuf;
  size_t buflen = __sysconf (_SC_GETGR_R_SIZE_MAX);
  enum nss_status status;
  set_function setgrent_fct;
  get_function getgrent_fct;
  end_function endgrent_fct;
  gid_t *groups = *groupsp;

  getgrent_fct = __nss_lookup_function (nip, "getgrent_r");
  if (getgrent_fct == NULL)
    return NSS_STATUS_UNAVAIL;

  setgrent_fct = __nss_lookup_function (nip, "setgrent");
  if (setgrent_fct)
    {
      status = DL_CALL_FCT (setgrent_fct, ());
      if (status != NSS_STATUS_SUCCESS)
	return status;
    }

  endgrent_fct = __nss_lookup_function (nip, "endgrent");

  char *tmpbuf = __alloca (buflen);
  bool use_malloc = false;
  enum nss_status result = NSS_STATUS_SUCCESS;

  do
    {
      while ((status = DL_CALL_FCT (getgrent_fct,
				     (&grpbuf, tmpbuf, buflen, errnop)),
	      status == NSS_STATUS_TRYAGAIN)
	     && *errnop == ERANGE)
        {
	  if (__libc_use_alloca (buflen * 2))
	    tmpbuf = extend_alloca (tmpbuf, buflen, buflen * 2);
	  else
	    {
	      buflen *= 2;
	      char *newbuf = realloc (use_malloc ? tmpbuf : NULL, buflen);
	      if (newbuf == NULL)
		{
		  result = NSS_STATUS_TRYAGAIN;
		  goto done;
		}
	      use_malloc = true;
	      tmpbuf = newbuf;
	    }
        }

      if (status != NSS_STATUS_SUCCESS)
        goto done;

      if (grpbuf.gr_gid != group)
        {
          char **m;

          for (m = grpbuf.gr_mem; *m != NULL; ++m)
            if (strcmp (*m, user) == 0)
              {
		/* Check whether the group is already on the list.  */
		long int cnt;
		for (cnt = 0; cnt < *start; ++cnt)
		  if (groups[cnt] == grpbuf.gr_gid)
		    break;

		if (cnt == *start)
		  {
		    /* Matches user and not yet on the list.  Insert
		       this group.  */
		    if (__glibc_unlikely (*start == *size))
		      {
			/* Need a bigger buffer.  */
			gid_t *newgroups;
			long int newsize;

			if (limit > 0 && *size == limit)
			  /* We reached the maximum.  */
			  goto done;

			if (limit <= 0)
			  newsize = 2 * *size;
			else
			  newsize = MIN (limit, 2 * *size);

			newgroups = realloc (groups,
					     newsize * sizeof (*groups));
			if (newgroups == NULL)
			  goto done;
			*groupsp = groups = newgroups;
			*size = newsize;
		      }

		    groups[*start] = grpbuf.gr_gid;
		    *start += 1;
		  }

                break;
              }
        }
    }
  while (status == NSS_STATUS_SUCCESS);

 done:
  if (use_malloc)
    free (tmpbuf);

  if (endgrent_fct)
    DL_CALL_FCT (endgrent_fct, ());

  return result;
}