source: trunk/third/ssh/auth-rhosts.c @ 12646

Revision 12646, 12.9 KB checked in by danw, 26 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r12645, which included commits to RCS files with non-trunk default branches.
Line 
1/*
2
3auth-rhosts.c
4
5Author: Tatu Ylonen <ylo@cs.hut.fi>
6
7Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
8                   All rights reserved
9
10Created: Fri Mar 17 05:12:18 1995 ylo
11
12Rhosts authentication.  This file contains code to check whether to admit
13the login based on rhosts authentication.  This file also processes
14/etc/hosts.equiv.
15
16*/
17
18/*
19 * $Id: auth-rhosts.c,v 1.1.1.3 1999-03-08 17:43:03 danw Exp $
20 * $Log: not supported by cvs2svn $
21 * Revision 1.10  1998/07/08 00:38:32  kivinen
22 *      Fixed typo (privileged).
23 *
24 * Revision 1.9  1998/05/23  20:20:04  kivinen
25 *      Added num_deny_shosts/num_allow_shosts option support.
26 *
27 * Revision 1.8  1998/04/30  03:58:38  kivinen
28 *      Fixed typo.
29 *
30 * Revision 1.7  1998/04/30 01:50:40  kivinen
31 *      Added parsing of comments in the end of any lind.
32 *
33 * Revision 1.6  1998/03/27 16:55:50  kivinen
34 *      Added check that .rhosts / .shosts cannot contain control
35 *      characters. Added ignore_root_rhosts support. Fixed .*hosts
36 *      ALLOW_GROUP_WRITEABLITY support.
37 *
38 * Revision 1.5  1997/03/26 06:59:58  kivinen
39 *      Changed uid 0 to UID_ROOT.
40 *
41 * Revision 1.4  1997/03/19 15:59:07  kivinen
42 *      Limit hostname and username to 255 characters.
43 *
44 * Revision 1.3  1996/10/29 22:34:25  kivinen
45 *      log -> log_msg.
46 *
47 * Revision 1.2  1996/02/18 21:53:00  ylo
48 *      Eliminate warning for innetgr arguments.
49 *
50 * Revision 1.1.1.1  1996/02/18 21:38:13  ylo
51 *      Imported ssh-1.2.13.
52 *
53 * Revision 1.11  1995/10/02  01:19:18  ylo
54 *      Fixed a serious security bug in the new hosts.equiv code.
55 *      Fixed case-insensitivity in host names.
56 *      Added support for /etc/shosts.equiv.
57 *
58 * Revision 1.10  1995/09/27  02:11:07  ylo
59 *      Ignore "NO_PLUS".
60 *      Fixed comment processing.
61 *
62 * Revision 1.9  1995/09/22  22:24:51  ylo
63 *      Removed some debugging calls that revealed too much
64 *      information.
65 *      Support negative entries and netgroups in /etc/hosts.equiv and
66 *      rhosts/shosts.
67 *
68 * Revision 1.8  1995/09/21  17:07:42  ylo
69 *      Added uidswap.h.
70 *      Restructured rhosts authentication code.  hosts.equiv now uses
71 *      the same code to process the file; user names are now
72 *      permitted in hosts.equiv.
73 *
74 * Revision 1.7  1995/09/09  21:26:37  ylo
75 * /m/shadows/u2/users/ylo/ssh/README
76 *
77 * Revision 1.6  1995/08/29  22:18:18  ylo
78 *      Permit using ip addresses in .rhosts and .shosts files.
79 *
80 * Revision 1.5  1995/08/22  14:05:16  ylo
81 *      Added uid-swapping.
82 *
83 * Revision 1.4  1995/07/27  00:37:00  ylo
84 *      Added /etc/hosts.equiv in quick test.
85 *
86 * Revision 1.3  1995/07/13  01:13:20  ylo
87 *      Removed the "Last modified" header.
88 *
89 * $Endlog$
90 */
91
92#include "includes.h"
93#include "packet.h"
94#include "ssh.h"
95#include "xmalloc.h"
96#include "userfile.h"
97#include "servconf.h"
98
99extern ServerOptions options;
100
101/* Returns true if the strings are equal, ignoring case (a-z only). */
102
103static int casefold_equal(const char *a, const char *b)
104{
105  unsigned char cha, chb;
106  for (; *a; a++, b++)
107    {
108      cha = *a;
109      chb = *b;
110      if (!chb)
111        return 0;
112      if (cha >= 'a' && cha <= 'z')
113        cha -= 32;
114      if (chb >= 'a' && chb <= 'z')
115        chb -= 32;
116      if (cha != chb)
117        return 0;
118    }
119  return !*b;
120}
121
122/* This function processes an rhosts-style file (.rhosts, .shosts, or
123   /etc/hosts.equiv).  This returns true if authentication can be granted
124   based on the file, and returns zero otherwise.  All I/O will be done
125   using the given uid with userfile. */
126
127int check_rhosts_file(uid_t uid, const char *filename, const char *hostname,
128                      const char *ipaddr, const char *client_user,
129                      const char *server_user)
130{
131  UserFile uf;
132  char buf[1024]; /* Must not be larger than host, user, dummy below. */
133 
134  /* Open the .rhosts file. */
135  uf = userfile_open(uid, filename, O_RDONLY, 0);
136  if (uf == NULL)
137    return 0; /* Cannot read the .rhosts - deny access. */
138
139  /* Go through the file, checking every entry. */
140  while (userfile_gets(buf, sizeof(buf), uf))
141    {
142      /* All three must be at least as big as buf to avoid overflows. */
143      char hostbuf[1024], userbuf[1024], dummy[1024], *host, *user, *cp, *c;
144      int negated;
145     
146      for(cp = buf; *cp; cp++)
147        {
148          if (*cp < 32 && !isspace(*cp))
149            {
150              packet_send_debug("Found control characters in the .rhosts or .shosts file, rest of the file ignored");
151              userfile_close(uf);
152              return 0;
153            }
154        }
155      for (cp = buf; *cp == ' ' || *cp == '\t'; cp++)
156        ;
157      if ((c = strchr(cp, '#')) != NULL)
158        *c = '\0';
159      if (*cp == '#' || *cp == '\n' || !*cp)
160        continue;
161
162      /* NO_PLUS is supported at least on OSF/1.  We skip it (we don't ever
163         support the plus syntax). */
164      if (strncmp(cp, "NO_PLUS", 7) == 0)
165        continue;
166
167      /* This should be safe because each buffer is as big as the whole
168         string, and thus cannot be overwritten. */
169      switch (sscanf(buf, "%s %s %s", hostbuf, userbuf, dummy))
170        {
171        case 0:
172          packet_send_debug("Found empty line in %.100s.", filename);
173          continue; /* Empty line? */
174        case 1:
175          /* Host name only. */
176          strncpy(userbuf, server_user, sizeof(userbuf));
177          userbuf[sizeof(userbuf) - 1] = 0;
178          break;
179        case 2:
180          /* Got both host and user name. */
181          break;
182        case 3:
183          packet_send_debug("Found garbage in %.100s.", filename);
184          continue; /* Extra garbage */
185        default:
186          continue; /* Weird... */
187        }
188
189      host = hostbuf;
190      user = userbuf;
191      /* Truncate host and user name to 255 to avoid buffer overflows in system
192         libraries */
193      if (strlen(host) > 255)
194        host[255] = '\0';
195      if (strlen(user) > 255)
196        user[255] = '\0';
197      negated = 0;
198
199      /* Process negated host names, or positive netgroups. */
200      if (host[0] == '-')
201        {
202          negated = 1;
203          host++;
204        }
205      else
206        if (host[0] == '+')
207          host++;
208
209      if (user[0] == '-')
210        {
211          negated = 1;
212          user++;
213        }
214      else
215        if (user[0] == '+')
216          user++;
217
218      /* Check for empty host/user names (particularly '+'). */
219      if (!host[0] || !user[0])
220        {
221          /* We come here if either was '+' or '-'. */
222          packet_send_debug("Ignoring wild host/user names in %.100s.",
223                            filename);
224          continue;
225        }
226         
227#ifdef HAVE_INNETGR
228
229      /* Verify that host name matches. */
230      if (host[0] == '@')
231        {
232          if (!innetgr(host + 1, (char *)hostname, NULL, NULL) &&
233              !innetgr(host + 1, (char *)ipaddr, NULL, NULL))
234            continue;
235        }
236      else
237        if (!casefold_equal(host, hostname) && strcmp(host, ipaddr) != 0)
238          continue; /* Different hostname. */
239
240      /* Verify that user name matches. */
241      if (user[0] == '@')
242        {
243          if (!innetgr(user + 1, NULL, (char *)client_user, NULL))
244            continue;
245        }
246      else
247        if (strcmp(user, client_user) != 0)
248          continue; /* Different username. */
249
250#else /* HAVE_INNETGR */
251
252      if (!casefold_equal(host, hostname) && strcmp(host, ipaddr) != 0)
253        continue; /* Different hostname. */
254
255      if (strcmp(user, client_user) != 0)
256        continue; /* Different username. */
257
258#endif /* HAVE_INNETGR */
259     
260      /* Check whether this host is permitted to be in .[rs]hosts. */
261      {
262        int perm_denied = 0;
263        int i;
264        if (options.num_deny_shosts > 0)
265          {
266            for (i = 0; i < options.num_deny_shosts; i++)
267              if (match_pattern(host, options.deny_shosts[i]))
268                perm_denied = 1;
269          }
270        if ((!perm_denied) && options.num_allow_shosts > 0)
271          {
272            for (i = 0; i < options.num_allow_shosts; i++)
273              if (match_pattern(host, options.allow_shosts[i]))
274                break;
275            if (i >= options.num_allow_shosts)
276              perm_denied = 1;
277          }
278        if (perm_denied)
279          {
280            log_msg("Use of %s denied for %s", filename, host);
281            continue;
282          }
283      }
284
285      /* Found the user and host. */
286      userfile_close(uf);
287
288      /* If the entry was negated, deny access. */
289      if (negated)
290        {
291          packet_send_debug("Matched negative entry in %.100s.",
292                            filename);
293          return 0;
294        }
295
296      /* Accept authentication. */
297      return 1;
298    }
299     
300  /* Authentication using this file denied. */
301  userfile_close(uf);
302  return 0;
303}
304
305/* Tries to authenticate the user using the .shosts or .rhosts file. 
306   Returns true if authentication succeeds.  If ignore_rhosts is
307   true, only /etc/hosts.equiv will be considered (.rhosts and .shosts
308   are ignored), unless the user is root and ignore_root_rhosts isn't
309   true. */
310
311int auth_rhosts(struct passwd *pw, const char *client_user,
312                int ignore_rhosts, int ignore_root_rhosts,
313                int strict_modes)
314{
315  char buf[1024];
316  const char *hostname, *ipaddr;
317  int port;
318  struct stat st;
319  static const char *rhosts_files[] = { ".shosts", ".rhosts", NULL };
320  unsigned int rhosts_file_index;
321
322  /* Quick check: if the user has no .shosts or .rhosts files, return failure
323     immediately without doing costly lookups from name servers. */
324  for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
325       rhosts_file_index++)
326    {
327      /* Check users .rhosts or .shosts. */
328      sprintf(buf, "%.500s/%.100s",
329              pw->pw_dir, rhosts_files[rhosts_file_index]);
330      if (userfile_stat(pw->pw_uid, buf, &st) >= 0)
331        break;
332    }
333
334  if (!rhosts_files[rhosts_file_index] &&
335      userfile_stat(pw->pw_uid, "/etc/hosts.equiv", &st) < 0 &&
336      userfile_stat(pw->pw_uid, SSH_HOSTS_EQUIV, &st) < 0)
337    return 0; /* The user has no .shosts or .rhosts file and there are no
338                 system-wide files. */
339
340  /* Get the name, address, and port of the remote host.  */
341  hostname = get_canonical_hostname();
342  ipaddr = get_remote_ipaddr();
343  port = get_remote_port();
344
345  /* Check that the connection comes from a privileged port.
346     Rhosts authentication only makes sense for privileged programs.
347     Of course, if the intruder has root access on his local machine,
348     he can connect from any port.  So do not use .rhosts
349     authentication from machines that you do not trust. */
350  if (port >= IPPORT_RESERVED ||
351      port < IPPORT_RESERVED / 2)
352    {
353      log_msg("Connection from %.100s from nonprivileged port %d",
354          hostname, port);
355      packet_send_debug("Your ssh client is not running as root.");
356      return 0;
357    }
358
359  /* If not logging in as superuser, try /etc/hosts.equiv and shosts.equiv. */
360  if (pw->pw_uid != UID_ROOT)
361    {
362      if (check_rhosts_file(geteuid(),
363                            "/etc/hosts.equiv", hostname, ipaddr, client_user,
364                            pw->pw_name))
365        {
366          packet_send_debug("Accepted for %.100s [%.100s] by /etc/hosts.equiv.",
367                            hostname, ipaddr);
368          return 1;
369        }
370      if (check_rhosts_file(geteuid(),
371                            SSH_HOSTS_EQUIV, hostname, ipaddr, client_user,
372                            pw->pw_name))
373        {
374          packet_send_debug("Accepted for %.100s [%.100s] by %.100s.",
375                            hostname, ipaddr, SSH_HOSTS_EQUIV);
376          return 1;
377        }
378    }
379
380  /* Check that the home directory is owned by root or the user, and is not
381     group or world writable. */
382  if (userfile_stat(pw->pw_uid, pw->pw_dir, &st) < 0)
383    {
384      log_msg("Rhosts authentication refused for %.100: no home directory %.200s",
385          pw->pw_name, pw->pw_dir);
386      packet_send_debug("Rhosts authentication refused for %.100: no home directory %.200s",
387                        pw->pw_name, pw->pw_dir);
388      return 0;
389    }
390  if (strict_modes &&
391      ((st.st_uid != UID_ROOT && st.st_uid != pw->pw_uid) ||
392#ifdef ALLOW_GROUP_WRITEABILITY
393       (st.st_mode & 002) != 0)
394#else
395       (st.st_mode & 022) != 0)
396#endif
397      )
398    {
399      log_msg("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.",
400          pw->pw_name);
401      packet_send_debug("Rhosts authentication refused for %.100s: bad ownership or modes for home directory.",
402                        pw->pw_name);
403      return 0;
404    }
405 
406  /* Check all .rhosts files (currently .shosts and .rhosts). */
407  for (rhosts_file_index = 0; rhosts_files[rhosts_file_index];
408       rhosts_file_index++)
409    {
410      /* Check users .rhosts or .shosts. */
411      sprintf(buf, "%.500s/%.100s",
412              pw->pw_dir, rhosts_files[rhosts_file_index]);
413      if (userfile_stat(pw->pw_uid, buf, &st) < 0)
414        continue; /* No such file. */
415
416      /* Make sure that the file is either owned by the user or by root,
417         and make sure it is not writable by anyone but the owner.  This is
418         to help avoid novices accidentally allowing access to their account
419         by anyone. */
420      if (strict_modes &&
421          ((st.st_uid != UID_ROOT && st.st_uid != pw->pw_uid) ||
422           (st.st_mode & 022) != 0))
423        {
424          log_msg("Rhosts authentication refused for %.100s: bad modes for %.200s",
425              pw->pw_name, buf);
426          packet_send_debug("Bad file modes for %.200s", buf);
427          continue;
428        }
429
430      /* Check if we have been configured to ignore .rhosts and .shosts
431         files.  If root, check ignore_root_rhosts first. */
432      if ((pw->pw_uid == UID_ROOT && ignore_root_rhosts) ||
433          (pw->pw_uid != UID_ROOT && ignore_rhosts))
434        {
435          packet_send_debug("Server has been configured to ignore %.100s.",
436                            rhosts_files[rhosts_file_index]);
437          continue;
438        }
439
440      /* Check if authentication is permitted by the file. */
441      if (check_rhosts_file(pw->pw_uid, buf, hostname, ipaddr, client_user,
442                            pw->pw_name))
443        {
444          packet_send_debug("Accepted by %.100s.",
445                            rhosts_files[rhosts_file_index]);
446          return 1;
447        }
448    }
449
450  /* Rhosts authentication denied. */
451  packet_send_debug("Rhosts/hosts.equiv authentication refused: client user '%.100s', server user '%.100s', client host '%.200s'.",
452                    client_user, pw->pw_name, get_canonical_hostname());
453
454  return 0;
455}
Note: See TracBrowser for help on using the repository browser.