1/*
2 * Copyright (c) 1995 - 2001 Kungliga Tekniska H�gskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#ifdef HAVE_CONFIG_H
35#include<config.h>
36RCSID("$Id: pam.c 11417 2002-09-09 15:57:24Z joda $");
37#endif
38
39#include <stdio.h>
40#include <stdlib.h>
41#include <string.h>
42#include <pwd.h>
43#include <unistd.h>
44#include <sys/types.h>
45#include <syslog.h>
46
47#include <security/pam_appl.h>
48#include <security/pam_modules.h>
49#ifndef PAM_AUTHTOK_RECOVERY_ERR /* Fix linsux typo. */
50#define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR
51#endif
52
53#include <netinet/in.h>
54#include <krb.h>
55#include <kafs.h>
56
57#if 0
58/* Debugging PAM modules is a royal pain, truss helps. */
59#define DEBUG(msg) (access(msg " at line", __LINE__))
60#endif
61
62static void
63psyslog(int level, const char *format, ...)
64{
65  va_list args;
66  va_start(args, format);
67  openlog("pam_krb4", LOG_PID, LOG_AUTH);
68  vsyslog(level, format, args);
69  va_end(args);
70  closelog();
71}
72
73enum {
74  KRB4_DEBUG,
75  KRB4_USE_FIRST_PASS,
76  KRB4_TRY_FIRST_PASS,
77  KRB4_IGNORE_ROOT,
78  KRB4_NO_VERIFY,
79  KRB4_REAFSLOG,
80  KRB4_CTRLS			/* Number of ctrl arguments defined. */
81};
82
83#define KRB4_DEFAULTS  0
84
85static int ctrl_flags = KRB4_DEFAULTS;
86#define ctrl_on(x)  (krb4_args[x].flag & ctrl_flags)
87#define ctrl_off(x) (!ctrl_on(x))
88
89typedef struct
90{
91  const char *token;
92  unsigned int flag;
93} krb4_ctrls_t;
94
95static krb4_ctrls_t krb4_args[KRB4_CTRLS] =
96{
97  /* KRB4_DEBUG          */ { "debug",          0x01 },
98  /* KRB4_USE_FIRST_PASS */ { "use_first_pass", 0x02 },
99  /* KRB4_TRY_FIRST_PASS */ { "try_first_pass", 0x04 },
100  /* KRB4_IGNORE_ROOT    */ { "ignore_root",    0x08 },
101  /* KRB4_NO_VERIFY      */ { "no_verify",      0x10 },
102  /* KRB4_REAFSLOG       */ { "reafslog",       0x20 },
103};
104
105static void
106parse_ctrl(int argc, const char **argv)
107{
108  int i, j;
109
110  ctrl_flags = KRB4_DEFAULTS;
111  for (i = 0; i < argc; i++)
112    {
113      for (j = 0; j < KRB4_CTRLS; j++)
114	if (strcmp(argv[i], krb4_args[j].token) == 0)
115	  break;
116
117      if (j >= KRB4_CTRLS)
118	psyslog(LOG_ALERT, "unrecognized option [%s]", *argv);
119      else
120	ctrl_flags |= krb4_args[j].flag;
121    }
122}
123
124static void
125pdeb(const char *format, ...)
126{
127  va_list args;
128  if (ctrl_off(KRB4_DEBUG))
129    return;
130  va_start(args, format);
131  openlog("pam_krb4", LOG_PID, LOG_AUTH);
132  vsyslog(LOG_DEBUG, format, args);
133  va_end(args);
134  closelog();
135}
136
137#define ENTRY(func) pdeb("%s() flags = %d ruid = %d euid = %d", func, flags, getuid(), geteuid())
138
139static void
140set_tkt_string(uid_t uid)
141{
142  char buf[128];
143
144  snprintf(buf, sizeof(buf), "%s%u", TKT_ROOT, (unsigned)uid);
145  krb_set_tkt_string(buf);
146
147#if 0
148  /* pam_set_data+pam_get_data are not guaranteed to work, grr. */
149  pam_set_data(pamh, "KRBTKFILE", strdup(t), cleanup);
150  if (pam_get_data(pamh, "KRBTKFILE", (const void**)&tkt) == PAM_SUCCESS)
151    {
152      pam_putenv(pamh, var);
153    }
154#endif
155
156  /* We don't want to inherit this variable.
157   * If we still do, it must have a sane value. */
158  if (getenv("KRBTKFILE") != 0)
159    {
160      char *var = malloc(sizeof(buf));
161      snprintf(var, sizeof(buf), "KRBTKFILE=%s", tkt_string());
162      putenv(var);
163      /* free(var); XXX */
164    }
165}
166
167static int
168verify_pass(pam_handle_t *pamh,
169	    const char *name,
170	    const char *inst,
171	    const char *pass)
172{
173  char realm[REALM_SZ];
174  int ret, krb_verify, old_euid, old_ruid;
175
176  krb_get_lrealm(realm, 1);
177  if (ctrl_on(KRB4_NO_VERIFY))
178    krb_verify = KRB_VERIFY_SECURE_FAIL;
179  else
180    krb_verify = KRB_VERIFY_SECURE;
181  old_ruid = getuid();
182  old_euid = geteuid();
183  setreuid(0, 0);
184  ret = krb_verify_user(name, inst, realm, pass, krb_verify, NULL);
185  pdeb("krb_verify_user(`%s', `%s', `%s', pw, %d, NULL) returns %s",
186       name, inst, realm, krb_verify,
187       krb_get_err_text(ret));
188  setreuid(old_ruid, old_euid);
189  if (getuid() != old_ruid || geteuid() != old_euid)
190    {
191      psyslog(LOG_ALERT , "setreuid(%d, %d) failed at line %d",
192	      old_ruid, old_euid, __LINE__);
193      exit(1);
194    }
195
196  switch(ret) {
197  case KSUCCESS:
198    return PAM_SUCCESS;
199  case KDC_PR_UNKNOWN:
200    return PAM_USER_UNKNOWN;
201  case SKDC_CANT:
202  case SKDC_RETRY:
203  case RD_AP_TIME:
204    return PAM_AUTHINFO_UNAVAIL;
205  default:
206    return PAM_AUTH_ERR;
207  }
208}
209
210static int
211krb4_auth(pam_handle_t *pamh,
212	  int flags,
213	  const char *name,
214	  const char *inst,
215	  struct pam_conv *conv)
216{
217  struct pam_response *resp;
218  char prompt[128];
219  struct pam_message msg, *pmsg = &msg;
220  int ret;
221
222  if (ctrl_on(KRB4_TRY_FIRST_PASS) || ctrl_on(KRB4_USE_FIRST_PASS))
223    {
224      char *pass = 0;
225      ret = pam_get_item(pamh, PAM_AUTHTOK, (void **) &pass);
226      if (ret != PAM_SUCCESS)
227        {
228          psyslog(LOG_ERR , "pam_get_item returned error to get-password");
229          return ret;
230        }
231      else if (pass != 0 && verify_pass(pamh, name, inst, pass) == PAM_SUCCESS)
232	return PAM_SUCCESS;
233      else if (ctrl_on(KRB4_USE_FIRST_PASS))
234	return PAM_AUTHTOK_RECOVERY_ERR;       /* Wrong password! */
235      else
236	/* We tried the first password but it didn't work, cont. */;
237    }
238
239  msg.msg_style = PAM_PROMPT_ECHO_OFF;
240  if (*inst == 0)
241    snprintf(prompt, sizeof(prompt), "%s's Password: ", name);
242  else
243    snprintf(prompt, sizeof(prompt), "%s.%s's Password: ", name, inst);
244  msg.msg = prompt;
245
246  ret = conv->conv(1, &pmsg, &resp, conv->appdata_ptr);
247  if (ret != PAM_SUCCESS)
248    return ret;
249
250  ret = verify_pass(pamh, name, inst, resp->resp);
251  if (ret == PAM_SUCCESS)
252    {
253      memset(resp->resp, 0, strlen(resp->resp)); /* Erase password! */
254      free(resp->resp);
255      free(resp);
256    }
257  else
258    {
259      pam_set_item(pamh, PAM_AUTHTOK, resp->resp); /* Save password. */
260      /* free(resp->resp); XXX */
261      /* free(resp); XXX */
262    }
263
264  return ret;
265}
266
267int
268pam_sm_authenticate(pam_handle_t *pamh,
269		    int flags,
270		    int argc,
271		    const char **argv)
272{
273  char *user;
274  int ret;
275  struct pam_conv *conv;
276  struct passwd *pw;
277  uid_t uid = -1;
278  const char *name, *inst;
279  char realm[REALM_SZ];
280  realm[0] = 0;
281
282  parse_ctrl(argc, argv);
283  ENTRY("pam_sm_authenticate");
284
285  ret = pam_get_user(pamh, &user, "login: ");
286  if (ret != PAM_SUCCESS)
287    return ret;
288
289  if (ctrl_on(KRB4_IGNORE_ROOT) && strcmp(user, "root") == 0)
290    return PAM_AUTHINFO_UNAVAIL;
291
292  ret = pam_get_item(pamh, PAM_CONV, (void*)&conv);
293  if (ret != PAM_SUCCESS)
294    return ret;
295
296  pw = getpwnam(user);
297  if (pw != 0)
298    {
299      uid = pw->pw_uid;
300      set_tkt_string(uid);
301    }
302
303  if (strcmp(user, "root") == 0 && getuid() != 0)
304    {
305      pw = getpwuid(getuid());
306      if (pw != 0)
307	{
308	  name = strdup(pw->pw_name);
309	  inst = "root";
310	}
311    }
312  else
313    {
314      name = user;
315      inst = "";
316    }
317
318  ret = krb4_auth(pamh, flags, name, inst, conv);
319
320  /*
321   * The realm was lost inside krb_verify_user() so we can't simply do
322   * a krb_kuserok() when inst != "".
323   */
324  if (ret == PAM_SUCCESS && inst[0] != 0)
325    {
326      uid_t old_euid = geteuid();
327      uid_t old_ruid = getuid();
328
329      setreuid(0, 0);		/* To read ticket file. */
330      if (krb_get_tf_fullname(tkt_string(), 0, 0, realm) != KSUCCESS)
331	ret = PAM_SERVICE_ERR;
332      else if (krb_kuserok(name, inst, realm, user) != KSUCCESS)
333	{
334	  setreuid(0, uid);	/*  To read ~/.klogin. */
335	  if (krb_kuserok(name, inst, realm, user) != KSUCCESS)
336	    ret = PAM_PERM_DENIED;
337	}
338
339      if (ret != PAM_SUCCESS)
340	{
341	  dest_tkt();		/* Passwd known, ok to kill ticket. */
342	  psyslog(LOG_NOTICE,
343		  "%s.%s@%s is not allowed to log in as %s",
344		  name, inst, realm, user);
345	}
346
347      setreuid(old_ruid, old_euid);
348      if (getuid() != old_ruid || geteuid() != old_euid)
349	{
350	  psyslog(LOG_ALERT , "setreuid(%d, %d) failed at line %d",
351		  old_ruid, old_euid, __LINE__);
352	  exit(1);
353	}
354    }
355
356  if (ret == PAM_SUCCESS)
357    {
358      psyslog(LOG_INFO,
359	      "%s.%s@%s authenticated as user %s",
360	      name, inst, realm, user);
361      if (chown(tkt_string(), uid, -1) == -1)
362	{
363	  dest_tkt();
364	  psyslog(LOG_ALERT , "chown(%s, %d, -1) failed", tkt_string(), uid);
365	  exit(1);
366	}
367    }
368
369  /*
370   * Kludge alert!!! Sun dtlogin unlock screen fails to call
371   * pam_setcred(3) with PAM_REFRESH_CRED after a successful
372   * authentication attempt, sic.
373   *
374   * This hack is designed as a workaround to that problem.
375   */
376  if (ctrl_on(KRB4_REAFSLOG))
377    if (ret == PAM_SUCCESS)
378      pam_sm_setcred(pamh, PAM_REFRESH_CRED, argc, argv);
379
380  return ret;
381}
382
383int
384pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
385{
386  parse_ctrl(argc, argv);
387  ENTRY("pam_sm_setcred");
388
389  switch (flags & ~PAM_SILENT) {
390  case 0:
391  case PAM_ESTABLISH_CRED:
392    if (k_hasafs())
393      k_setpag();
394    /* Fall through, fill PAG with credentials below. */
395  case PAM_REINITIALIZE_CRED:
396  case PAM_REFRESH_CRED:
397    if (k_hasafs())
398      {
399	void *user = 0;
400
401	if (pam_get_item(pamh, PAM_USER, &user) == PAM_SUCCESS)
402	  {
403	    struct passwd *pw = getpwnam((char *)user);
404	    if (pw != 0)
405	      krb_afslog_uid_home(/*cell*/ 0,/*realm_hint*/ 0,
406				  pw->pw_uid, pw->pw_dir);
407	  }
408      }
409    break;
410  case PAM_DELETE_CRED:
411    dest_tkt();
412    if (k_hasafs())
413      k_unlog();
414    break;
415  default:
416    psyslog(LOG_ALERT , "pam_sm_setcred: unknown flags 0x%x", flags);
417    break;
418  }
419
420  return PAM_SUCCESS;
421}
422
423int
424pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
425{
426  parse_ctrl(argc, argv);
427  ENTRY("pam_sm_open_session");
428
429  return PAM_SUCCESS;
430}
431
432
433int
434pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char**argv)
435{
436  parse_ctrl(argc, argv);
437  ENTRY("pam_sm_close_session");
438
439  /* This isn't really kosher, but it's handy. */
440  pam_sm_setcred(pamh, PAM_DELETE_CRED, argc, argv);
441
442  return PAM_SUCCESS;
443}
444