chiark / gitweb /
debugging for thing that crashed
[innduct.git] / authprogs / ckpasswd.c
1 /*  $Id: ckpasswd.c 7565 2006-08-28 02:42:54Z eagle $
2 **
3 **  The default username/password authenticator.
4 **
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,
8 **  or PAM.
9 */
10
11 #include "config.h"
12 #include "clibrary.h"
13
14 #include "inn/messages.h"
15 #include "inn/qio.h"
16 #include "inn/vector.h"
17 #include "libinn.h"
18
19 #include "libauth.h"
20
21 #if HAVE_CRYPT_H
22 # include <crypt.h>
23 #endif
24 #include <fcntl.h>
25 #include <pwd.h>
26 #include <grp.h>
27
28 #if defined(HAVE_DBM) || defined(HAVE_BDB_DBM)
29 # if HAVE_NDBM_H
30 #  include <ndbm.h>
31 # elif HAVE_BDB_DBM
32 #  define DB_DBM_HSEARCH 1
33 #  include <db.h>
34 # elif HAVE_GDBM_NDBM_H
35 #  include <gdbm-ndbm.h>
36 # elif HAVE_DB1_NDBM_H
37 #  include <db1/ndbm.h>
38 # endif
39 # define OPT_DBM "d:"
40 #else
41 # define OPT_DBM ""
42 #endif
43
44 #if HAVE_GETSPNAM
45 # include <shadow.h>
46 # define OPT_SHADOW "s"
47 #else
48 # define OPT_SHADOW ""
49 #endif
50
51 #if HAVE_PAM
52 # if HAVE_PAM_PAM_APPL_H
53 #  include <pam/pam_appl.h>
54 # else
55 #  include <security/pam_appl.h>
56 # endif
57 #endif
58
59
60 /*
61 **  The PAM conversation function.
62 **
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....
68 **
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.
75 **
76 **  appdata_ptr contains the password we were given.
77 */
78 #if HAVE_PAM
79 static int
80 pass_conv(int num_msg, const struct pam_message **msgm UNUSED,
81           struct pam_response **response, void *appdata_ptr)
82 {
83     int i;
84
85     *response = malloc(num_msg * sizeof(struct pam_response));
86     if (*response == NULL)
87         return PAM_CONV_ERR;
88     for (i = 0; i < num_msg; i++) {
89         (*response)[i].resp = strdup((char *)appdata_ptr);
90         (*response)[i].resp_retcode = 0;
91     }
92     return PAM_SUCCESS;
93 }
94 #endif /* HAVE_PAM */
95
96
97 /*
98 **  Authenticate a user via PAM.
99 **
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.
105 **
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.
109 */
110 #if !HAVE_PAM
111 static bool
112 auth_pam(char *username UNUSED, char *password UNUSED)
113 {
114     return false;
115 }
116 #else
117 static bool
118 auth_pam(const char *username, char *password)
119 {
120     pam_handle_t *pamh;
121     struct pam_conv conv;
122     int status;
123
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));
138
139     /* If we get to here, the user successfully authenticated. */
140     return true;
141 }
142 #endif /* HAVE_PAM */
143
144
145 /*
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,
149 **  NULL is returned.
150 */
151 #if !(defined(HAVE_DBM) || defined(HAVE_BDB_DBM))
152 static char *
153 password_dbm(char *user UNUSED, const char *file UNUSED)
154 {
155     return NULL;
156 }
157 #else
158 static char *
159 password_dbm(char *name, const char *file)
160 {
161     datum key, value;
162     DBM *database;
163     char *password;
164
165     database = dbm_open(file, O_RDONLY, 0600);
166     if (database == NULL)
167         return NULL;
168     key.dptr = name;
169     key.dsize = strlen(name);
170     value = dbm_fetch(database, key);
171     if (value.dptr == NULL) {
172         dbm_close(database);
173         return NULL;
174     }
175     password = xmalloc(value.dsize + 1);
176     strlcpy(password, value.dptr, value.dsize + 1);
177     dbm_close(database);
178     return password;
179 }
180 #endif /* HAVE_DBM || HAVE_BDB_DBM */
181
182
183 /*
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,
186 **  NULL is returned.
187 */
188 #if !HAVE_GETSPNAM
189 static char *
190 password_shadow(const char *user UNUSED)
191 {
192     return NULL;
193 }
194 #else
195 static char *
196 password_shadow(const char *user)
197 {
198     struct spwd *spwd;
199
200     spwd = getspnam(user);
201     if (spwd != NULL)
202         return xstrdup(spwd->sp_pwdp);
203     return NULL;
204 }
205 #endif /* HAVE_GETSPNAM */
206
207
208 /*
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.
211 */
212 static char *
213 password_file(const char *username, const char *file)
214 {
215     QIOSTATE *qp;
216     char *line, *password;
217     struct cvector *info = NULL;
218
219     qp = QIOopen(file);
220     if (qp == NULL)
221         return NULL;
222     for (line = QIOread(qp); line != NULL; line = QIOread(qp)) {
223         if (*line == '#' || *line == '\n')
224             continue;
225         info = cvector_split(line, ':', info);
226         if (info->count < 2 || strcmp(info->strings[0], username) != 0)
227             continue;
228         password = xstrdup(info->strings[1]);
229         QIOclose(qp);
230         cvector_free(info);
231         return password;
232     }
233     if (QIOtoolong(qp))
234         die("line too long in %s", file);
235     if (QIOerror(qp))
236         sysdie("error reading %s", file);
237     QIOclose(qp);
238     cvector_free(info);
239     return NULL;
240 }
241
242
243 /*
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,
246 **  NULL is returned.
247 */
248 static char *
249 password_system(const char *username)
250 {
251     struct passwd *pwd;
252
253     pwd = getpwnam(username);
254     if (pwd != NULL)
255         return xstrdup(pwd->pw_passwd);
256     return NULL;
257 }
258
259
260 /*
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
264 **  returned.
265 */
266 static char *
267 group_system(const char *username)
268 {
269     struct passwd *pwd;
270     struct group *gr;
271
272     pwd = getpwnam(username);
273     if (pwd == NULL)
274         return NULL;
275     gr = getgrgid(pwd->pw_gid);
276     if (gr == NULL)
277         return NULL;
278     return xstrdup(gr->gr_name);
279 }
280
281
282 /*
283 **  Output username (and group, if desired) in correct return format.
284 */
285 static void
286 output_user(const char *username, bool wantgroup)
287 {
288     if (wantgroup) {
289         char *group = group_system(username);
290         if (group == NULL)
291             die("group info for user %s not available", username);
292         printf("User:%s@%s\n", username, group);
293     }
294     else
295         printf("User:%s\n", username);
296 }
297
298
299 /*
300 **  Main routine.
301 **
302 **  We handle the variences between systems with #if blocks above, so that
303 **  this code can look fairly clean.
304 */
305 int
306 main(int argc, char *argv[])
307 {
308     enum authtype { AUTH_NONE, AUTH_SHADOW, AUTH_FILE, AUTH_DBM };
309
310     int opt;
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;
316
317     message_program_name = "ckpasswd";
318
319     while ((opt = getopt(argc, argv, "gf:u:p:" OPT_DBM OPT_SHADOW)) != -1) {
320         switch (opt) {
321         case 'g':
322             if (type == AUTH_DBM || type == AUTH_FILE)
323                 die("-g option is incompatible with -d or -f");
324             wantgroup = true;
325             break;
326         case 'd':
327             if (type != AUTH_NONE)
328                 die("only one of -s, -f, or -d allowed");
329             if (wantgroup)
330                 die("-g option is incompatible with -d or -f");
331             type = AUTH_DBM;
332             filename = optarg;
333             break;
334         case 'f':
335             if (type != AUTH_NONE)
336                 die("only one of -s, -f, or -d allowed");
337             if (wantgroup)
338                 die("-g option is incompatible with -d or -f");
339             type = AUTH_FILE;
340             filename = optarg;
341             break;
342         case 's':
343             if (type != AUTH_NONE)
344                 die("only one of -s, -f, or -d allowed");
345             type = AUTH_SHADOW;
346             break;
347         case 'u':
348             if (authinfo == NULL) {
349                 authinfo = xmalloc(sizeof(struct auth_info));
350                 authinfo->password = NULL;
351             }
352             authinfo->username = optarg;
353             break;
354         case 'p':
355             if (authinfo == NULL) {
356                 authinfo = xmalloc(sizeof(struct auth_info));
357                 authinfo->username = NULL;
358             }
359             authinfo->password = optarg;
360             break;
361         default:
362             exit(1);
363         }
364     }
365     if (argc != optind)
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");
371
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");
380
381     /* Run the appropriate authentication routines. */
382     switch (type) {
383     case AUTH_SHADOW:
384         password = password_shadow(authinfo->username);
385         if (password == NULL)
386             password = password_system(authinfo->username);
387         break;
388     case AUTH_FILE:
389         password = password_file(authinfo->username, filename);
390         break;
391     case AUTH_DBM:
392         password = password_dbm(authinfo->username, filename);
393         break;
394     case AUTH_NONE:
395         if (auth_pam(authinfo->username, authinfo->password)) {
396             output_user(authinfo->username, wantgroup);
397             exit(0);
398         }
399         password = password_system(authinfo->username);
400         break;
401     }
402
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);
407
408     /* The password matched. */
409     output_user(authinfo->username, wantgroup);
410     exit(0);
411 }