1 /* $Id: ckpasswd.c 7565 2006-08-28 02:42:54Z eagle $
3 ** The default username/password authenticator.
5 ** This program is intended to be run by nnrpd and handle usernames and
6 ** passwords. It can authenticate against a regular flat file (the type
7 ** managed by htpasswd), a DBM file, the system password file or shadow file,
14 #include "inn/messages.h"
16 #include "inn/vector.h"
28 #if defined(HAVE_DBM) || defined(HAVE_BDB_DBM)
32 # define DB_DBM_HSEARCH 1
34 # elif HAVE_GDBM_NDBM_H
35 # include <gdbm-ndbm.h>
36 # elif HAVE_DB1_NDBM_H
37 # include <db1/ndbm.h>
46 # define OPT_SHADOW "s"
48 # define OPT_SHADOW ""
52 # if HAVE_PAM_PAM_APPL_H
53 # include <pam/pam_appl.h>
55 # include <security/pam_appl.h>
61 ** The PAM conversation function.
63 ** Since we already have all the information and can't ask the user
64 ** questions, we can't quite follow the real PAM protocol. Instead, we just
65 ** return the password in response to every question that PAM asks. There
66 ** appears to be no generic way to determine whether the message in question
67 ** is indeed asking for the password....
69 ** This function allocates an array of struct pam_response to return to the
70 ** PAM libraries that's never freed. For this program, this isn't much of an
71 ** issue, since it will likely only be called once and then the program will
72 ** exit. This function uses malloc and strdup instead of xmalloc and xstrdup
73 ** intentionally so that the PAM conversation will be closed cleanly if we
74 ** run out of memory rather than simply terminated.
76 ** appdata_ptr contains the password we were given.
80 pass_conv(int num_msg, const struct pam_message **msgm UNUSED,
81 struct pam_response **response, void *appdata_ptr)
85 *response = malloc(num_msg * sizeof(struct pam_response));
86 if (*response == NULL)
88 for (i = 0; i < num_msg; i++) {
89 (*response)[i].resp = strdup((char *)appdata_ptr);
90 (*response)[i].resp_retcode = 0;
98 ** Authenticate a user via PAM.
100 ** Attempts to authenticate a user with PAM, returning true if the user
101 ** successfully authenticates and false otherwise. Note that this function
102 ** doesn't attempt to handle any remapping of the authenticated user by the
103 ** PAM stack, but just assumes that the authenticated user was the same as
104 ** the username given.
106 ** Right now, all failures are handled via die. This may be worth revisiting
107 ** in case we want to try other authentication methods if this fails for a
108 ** reason other than the system not having PAM support.
112 auth_pam(char *username UNUSED, char *password UNUSED)
118 auth_pam(const char *username, char *password)
121 struct pam_conv conv;
124 conv.conv = pass_conv;
125 conv.appdata_ptr = password;
126 status = pam_start("nnrpd", username, &conv, &pamh);
127 if (status != PAM_SUCCESS)
128 die("pam_start failed: %s", pam_strerror(pamh, status));
129 status = pam_authenticate(pamh, PAM_SILENT);
130 if (status != PAM_SUCCESS)
131 die("pam_authenticate failed: %s", pam_strerror(pamh, status));
132 status = pam_acct_mgmt(pamh, PAM_SILENT);
133 if (status != PAM_SUCCESS)
134 die("pam_acct_mgmt failed: %s", pam_strerror(pamh, status));
135 status = pam_end(pamh, status);
136 if (status != PAM_SUCCESS)
137 die("pam_end failed: %s", pam_strerror(pamh, status));
139 /* If we get to here, the user successfully authenticated. */
142 #endif /* HAVE_PAM */
146 ** Try to get a password out of a dbm file. The dbm file should have the
147 ** username for the key and the crypted password as the value. The crypted
148 ** password, if found, is returned as a newly allocated string; otherwise,
151 #if !(defined(HAVE_DBM) || defined(HAVE_BDB_DBM))
153 password_dbm(char *user UNUSED, const char *file UNUSED)
159 password_dbm(char *name, const char *file)
165 database = dbm_open(file, O_RDONLY, 0600);
166 if (database == NULL)
169 key.dsize = strlen(name);
170 value = dbm_fetch(database, key);
171 if (value.dptr == NULL) {
175 password = xmalloc(value.dsize + 1);
176 strlcpy(password, value.dptr, value.dsize + 1);
180 #endif /* HAVE_DBM || HAVE_BDB_DBM */
184 ** Try to get a password out of the system /etc/shadow file. The crypted
185 ** password, if found, is returned as a newly allocated string; otherwise,
190 password_shadow(const char *user UNUSED)
196 password_shadow(const char *user)
200 spwd = getspnam(user);
202 return xstrdup(spwd->sp_pwdp);
205 #endif /* HAVE_GETSPNAM */
209 ** Try to get a password out of a file. The crypted password, if found, is
210 ** returned as a newly allocated string; otherwise, NULL is returned.
213 password_file(const char *username, const char *file)
216 char *line, *password;
217 struct cvector *info = NULL;
222 for (line = QIOread(qp); line != NULL; line = QIOread(qp)) {
223 if (*line == '#' || *line == '\n')
225 info = cvector_split(line, ':', info);
226 if (info->count < 2 || strcmp(info->strings[0], username) != 0)
228 password = xstrdup(info->strings[1]);
234 die("line too long in %s", file);
236 sysdie("error reading %s", file);
244 ** Try to get a password out of the system password file. The crypted
245 ** password, if found, is returned as a newly allocated string; otherwise,
249 password_system(const char *username)
253 pwd = getpwnam(username);
255 return xstrdup(pwd->pw_passwd);
261 ** Try to get the name of a user's primary group out of the system group
262 ** file. The group, if found, is returned as a newly allocated string;
263 ** otherwise, NULL is returned. If the username is not found, NULL is
267 group_system(const char *username)
272 pwd = getpwnam(username);
275 gr = getgrgid(pwd->pw_gid);
278 return xstrdup(gr->gr_name);
283 ** Output username (and group, if desired) in correct return format.
286 output_user(const char *username, bool wantgroup)
289 char *group = group_system(username);
291 die("group info for user %s not available", username);
292 printf("User:%s@%s\n", username, group);
295 printf("User:%s\n", username);
302 ** We handle the variences between systems with #if blocks above, so that
303 ** this code can look fairly clean.
306 main(int argc, char *argv[])
308 enum authtype { AUTH_NONE, AUTH_SHADOW, AUTH_FILE, AUTH_DBM };
311 enum authtype type = AUTH_NONE;
312 bool wantgroup = false;
313 const char *filename = NULL;
314 struct auth_info *authinfo = NULL;
315 char *password = NULL;
317 message_program_name = "ckpasswd";
319 while ((opt = getopt(argc, argv, "gf:u:p:" OPT_DBM OPT_SHADOW)) != -1) {
322 if (type == AUTH_DBM || type == AUTH_FILE)
323 die("-g option is incompatible with -d or -f");
327 if (type != AUTH_NONE)
328 die("only one of -s, -f, or -d allowed");
330 die("-g option is incompatible with -d or -f");
335 if (type != AUTH_NONE)
336 die("only one of -s, -f, or -d allowed");
338 die("-g option is incompatible with -d or -f");
343 if (type != AUTH_NONE)
344 die("only one of -s, -f, or -d allowed");
348 if (authinfo == NULL) {
349 authinfo = xmalloc(sizeof(struct auth_info));
350 authinfo->password = NULL;
352 authinfo->username = optarg;
355 if (authinfo == NULL) {
356 authinfo = xmalloc(sizeof(struct auth_info));
357 authinfo->username = NULL;
359 authinfo->password = optarg;
366 die("extra arguments given");
367 if (authinfo != NULL && authinfo->username == NULL)
368 die("-u option is required if -p option is given");
369 if (authinfo != NULL && authinfo->password == NULL)
370 die("-p option is required if -u option is given");
372 /* Unless a username or password was given on the command line, assume
373 we're being run by nnrpd. */
374 if (authinfo == NULL)
375 authinfo = get_auth_info(stdin);
376 if (authinfo == NULL)
377 die("no authentication information from nnrpd");
378 if (authinfo->username[0] == '\0')
379 die("null username");
381 /* Run the appropriate authentication routines. */
384 password = password_shadow(authinfo->username);
385 if (password == NULL)
386 password = password_system(authinfo->username);
389 password = password_file(authinfo->username, filename);
392 password = password_dbm(authinfo->username, filename);
395 if (auth_pam(authinfo->username, authinfo->password)) {
396 output_user(authinfo->username, wantgroup);
399 password = password_system(authinfo->username);
403 if (password == NULL)
404 die("user %s unknown", authinfo->username);
405 if (strcmp(password, crypt(authinfo->password, password)) != 0)
406 die("invalid password for user %s", authinfo->username);
408 /* The password matched. */
409 output_user(authinfo->username, wantgroup);