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