1 /*
2  * Vector handling (counted lists of char *'s).
3  *
4  * A vector is a table for handling a list of strings with less overhead than
5  * linked list.  The intention is for vectors, once allocated, to be reused;
6  * this saves on memory allocations once the array of char *'s reaches a
7  * stable size.
8  *
9  * This is based on the util/vector.c library, but that library uses xmalloc
10  * routines to exit the program if memory allocation fails.  This is a
11  * modified version of the vector library that instead returns false on
12  * failure to allocate memory, allowing the caller to do appropriate recovery.
13  *
14  * Vectors require list of strings, not arbitrary binary data, and cannot
15  * handle data elements containing nul characters.
16  *
17  * Only the portions of the vector library used by PAM modules are
18  * implemented.
19  *
20  * The canonical version of this file is maintained in the rra-c-util package,
21  * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
22  *
23  * Written by Russ Allbery <eagle@eyrie.org>
24  * Copyright 2017-2018 Russ Allbery <eagle@eyrie.org>
25  * Copyright 2010-2011, 2014
26  *     The Board of Trustees of the Leland Stanford Junior University
27  *
28  * Copying and distribution of this file, with or without modification, are
29  * permitted in any medium without royalty provided the copyright notice and
30  * this notice are preserved.  This file is offered as-is, without any
31  * warranty.
32  *
33  * SPDX-License-Identifier: FSFAP
34  */
35 
36 #include <config.h>
37 #include <portable/system.h>
38 
39 #include <pam-util/vector.h>
40 
41 
42 /*
43  * Allocate a new, empty vector.  Returns NULL if memory allocation fails.
44  */
45 struct vector *
vector_new(void)46 vector_new(void)
47 {
48     struct vector *vector;
49 
50     vector = calloc(1, sizeof(struct vector));
51     vector->allocated = 1;
52     vector->strings = calloc(1, sizeof(char *));
53     return vector;
54 }
55 
56 
57 /*
58  * Allocate a new vector that's a copy of an existing vector.  Returns NULL if
59  * memory allocation fails.
60  */
61 struct vector *
vector_copy(const struct vector * old)62 vector_copy(const struct vector *old)
63 {
64     struct vector *vector;
65     size_t i;
66 
67     vector = vector_new();
68     if (!vector_resize(vector, old->count)) {
69         vector_free(vector);
70         return NULL;
71     }
72     vector->count = old->count;
73     for (i = 0; i < old->count; i++) {
74         vector->strings[i] = strdup(old->strings[i]);
75         if (vector->strings[i] == NULL) {
76             vector_free(vector);
77             return NULL;
78         }
79     }
80     return vector;
81 }
82 
83 
84 /*
85  * Resize a vector (using reallocarray to resize the table).  Return false if
86  * memory allocation fails.
87  */
88 bool
vector_resize(struct vector * vector,size_t size)89 vector_resize(struct vector *vector, size_t size)
90 {
91     size_t i;
92     char **strings;
93 
94     if (vector->count > size) {
95         for (i = size; i < vector->count; i++)
96             free(vector->strings[i]);
97         vector->count = size;
98     }
99     if (size == 0)
100         size = 1;
101     strings = reallocarray(vector->strings, size, sizeof(char *));
102     if (strings == NULL)
103         return false;
104     vector->strings = strings;
105     vector->allocated = size;
106     return true;
107 }
108 
109 
110 /*
111  * Add a new string to the vector, resizing the vector as necessary.  The
112  * vector is resized an element at a time; if a lot of resizes are expected,
113  * vector_resize should be called explicitly with a more suitable size.
114  * Return false if memory allocation fails.
115  */
116 bool
vector_add(struct vector * vector,const char * string)117 vector_add(struct vector *vector, const char *string)
118 {
119     size_t next = vector->count;
120 
121     if (vector->count == vector->allocated)
122         if (!vector_resize(vector, vector->allocated + 1))
123             return false;
124     vector->strings[next] = strdup(string);
125     if (vector->strings[next] == NULL)
126         return false;
127     vector->count++;
128     return true;
129 }
130 
131 
132 /*
133  * Empty a vector but keep the allocated memory for the pointer table.
134  */
135 void
vector_clear(struct vector * vector)136 vector_clear(struct vector *vector)
137 {
138     size_t i;
139 
140     for (i = 0; i < vector->count; i++)
141         if (vector->strings[i] != NULL)
142             free(vector->strings[i]);
143     vector->count = 0;
144 }
145 
146 
147 /*
148  * Free a vector completely.
149  */
150 void
vector_free(struct vector * vector)151 vector_free(struct vector *vector)
152 {
153     if (vector == NULL)
154         return;
155     vector_clear(vector);
156     free(vector->strings);
157     free(vector);
158 }
159 
160 
161 /*
162  * Given a vector that we may be reusing, clear it out.  If the first argument
163  * is NULL, allocate a new vector.  Used by vector_split*.  Returns NULL if
164  * memory allocation fails.
165  */
166 static struct vector *
vector_reuse(struct vector * vector)167 vector_reuse(struct vector *vector)
168 {
169     if (vector == NULL)
170         return vector_new();
171     else {
172         vector_clear(vector);
173         return vector;
174     }
175 }
176 
177 
178 /*
179  * Given a string and a set of separators expressed as a string, count the
180  * number of strings that it will split into when splitting on those
181  * separators.
182  */
183 static size_t
split_multi_count(const char * string,const char * seps)184 split_multi_count(const char *string, const char *seps)
185 {
186     const char *p;
187     size_t count;
188 
189     if (*string == '\0')
190         return 0;
191     for (count = 1, p = string + 1; *p != '\0'; p++)
192         if (strchr(seps, *p) != NULL && strchr(seps, p[-1]) == NULL)
193             count++;
194 
195     /*
196      * If the string ends in separators, we've overestimated the number of
197      * strings by one.
198      */
199     if (strchr(seps, p[-1]) != NULL)
200         count--;
201     return count;
202 }
203 
204 
205 /*
206  * Given a string, split it at any of the provided separators to form a
207  * vector, copying each string segment.  If the third argument isn't NULL,
208  * reuse that vector; otherwise, allocate a new one.  Any number of
209  * consecutive separators are considered a single separator.  Returns NULL on
210  * memory allocation failure, after which the provided vector may only have
211  * partial results.
212  */
213 struct vector *
vector_split_multi(const char * string,const char * seps,struct vector * vector)214 vector_split_multi(const char *string, const char *seps, struct vector *vector)
215 {
216     const char *p, *start;
217     size_t i, count;
218     bool created = false;
219 
220     if (vector == NULL)
221         created = true;
222     vector = vector_reuse(vector);
223     if (vector == NULL)
224         return NULL;
225 
226     count = split_multi_count(string, seps);
227     if (vector->allocated < count && !vector_resize(vector, count))
228         goto fail;
229 
230     vector->count = 0;
231     for (start = string, p = string, i = 0; *p != '\0'; p++)
232         if (strchr(seps, *p) != NULL) {
233             if (start != p) {
234                 vector->strings[i] = strndup(start, (size_t)(p - start));
235                 if (vector->strings[i] == NULL)
236                     goto fail;
237                 i++;
238                 vector->count++;
239             }
240             start = p + 1;
241         }
242     if (start != p) {
243         vector->strings[i] = strndup(start, (size_t)(p - start));
244         if (vector->strings[i] == NULL)
245             goto fail;
246         vector->count++;
247     }
248     return vector;
249 
250 fail:
251     if (created)
252         vector_free(vector);
253     return NULL;
254 }
255 
256 
257 /*
258  * Given a vector and a path to a program, exec that program with the vector
259  * as its arguments.  This requires adding a NULL terminator to the vector and
260  * casting it appropriately.  Returns 0 on success and -1 on error, like exec
261  * does.
262  */
263 int
vector_exec(const char * path,struct vector * vector)264 vector_exec(const char *path, struct vector *vector)
265 {
266     if (vector->allocated == vector->count)
267         if (!vector_resize(vector, vector->count + 1))
268             return -1;
269     vector->strings[vector->count] = NULL;
270     return execv(path, (char *const *) vector->strings);
271 }
272 
273 
274 /*
275  * Given a vector, a path to a program, and the environment, exec that program
276  * with the vector as its arguments and the given environment.  This requires
277  * adding a NULL terminator to the vector and casting it appropriately.
278  * Returns 0 on success and -1 on error, like exec does.
279  */
280 int
vector_exec_env(const char * path,struct vector * vector,const char * const env[])281 vector_exec_env(const char *path, struct vector *vector,
282                 const char *const env[])
283 {
284     if (vector->allocated == vector->count)
285         if (!vector_resize(vector, vector->count + 1))
286             return -1;
287     vector->strings[vector->count] = NULL;
288     return execve(path, (char *const *) vector->strings, (char *const *) env);
289 }
290