build: Move uid_wrapper to third_party
[metze/samba/wip.git] / lib / pam_wrapper / libpamtest.c
1 /*
2  * Copyright (c) 2015 Andreas Schneider <asn@samba.org>
3  * Copyright (c) 2015 Jakub Hrozek <jakub.hrozek@posteo.se>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include <stdlib.h>
20 #include <string.h>
21 #include <stdbool.h>
22
23 #include "libpamtest.h"
24
25 #define MIN(a,b) ((a) < (b) ? (a) : (b))
26
27 static enum pamtest_err run_test_case(pam_handle_t *ph,
28                                       struct pam_testcase *tc)
29 {
30         switch (tc->pam_operation) {
31         case PAMTEST_AUTHENTICATE:
32                 tc->op_rv = pam_authenticate(ph, tc->flags);
33                 return PAMTEST_ERR_OK;
34         case PAMTEST_SETCRED:
35                 tc->op_rv = pam_setcred(ph, tc->flags);
36                 return PAMTEST_ERR_OK;
37         case PAMTEST_ACCOUNT:
38                 tc->op_rv = pam_acct_mgmt(ph, tc->flags);
39                 return PAMTEST_ERR_OK;
40         case PAMTEST_OPEN_SESSION:
41                 tc->op_rv = pam_open_session(ph, tc->flags);
42                 return PAMTEST_ERR_OK;
43         case PAMTEST_CLOSE_SESSION:
44                 tc->op_rv = pam_close_session(ph, tc->flags);
45                 return PAMTEST_ERR_OK;
46         case PAMTEST_CHAUTHTOK:
47                 tc->op_rv = pam_chauthtok(ph, tc->flags);
48                 return PAMTEST_ERR_OK;
49         case PAMTEST_GETENVLIST:
50                 tc->case_out.envlist = pam_getenvlist(ph);
51                 return PAMTEST_ERR_OK;
52         case PAMTEST_KEEPHANDLE:
53                 tc->case_out.ph = ph;
54                 return PAMTEST_ERR_KEEPHANDLE;
55         default:
56                 return PAMTEST_ERR_OP;
57         }
58
59         return PAMTEST_ERR_OP;
60 }
61
62 enum pamtest_err _pamtest_conv(const char *service,
63                                const char *user,
64                                pam_conv_fn conv_fn,
65                                void *conv_userdata,
66                                struct pam_testcase test_cases[],
67                                size_t num_test_cases)
68 {
69         int rv;
70         pam_handle_t *ph;
71         struct pam_conv conv;
72         size_t tcindex;
73         struct pam_testcase *tc = NULL;
74         bool call_pam_end = true;
75
76         conv.conv = conv_fn;
77         conv.appdata_ptr = conv_userdata;
78
79         if (test_cases == NULL) {
80                 return PAMTEST_ERR_INTERNAL;
81         }
82
83         rv = pam_start(service, user, &conv, &ph);
84         if (rv != PAM_SUCCESS) {
85                 return PAMTEST_ERR_START;
86         }
87
88         for (tcindex = 0; tcindex < num_test_cases; tcindex++) {
89                 tc = &test_cases[tcindex];
90
91                 rv = run_test_case(ph, tc);
92                 if (rv == PAMTEST_ERR_KEEPHANDLE) {
93                         call_pam_end = false;
94                         continue;
95                 } else if (rv != PAMTEST_ERR_OK) {
96                         return PAMTEST_ERR_INTERNAL;
97                 }
98
99                 if (tc->op_rv != tc->expected_rv) {
100                         break;
101                 }
102         }
103
104         if (call_pam_end == true && tc != NULL) {
105                 rv = pam_end(ph, tc->op_rv);
106                 if (rv != PAM_SUCCESS) {
107                         return PAMTEST_ERR_END;
108                 }
109         }
110
111         if (tcindex < num_test_cases) {
112                 return PAMTEST_ERR_CASE;
113         }
114
115         return PAMTEST_ERR_OK;
116 }
117
118 void pamtest_free_env(char **envlist)
119 {
120         size_t i;
121
122         if (envlist == NULL) {
123                 return;
124         }
125
126         for (i = 0; envlist[i] != NULL; i++) {
127                 free(envlist[i]);
128         }
129         free(envlist);
130 }
131
132 const struct pam_testcase *
133 _pamtest_failed_case(struct pam_testcase *test_cases,
134                      size_t num_test_cases)
135 {
136         size_t tcindex;
137
138         for (tcindex = 0; tcindex < num_test_cases; tcindex++) {
139                 const struct pam_testcase *tc = &test_cases[tcindex];
140
141                 if (tc->expected_rv != tc->op_rv) {
142                         return tc;
143                 }
144         }
145
146         /* Nothing failed */
147         return NULL;
148 }
149
150 const char *pamtest_strerror(enum pamtest_err perr)
151 {
152         switch (perr) {
153         case PAMTEST_ERR_OK:
154                 return "Success";
155         case PAMTEST_ERR_START:
156                 return "pam_start failed()";
157         case PAMTEST_ERR_CASE:
158                 return "Unexpected testcase result";
159         case PAMTEST_ERR_OP:
160                 return "Could not run a test case";
161         case PAMTEST_ERR_END:
162                 return "pam_end failed()";
163         case PAMTEST_ERR_KEEPHANDLE:
164                 /* Fallthrough */
165         case PAMTEST_ERR_INTERNAL:
166                 return "Internal libpamtest error";
167         }
168
169         return "Unknown";
170 }
171
172 struct pamtest_conv_ctx {
173         struct pamtest_conv_data *data;
174
175         size_t echo_off_idx;
176         size_t echo_on_idx;
177         size_t err_idx;
178         size_t info_idx;
179 };
180
181 static int add_to_reply(struct pam_response *reply, const char *str)
182 {
183         size_t len;
184
185         len = strlen(str) + 1;
186
187         reply->resp = calloc(len, sizeof(char));
188         if (reply->resp == NULL) {
189                 return PAM_BUF_ERR;
190         }
191
192         memcpy(reply->resp, str, len);
193         return PAM_SUCCESS;
194 }
195
196 static void free_reply(struct pam_response *reply, int num_msg)
197 {
198         int i;
199
200         if (reply == NULL) {
201                 return;
202         }
203
204         for (i = 0; i < num_msg; i++) {
205                 free(reply[i].resp);
206         }
207         free(reply);
208 }
209
210 static int pamtest_simple_conv(int num_msg,
211                                const struct pam_message **msgm,
212                                struct pam_response **response,
213                                void *appdata_ptr)
214 {
215         int i, ri = 0;
216         int ret;
217         struct pam_response *reply = NULL;
218         const char *prompt;
219         struct pamtest_conv_ctx *cctx = \
220                                     (struct pamtest_conv_ctx *) appdata_ptr;
221
222         if (cctx == NULL) {
223                 return PAM_CONV_ERR;
224         }
225
226         if (response) {
227                 reply = (struct pam_response *) calloc(num_msg,
228                                                 sizeof(struct pam_response));
229                 if (reply == NULL) {
230                         return PAM_CONV_ERR;
231                 }
232         }
233
234         for (i=0; i < num_msg; i++) {
235                 switch (msgm[i]->msg_style) {
236                 case PAM_PROMPT_ECHO_OFF:
237                         prompt = (const char *) \
238                                    cctx->data->in_echo_off[cctx->echo_off_idx];
239
240                         if (reply != NULL) {
241                                 if (prompt != NULL) {
242                                         ret = add_to_reply(&reply[ri], prompt);
243                                         if (ret != PAM_SUCCESS) {
244                                                 free_reply(reply, num_msg);
245                                                 return ret;
246                                         }
247                                 } else {
248                                         reply[ri].resp = NULL;
249                                 }
250                                 ri++;
251                         }
252
253                         cctx->echo_off_idx++;
254                         break;
255                 case PAM_PROMPT_ECHO_ON:
256                         prompt = (const char *) \
257                                    cctx->data->in_echo_on[cctx->echo_on_idx];
258                         if (prompt == NULL) {
259                                 free_reply(reply, num_msg);
260                                 return PAM_CONV_ERR;
261                         }
262
263                         if (reply != NULL) {
264                                 if (prompt != NULL) {
265                                         ret = add_to_reply(&reply[ri], prompt);
266                                         if (ret != PAM_SUCCESS) {
267                                                 free_reply(reply, num_msg);
268                                                 return ret;
269                                         }
270                                 }
271                                 ri++;
272                         }
273
274                         cctx->echo_on_idx++;
275                         break;
276                 case PAM_ERROR_MSG:
277                         if (cctx->data->out_err != NULL) {
278                                 memcpy(cctx->data->out_err[cctx->err_idx],
279                                        msgm[i]->msg,
280                                        MIN(strlen(msgm[i]->msg),
281                                            PAM_MAX_MSG_SIZE));
282                                 cctx->err_idx++;
283                         }
284                         break;
285                 case PAM_TEXT_INFO:
286                         if (cctx->data->out_info != NULL) {
287                                 memcpy(cctx->data->out_info[cctx->info_idx],
288                                        msgm[i]->msg,
289                                        MIN(strlen(msgm[i]->msg),
290                                            PAM_MAX_MSG_SIZE));
291                                 cctx->info_idx++;
292                         }
293                         break;
294                 default:
295                         continue;
296                 }
297         }
298
299         if (response && ri > 0) {
300                 *response = reply;
301         } else {
302                 free(reply);
303         }
304
305         return PAM_SUCCESS;
306 }
307
308 enum pamtest_err _pamtest(const char *service,
309                           const char *user,
310                           struct pamtest_conv_data *conv_data,
311                           struct pam_testcase test_cases[],
312                           size_t num_test_cases)
313 {
314         struct pamtest_conv_ctx cctx = {
315                 .data = conv_data,
316         };
317
318         return _pamtest_conv(service, user,
319                              pamtest_simple_conv,
320                              &cctx,
321                              test_cases,
322                              num_test_cases);
323 }