| 1 | /* |
| 2 | ** Copyright 1998 - 2006 Double Precision, Inc. See COPYING for |
| 3 | ** distribution information. |
| 4 | */ |
| 5 | |
| 6 | #if HAVE_CONFIG_H |
| 7 | #include "courier_auth_config.h" |
| 8 | #endif |
| 9 | #include <stdio.h> |
| 10 | #include <stdlib.h> |
| 11 | #include <string.h> |
| 12 | #include <signal.h> |
| 13 | #include <errno.h> |
| 14 | #if HAVE_UNISTD_H |
| 15 | #include <unistd.h> |
| 16 | #endif |
| 17 | #include "auth.h" |
| 18 | #include "authwait.h" |
| 19 | #include "authstaticlist.h" |
| 20 | #include "courierauthdebug.h" |
| 21 | |
| 22 | #if HAVE_SECURITY_PAM_APPL_H |
| 23 | #include <security/pam_appl.h> |
| 24 | #endif |
| 25 | |
| 26 | #if HAVE_PAM_PAM_APPL_H |
| 27 | #include <Pam/pam_appl.h> |
| 28 | #endif |
| 29 | |
| 30 | static const char rcsid[]="$Id: authpam.c,v 1.24 2006/10/28 19:22:52 mrsam Exp $"; |
| 31 | |
| 32 | static const char *pam_username, *pam_password, *pam_service; |
| 33 | |
| 34 | extern void auth_pwd_enumerate( void(*cb_func)(const char *name, |
| 35 | uid_t uid, |
| 36 | gid_t gid, |
| 37 | const char *homedir, |
| 38 | const char *maildir, |
| 39 | const char *options, |
| 40 | void *void_arg), |
| 41 | void *void_arg); |
| 42 | |
| 43 | static int pam_conv(int num_msg, const struct pam_message **msg, |
| 44 | struct pam_response **resp, void *appdata_ptr) |
| 45 | { |
| 46 | int i = 0; |
| 47 | struct pam_response *repl = NULL; |
| 48 | |
| 49 | repl = malloc(sizeof(struct pam_response) * num_msg); |
| 50 | if (!repl) return PAM_CONV_ERR; |
| 51 | |
| 52 | for (i=0; i<num_msg; i++) |
| 53 | switch (msg[i]->msg_style) { |
| 54 | case PAM_PROMPT_ECHO_ON: |
| 55 | repl[i].resp_retcode = PAM_SUCCESS; |
| 56 | repl[i].resp = strdup(pam_username); |
| 57 | if (!repl[i].resp) |
| 58 | { |
| 59 | perror("strdup"); |
| 60 | exit(1); |
| 61 | } |
| 62 | break; |
| 63 | case PAM_PROMPT_ECHO_OFF: |
| 64 | repl[i].resp_retcode = PAM_SUCCESS; |
| 65 | repl[i].resp = strdup(pam_password); |
| 66 | if (!repl[i].resp) |
| 67 | { |
| 68 | perror("strdup"); |
| 69 | exit(1); |
| 70 | } |
| 71 | break; |
| 72 | case PAM_TEXT_INFO: |
| 73 | case PAM_ERROR_MSG: |
| 74 | if (write(2, msg[i]->msg, strlen(msg[i]->msg)) < 0 || |
| 75 | write(2, "\n", 1) < 0) |
| 76 | ; /* ignore gcc warning */ |
| 77 | |
| 78 | repl[i].resp_retcode = PAM_SUCCESS; |
| 79 | repl[i].resp = NULL; |
| 80 | break; |
| 81 | default: |
| 82 | free (repl); |
| 83 | return PAM_CONV_ERR; |
| 84 | } |
| 85 | |
| 86 | *resp=repl; |
| 87 | return PAM_SUCCESS; |
| 88 | } |
| 89 | |
| 90 | static struct pam_conv conv = { |
| 91 | pam_conv, |
| 92 | NULL |
| 93 | }; |
| 94 | |
| 95 | static int dopam(pam_handle_t **pamh) |
| 96 | { |
| 97 | int retval; |
| 98 | |
| 99 | DPRINTF("pam_service=%s, pam_username=%s", |
| 100 | pam_service ? pam_service : "<null>", |
| 101 | pam_username ? pam_username : "<null>"); |
| 102 | |
| 103 | retval=pam_start(pam_service, pam_username, &conv, pamh); |
| 104 | if (retval != PAM_SUCCESS) |
| 105 | { |
| 106 | DPRINTF("pam_start failed, result %d [Hint: bad PAM configuration?]", retval); |
| 107 | } |
| 108 | |
| 109 | #if 0 |
| 110 | if (retval == PAM_SUCCESS) |
| 111 | { |
| 112 | retval=pam_set_item(*pamh, PAM_AUTHTOK, pam_password); |
| 113 | if (retval != PAM_SUCCESS) |
| 114 | { |
| 115 | DPRINTF("pam_set_item failed, result %d", retval); |
| 116 | } |
| 117 | } |
| 118 | #endif |
| 119 | |
| 120 | if (retval == PAM_SUCCESS) |
| 121 | { |
| 122 | retval=pam_authenticate(*pamh, 0); |
| 123 | if (retval != PAM_SUCCESS) |
| 124 | { |
| 125 | DPRINTF("pam_authenticate failed, result %d", retval); |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | #if 0 |
| 130 | |
| 131 | #if HAVE_PAM_SETCRED |
| 132 | if (retval == PAM_SUCCESS) |
| 133 | { |
| 134 | retval=pam_setcred(*pamh, PAM_ESTABLISH_CRED); |
| 135 | if (retval != PAM_SUCCESS) |
| 136 | { |
| 137 | DPRINTF("pam_setcred failed, result %d", retval); |
| 138 | } |
| 139 | } |
| 140 | #endif |
| 141 | #endif |
| 142 | |
| 143 | if (retval == PAM_SUCCESS) |
| 144 | { |
| 145 | retval=pam_acct_mgmt(*pamh, 0); |
| 146 | if (retval != PAM_SUCCESS) |
| 147 | { |
| 148 | DPRINTF("pam_acct_mgmt failed, result %d", retval); |
| 149 | } |
| 150 | } |
| 151 | if (retval == PAM_SUCCESS) |
| 152 | { |
| 153 | DPRINTF("dopam successful"); |
| 154 | } |
| 155 | return (retval); |
| 156 | } |
| 157 | |
| 158 | struct callback_info { |
| 159 | int (*callback_func)(struct authinfo *, void *); |
| 160 | void *callback_arg; |
| 161 | } ; |
| 162 | |
| 163 | static int callback_pam(struct authinfo *a, void *argptr) |
| 164 | { |
| 165 | struct callback_info *ci=(struct callback_info *)argptr; |
| 166 | pam_handle_t *pamh=NULL; |
| 167 | int pipefd[2]; |
| 168 | int retval; |
| 169 | pid_t p; |
| 170 | int waitstat; |
| 171 | char *s; |
| 172 | char buf[1]; |
| 173 | |
| 174 | |
| 175 | a->clearpasswd=pam_password; |
| 176 | s=strdup(a->sysusername); |
| 177 | if (!s) |
| 178 | { |
| 179 | perror("malloc"); |
| 180 | return (1); |
| 181 | } |
| 182 | |
| 183 | |
| 184 | /* |
| 185 | ** OK, in order to transparently support PAM sessions inside this |
| 186 | ** authentication module, what we need to do is to fork(), and let |
| 187 | ** the child run in its parent place. Once the child process exits, |
| 188 | ** the parent calls pam_end_session, and clears the PAM library. |
| 189 | ** |
| 190 | ** This means that upon return from auth_pam(), your process ID |
| 191 | ** might've changed!!! |
| 192 | ** |
| 193 | ** However, if the authentication fails, we can simply exit, without |
| 194 | ** running as a child process. |
| 195 | ** |
| 196 | ** Additionally, the PAM library might allocate some resources that |
| 197 | ** authenticated clients should not access. Therefore, we fork |
| 198 | ** *before* we try to authenticate. If the authentication succeeds, |
| 199 | ** the child process will run in the parent's place. The child |
| 200 | ** process waits until the parent tells it whether the authentication |
| 201 | ** worked. If it worked, the child keeps running. If not, the child |
| 202 | ** exits, which the parent waits for. |
| 203 | ** |
| 204 | ** The authentication status is communicated to the child process via |
| 205 | ** a pipe. |
| 206 | */ |
| 207 | if (pipe(pipefd) < 0) |
| 208 | { |
| 209 | perror("pipe"); |
| 210 | free(s); |
| 211 | return (1); |
| 212 | } |
| 213 | |
| 214 | if ((p=fork()) == -1) |
| 215 | { |
| 216 | perror("fork"); |
| 217 | free(s); |
| 218 | return (1); |
| 219 | } |
| 220 | |
| 221 | if (p == 0) |
| 222 | { |
| 223 | close(pipefd[0]); |
| 224 | retval=dopam(&pamh); |
| 225 | if (retval == PAM_SUCCESS) |
| 226 | if (write(pipefd[1], "", 1) < 0) |
| 227 | ; /* ignore gcc warning */ |
| 228 | close(pipefd[1]); |
| 229 | _exit(0); |
| 230 | } |
| 231 | |
| 232 | |
| 233 | close(pipefd[1]); |
| 234 | while (wait(&waitstat) != p) |
| 235 | ; |
| 236 | if (read(pipefd[0], buf, 1) > 0) |
| 237 | { |
| 238 | int rc; |
| 239 | |
| 240 | close(pipefd[0]); |
| 241 | a->address=s; |
| 242 | rc=(*ci->callback_func)(a, ci->callback_arg); |
| 243 | free(s); |
| 244 | return rc; |
| 245 | } |
| 246 | close(pipefd[0]); |
| 247 | free(s); |
| 248 | errno=EPERM; |
| 249 | return (-1); |
| 250 | |
| 251 | #if 0 |
| 252 | free(s); |
| 253 | close(pipefd[0]); |
| 254 | |
| 255 | retval=dopam(&pamh); |
| 256 | |
| 257 | if (retval == PAM_SUCCESS) |
| 258 | retval=pam_open_session(pamh, 0); |
| 259 | |
| 260 | if (retval != PAM_SUCCESS) |
| 261 | { |
| 262 | if (pam_end(pamh, retval) != PAM_SUCCESS) |
| 263 | perror("Unable to release PAM tokens"); |
| 264 | |
| 265 | /* Wait for child to terminate */ |
| 266 | |
| 267 | close(pipefd[1]); /* Tell the child to shut down */ |
| 268 | while (wait(&waitstat) != p) |
| 269 | ; |
| 270 | return (-1); |
| 271 | } |
| 272 | |
| 273 | /* Tell child process to run in authenticated state */ |
| 274 | |
| 275 | write(pipefd[1], "", 1); |
| 276 | close(pipefd[1]); |
| 277 | |
| 278 | /* Wait for child process to finish */ |
| 279 | |
| 280 | while (wait(&waitstat) != p) |
| 281 | ; |
| 282 | |
| 283 | retval=pam_close_session(pamh, 0); |
| 284 | if (retval != PAM_SUCCESS) |
| 285 | perror("pam_close_session"); |
| 286 | |
| 287 | if (pam_end(pamh, retval) != PAM_SUCCESS) |
| 288 | perror("Unable to release PAM tokens"); |
| 289 | |
| 290 | if (WIFEXITED(waitstat)) |
| 291 | exit(WEXITSTATUS(waitstat)); |
| 292 | exit(255); |
| 293 | return (1); |
| 294 | #endif |
| 295 | } |
| 296 | |
| 297 | extern int auth_pam_pre(const char *userid, const char *service, |
| 298 | int (*callback)(struct authinfo *, void *), |
| 299 | void *arg); |
| 300 | |
| 301 | int auth_pam(const char *service, const char *type, char *authdata, |
| 302 | int (*callback_func)(struct authinfo *, void *), |
| 303 | void *callback_arg) |
| 304 | { |
| 305 | struct callback_info ci; |
| 306 | |
| 307 | if (strcmp(type, AUTHTYPE_LOGIN)) |
| 308 | { |
| 309 | DPRINTF("authpam only handles authtype=" AUTHTYPE_LOGIN); |
| 310 | errno=EPERM; |
| 311 | return (-1); |
| 312 | } |
| 313 | |
| 314 | if ((pam_username=strtok(authdata, "\n")) == 0 || |
| 315 | (pam_password=strtok(0, "\n")) == 0) |
| 316 | { |
| 317 | DPRINTF("incomplete username or missing password"); |
| 318 | errno=EPERM; |
| 319 | return (-1); |
| 320 | } |
| 321 | |
| 322 | pam_service=service; |
| 323 | |
| 324 | ci.callback_func=callback_func; |
| 325 | ci.callback_arg=callback_arg; |
| 326 | return auth_pam_pre(pam_username, service, &callback_pam, &ci); |
| 327 | } |
| 328 | |
| 329 | static void auth_pam_cleanup() |
| 330 | { |
| 331 | } |
| 332 | |
| 333 | static struct authstaticinfo authpam_info={ |
| 334 | "authpam", |
| 335 | auth_pam, |
| 336 | auth_pam_pre, |
| 337 | auth_pam_cleanup, |
| 338 | auth_syspasswd, |
| 339 | auth_pam_cleanup, |
| 340 | auth_pwd_enumerate}; |
| 341 | |
| 342 | |
| 343 | struct authstaticinfo *courier_authpam_init() |
| 344 | { |
| 345 | return &authpam_info; |
| 346 | } |