1 /*
2 ** The default username/password authenticator.
3 **
4 ** This program is intended to be run by nnrpd and handle usernames and
5 ** passwords. It can authenticate against a regular flat file (the type
6 ** managed by htpasswd), a DBM file, the system password file or shadow file,
7 ** or PAM.
8 */
9
10 #include "portable/system.h"
11
12 #include "inn/libinn.h"
13 #include "inn/messages.h"
14 #include "inn/qio.h"
15 #include "inn/vector.h"
16
17 #include "libauth.h"
18
19 #if HAVE_CRYPT_H
20 # include <crypt.h>
21 #endif
22
23 #include <fcntl.h>
24 #include <grp.h>
25 #include <pwd.h>
26
27
28 /*
29 ** If compiling with Berkeley DB, use its ndbm compatibility layer
30 ** in preference to other libraries.
31 */
32 #if defined(HAVE_DBM) || defined(HAVE_BDB_NDBM)
33 # if HAVE_BDB_NDBM
34 # define DB_DBM_HSEARCH 1
35 # include <db.h>
36 # elif HAVE_NDBM_H
37 # include <ndbm.h>
38 # elif HAVE_DB1_NDBM_H
39 # include <db1/ndbm.h>
40 # elif HAVE_GDBM_SLASH_NDBM_H
41 # include <gdbm/ndbm.h>
42 # elif HAVE_GDBM_HYPHEN_NDBM_H
43 # include <gdbm-ndbm.h>
44 # endif
45 # define OPT_DBM "d:"
46 #else
47 # define OPT_DBM ""
48 #endif
49
50 #if HAVE_GETSPNAM
51 # include <shadow.h>
52 # define OPT_SHADOW "s"
53 #else
54 # define OPT_SHADOW ""
55 #endif
56
57 #if HAVE_PAM
58 # if HAVE_SECURITY_PAM_APPL_H
59 # include <security/pam_appl.h>
60 # else
61 # include <pam/pam_appl.h>
62 # endif
63 #endif
64
65
66 /*
67 ** The PAM conversation function.
68 **
69 ** Since we already have all the information and can't ask the user
70 ** questions, we can't quite follow the real PAM protocol. Instead, we just
71 ** return the password in response to every question that PAM asks. There
72 ** appears to be no generic way to determine whether the message in question
73 ** is indeed asking for the password....
74 **
75 ** This function allocates an array of struct pam_response to return to the
76 ** PAM libraries that's never freed. For this program, this isn't much of an
77 ** issue, since it will likely only be called once and then the program will
78 ** exit. This function uses malloc and strdup instead of xmalloc and xstrdup
79 ** intentionally so that the PAM conversation will be closed cleanly if we
80 ** run out of memory rather than simply terminated.
81 **
82 ** appdata_ptr contains the password we were given.
83 */
84 #if HAVE_PAM
85 static int
pass_conv(int num_msg,PAM_CONST struct pam_message ** msgm UNUSED,struct pam_response ** response,void * appdata_ptr)86 pass_conv(int num_msg, PAM_CONST struct pam_message **msgm UNUSED,
87 struct pam_response **response, void *appdata_ptr)
88 {
89 int i;
90
91 *response = malloc(num_msg * sizeof(struct pam_response));
92 if (*response == NULL)
93 return PAM_CONV_ERR;
94 for (i = 0; i < num_msg; i++) {
95 (*response)[i].resp = strdup((char *) appdata_ptr);
96 (*response)[i].resp_retcode = 0;
97 }
98 return PAM_SUCCESS;
99 }
100 #endif /* HAVE_PAM */
101
102
103 /*
104 ** Authenticate a user via PAM.
105 **
106 ** Attempts to authenticate a user with PAM, returning true if the user
107 ** successfully authenticates and false otherwise. Note that this function
108 ** doesn't attempt to handle any remapping of the authenticated user by the
109 ** PAM stack, but just assumes that the authenticated user was the same as
110 ** the username given.
111 **
112 ** Right now, all failures are handled via die. This may be worth revisiting
113 ** in case we want to try other authentication methods if this fails for a
114 ** reason other than the system not having PAM support.
115 */
116 #if !HAVE_PAM
117 static bool
auth_pam(char * username UNUSED,char * password UNUSED)118 auth_pam(char *username UNUSED, char *password UNUSED)
119 {
120 return false;
121 }
122 #else
123 static bool
auth_pam(const char * username,char * password)124 auth_pam(const char *username, char *password)
125 {
126 pam_handle_t *pamh;
127 struct pam_conv conv;
128 int status;
129
130 conv.conv = pass_conv;
131 conv.appdata_ptr = password;
132 status = pam_start("nnrpd", username, &conv, &pamh);
133 if (status != PAM_SUCCESS)
134 die("pam_start failed: %s", pam_strerror(pamh, status));
135 status = pam_authenticate(pamh, PAM_SILENT);
136 if (status != PAM_SUCCESS)
137 die("pam_authenticate failed: %s", pam_strerror(pamh, status));
138 status = pam_acct_mgmt(pamh, PAM_SILENT);
139 if (status != PAM_SUCCESS)
140 die("pam_acct_mgmt failed: %s", pam_strerror(pamh, status));
141 status = pam_end(pamh, status);
142 if (status != PAM_SUCCESS)
143 die("pam_end failed: %s", pam_strerror(pamh, status));
144
145 /* If we get to here, the user successfully authenticated. */
146 return true;
147 }
148 #endif /* HAVE_PAM */
149
150
151 /*
152 ** Try to get a password out of a dbm file. The dbm file should have the
153 ** username for the key and the crypted password as the value. The crypted
154 ** password, if found, is returned as a newly allocated string; otherwise,
155 ** NULL is returned.
156 */
157 #if !(defined(HAVE_DBM) || defined(HAVE_BDB_NDBM))
158 static char *
password_dbm(char * user UNUSED,const char * file UNUSED)159 password_dbm(char *user UNUSED, const char *file UNUSED)
160 {
161 return NULL;
162 }
163 #else
164 static char *
password_dbm(char * name,const char * file)165 password_dbm(char *name, const char *file)
166 {
167 datum key, value;
168 DBM *database;
169 char *password;
170
171 database = dbm_open(file, O_RDONLY, 0600);
172 if (database == NULL)
173 return NULL;
174 key.dptr = name;
175 key.dsize = strlen(name);
176 value = dbm_fetch(database, key);
177 if (value.dptr == NULL) {
178 dbm_close(database);
179 return NULL;
180 }
181 password = xstrndup(value.dptr, value.dsize);
182 dbm_close(database);
183 return password;
184 }
185 #endif /* HAVE_DBM || HAVE_BDB_NDBM */
186
187
188 /*
189 ** Try to get a password out of the system /etc/shadow file. The crypted
190 ** password, if found, is returned as a newly allocated string; otherwise,
191 ** NULL is returned.
192 */
193 #if !HAVE_GETSPNAM
194 static char *
password_shadow(const char * user UNUSED)195 password_shadow(const char *user UNUSED)
196 {
197 return NULL;
198 }
199 #else
200 static char *
password_shadow(const char * user)201 password_shadow(const char *user)
202 {
203 struct spwd *spwd;
204
205 spwd = getspnam(user);
206 if (spwd != NULL)
207 return xstrdup(spwd->sp_pwdp);
208 return NULL;
209 }
210 #endif /* HAVE_GETSPNAM */
211
212
213 /*
214 ** Try to get a password out of a file. The crypted password, if found, is
215 ** returned as a newly allocated string; otherwise, NULL is returned.
216 */
217 static char *
password_file(const char * username,const char * file)218 password_file(const char *username, const char *file)
219 {
220 QIOSTATE *qp;
221 char *line, *password;
222 struct cvector *info = NULL;
223
224 qp = QIOopen(file);
225 if (qp == NULL)
226 return NULL;
227 for (line = QIOread(qp); line != NULL; line = QIOread(qp)) {
228 if (*line == '#' || *line == '\n')
229 continue;
230 info = cvector_split(line, ':', info);
231 if (info->count < 2 || strcmp(info->strings[0], username) != 0)
232 continue;
233 password = xstrdup(info->strings[1]);
234 QIOclose(qp);
235 cvector_free(info);
236 return password;
237 }
238 if (QIOtoolong(qp))
239 die("line too long in %s", file);
240 if (QIOerror(qp))
241 sysdie("error reading %s", file);
242 QIOclose(qp);
243 cvector_free(info);
244 return NULL;
245 }
246
247
248 /*
249 ** Try to get a password out of the system password file. The crypted
250 ** password, if found, is returned as a newly allocated string; otherwise,
251 ** NULL is returned.
252 */
253 static char *
password_system(const char * username)254 password_system(const char *username)
255 {
256 struct passwd *pwd;
257
258 pwd = getpwnam(username);
259 if (pwd != NULL)
260 return xstrdup(pwd->pw_passwd);
261 return NULL;
262 }
263
264
265 /*
266 ** Try to get the name of a user's primary group out of the system group
267 ** file. The group, if found, is returned as a newly allocated string;
268 ** otherwise, NULL is returned. If the username is not found, NULL is
269 ** returned.
270 */
271 static char *
group_system(const char * username)272 group_system(const char *username)
273 {
274 struct passwd *pwd;
275 struct group *gr;
276
277 pwd = getpwnam(username);
278 if (pwd == NULL)
279 return NULL;
280 gr = getgrgid(pwd->pw_gid);
281 if (gr == NULL)
282 return NULL;
283 return xstrdup(gr->gr_name);
284 }
285
286
287 /*
288 ** Output username (and group, if desired) in correct return format.
289 */
290 static void
output_user(const char * username,bool wantgroup)291 output_user(const char *username, bool wantgroup)
292 {
293 if (wantgroup) {
294 char *group = group_system(username);
295 if (group == NULL)
296 die("group info for user %s not available", username);
297 printf("User:%s@%s\r\n", username, group);
298 } else
299 print_user(username);
300 }
301
302
303 /*
304 ** Main routine.
305 **
306 ** We handle the variences between systems with #if blocks above, so that
307 ** this code can look fairly clean.
308 */
309 int
main(int argc,char * argv[])310 main(int argc, char *argv[])
311 {
312 enum authtype
313 {
314 AUTH_NONE,
315 AUTH_SHADOW,
316 AUTH_FILE,
317 AUTH_DBM
318 };
319
320 int opt;
321 const char *hash;
322 enum authtype type = AUTH_NONE;
323 bool wantgroup = false;
324 const char *filename = NULL;
325 struct auth_info *authinfo = NULL;
326 char *password = NULL;
327
328 message_program_name = "ckpasswd";
329
330 while ((opt = getopt(argc, argv, "gf:u:p:" OPT_DBM OPT_SHADOW)) != -1) {
331 switch (opt) {
332 case 'g':
333 if (type == AUTH_DBM || type == AUTH_FILE)
334 die("-g option is incompatible with -d or -f");
335 wantgroup = true;
336 break;
337 case 'd':
338 if (type != AUTH_NONE)
339 die("only one of -s, -f, or -d allowed");
340 if (wantgroup)
341 die("-g option is incompatible with -d or -f");
342 type = AUTH_DBM;
343 filename = optarg;
344 break;
345 case 'f':
346 if (type != AUTH_NONE)
347 die("only one of -s, -f, or -d allowed");
348 if (wantgroup)
349 die("-g option is incompatible with -d or -f");
350 type = AUTH_FILE;
351 filename = optarg;
352 break;
353 case 's':
354 if (type != AUTH_NONE)
355 die("only one of -s, -f, or -d allowed");
356 type = AUTH_SHADOW;
357 break;
358 case 'u':
359 if (authinfo == NULL) {
360 authinfo = xmalloc(sizeof(struct auth_info));
361 authinfo->password = NULL;
362 }
363 authinfo->username = optarg;
364 break;
365 case 'p':
366 if (authinfo == NULL) {
367 authinfo = xmalloc(sizeof(struct auth_info));
368 authinfo->username = NULL;
369 }
370 authinfo->password = optarg;
371 break;
372 default:
373 exit(1);
374 }
375 }
376 if (argc != optind)
377 die("extra arguments given");
378 if (authinfo != NULL && authinfo->username == NULL)
379 die("-u option is required if -p option is given");
380 if (authinfo != NULL && authinfo->password == NULL)
381 die("-p option is required if -u option is given");
382
383 /* Unless a username or password was given on the command line, assume
384 we're being run by nnrpd. */
385 if (authinfo == NULL)
386 authinfo = get_auth_info(stdin);
387 if (authinfo == NULL)
388 die("no authentication information from nnrpd");
389 if (authinfo->username[0] == '\0')
390 die("null username");
391
392 /* Run the appropriate authentication routines. */
393 switch (type) {
394 case AUTH_SHADOW:
395 password = password_shadow(authinfo->username);
396 if (password == NULL)
397 password = password_system(authinfo->username);
398 break;
399 case AUTH_FILE:
400 password = password_file(authinfo->username, filename);
401 break;
402 case AUTH_DBM:
403 password = password_dbm(authinfo->username, filename);
404 break;
405 case AUTH_NONE:
406 if (auth_pam(authinfo->username, authinfo->password)) {
407 output_user(authinfo->username, wantgroup);
408 exit(0);
409 }
410 password = password_system(authinfo->username);
411 break;
412 }
413
414 if (password == NULL)
415 die("user %s unknown", authinfo->username);
416 hash = crypt(authinfo->password, password);
417 if (hash == NULL || strcmp(password, hash) != 0)
418 die("invalid password for user %s", authinfo->username);
419
420 /* The password matched. */
421 output_user(authinfo->username, wantgroup);
422 exit(0);
423 }
424