server_password.c 7.93 KB
Newer Older
Niels Möller's avatar
Niels Möller committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/* server_password.c
 *
 * System dependant password related functions.
 */

/* lsh, an implementation of the ssh protocol
 *
 * Copyright (C) 1998 Niels Mller
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "password.h"

27
#include "charset.h"
28
#include "format.h"
29
#include "parse.h"
30 31
#include "userauth.h"
#include "werror.h"
32 33
#include "xalloc.h"

34 35 36
#include <string.h>
#include <errno.h>

37 38 39 40 41 42
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#if HAVE_CRYPT_H
# include <crypt.h>
#endif
43 44
#include <pwd.h>
#include <grp.h>
45

46 47 48 49
#ifdef HAVE_SHADOW_H
#include <shadow.h>
#endif

50 51
#include "server_password.c.x"

52
/* NOTE: Calls functions using the *disgusting* convention of returning
Niels Möller's avatar
Niels Möller committed
53
 * pointers to static buffers. */
54
struct unix_user *lookup_user(struct lsh_string *name, int free)
Niels Möller's avatar
Niels Möller committed
55 56
{
  struct passwd *passwd;
57

58
  NEW(unix_user, res);
Niels Möller's avatar
Niels Möller committed
59

60
  name = make_cstring(name, free);
Niels Möller's avatar
Niels Möller committed
61

62
  if (!name)
63 64 65 66
    {
      KILL(res);
      return 0;
    }
67 68
  
  if (!(passwd = getpwnam(name->data)))
69 70
    {
      lsh_string_free(name);
71
      KILL(res);
72 73
      return 0;
    }
74

Niels Möller's avatar
Niels Möller committed
75
  res->uid = passwd->pw_uid;
76
  res->gid = passwd->pw_gid;
77
  res->name = name;
78 79 80 81
  
#ifdef HAVE_GETSPNAM
  if (passwd->pw_passwd && !strcmp(passwd->pw_passwd, "x"))
  {
Niels Möller's avatar
Niels Möller committed
82 83
    struct spwd *shadowpwd;
    
84 85 86 87 88 89 90 91 92 93 94
    if (!(shadowpwd = getspnam(name->data)))
    {
      KILL(res);
      return 0;
    }
    res->passwd = format_cstring(shadowpwd->sp_pwdp);
  }
  else
#endif /* HAVE_GETSPNAM */
    res->passwd = format_cstring(passwd->pw_passwd);

Niels Möller's avatar
Niels Möller committed
95
  res->home = format_cstring(passwd->pw_dir);
96 97
  res->shell = format_cstring(passwd->pw_shell);
  
Niels Möller's avatar
Niels Möller committed
98 99 100
  return res;
}

101
/* NOTE: Calls functions using the *disgusting* convention of returning
Niels Möller's avatar
Niels Möller committed
102
 * pointers to static buffers. */
103
int verify_password(struct unix_user *user,
104
		    struct lsh_string *password, int free)
Niels Möller's avatar
Niels Möller committed
105 106 107
{
  char *salt;
  
108
  if (!user->passwd || (user->passwd->length < 2) )
Niels Möller's avatar
Niels Möller committed
109
    {
110 111
      /* FIXME: How are accounts without passwords handled? */
      lsh_string_free(password);
Niels Möller's avatar
Niels Möller committed
112 113 114
      return 0;
    }

115 116 117 118 119 120
  /* Convert password to a NULL-terminated string */
  password = make_cstring(password, free);

  if (!password)
    return 0;
  
121 122 123
  salt = user->passwd->data;

  if (strcmp(crypt(password->data, salt), user->passwd->data))
Niels Möller's avatar
Niels Möller committed
124
    {
125 126
      /* Passwd doesn't match */
      lsh_string_free(password);
Niels Möller's avatar
Niels Möller committed
127 128 129
      return 0;
    }

130
  lsh_string_free(password);
Niels Möller's avatar
Niels Möller committed
131 132
  return 1;
}
133

134 135 136 137 138 139 140 141 142 143
/* CLASS:
   (class
     (name unix_authentication)
     (super userauth)
     (vars
       ;; (login object login_method)
       ; Services allowed. Maps names to struct unix_service.
       (services object alist)))
*/
     
144 145 146 147
static int do_authenticate(struct userauth *c,
			   struct lsh_string *username,
			   int requested_service,
			   struct simple_buffer *args,
148
			   struct ssh_service **result)
149
{
150
  CAST(unix_authentication, closure, c);
151
  struct lsh_string *password = NULL;
152
  struct unix_service *service;
153
  int change_passwd;
154 155
  
  if (!( (service = ALIST_GET(closure->services, requested_service))))
156
    {
157
      lsh_string_free(username);
158 159 160 161 162 163 164
      return LSH_AUTH_FAILED;
    }

  username = utf8_to_local(username, 1);
  if (!username)
    return 0;

165 166 167
  if (parse_boolean(args, &change_passwd))
    {
      if (change_passwd)
168
	{
169
	  /* Password changeing is not implemented. */
170 171 172
	  lsh_string_free(username);
	  return LSH_AUTH_FAILED;
	}
173 174
      if ( (password = parse_string_copy(args))
	   && parse_eod(args))
175
	{
176 177
	  struct unix_user *user;
	  int access;
178

179
	  password = utf8_to_local(password, 1);
180

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
	  if (!password)
	    {
	      lsh_string_free(username);
	      return LSH_AUTH_FAILED;
	    }
       
	  user = lookup_user(username, 1);

	  if (!user)
	    {
	      lsh_string_free(password);
	      return LSH_AUTH_FAILED;
	    }

	  access = verify_password(user, password, 1);

	  if (access)
	    {
	      *result = LOGIN(service, user);
	      return LSH_OK | LSH_GOON;
	    }
	  else
	    {
	      /* FIXME: Free user struct */
	      return LSH_AUTH_FAILED;
	    }
207 208
	}
    }
209 210

  /* Request was invalid */
211
  lsh_string_free(username);
212 213 214

  if (password)
    lsh_string_free(password);
215
  return LSH_FAIL | LSH_DIE;
216 217
}
  
218
struct userauth *make_unix_userauth(struct alist *services)
219
{
220
  NEW(unix_authentication, closure);
221 222

  closure->super.authenticate = do_authenticate;
223
#if 0
224
  closure->login = login;
225
#endif
226 227 228 229 230
  closure->services = services;

  return &closure->super;
}

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
int change_uid(struct unix_user *user)
{
  /* NOTE: Error handling is crucial here. If we do something
   * wrong, the server will think that the user is logged in
   * under his or her user id, while in fact the process is
   * still running as root. */
  if (initgroups(user->name->data, user->gid) < 0)
    {
      werror("initgroups failed: %s\n", strerror(errno));
      return 0;
    }
  if (setgid(user->gid) < 0)
    {
      werror("setgid failed: %s\n", strerror(errno));
      return 0;
    }
  if (setuid(user->uid) < 0)
    {
      werror("setuid failed: %s\n", strerror(errno));
      return 0;
    }
  return 1;
}

int change_dir(struct unix_user *user)
{
  /* Change to user's home directory. FIXME: If the server is running
   * as the same user, perhaps it's better to use $HOME? */
  if (!user->home)
    {
      if (chdir("/") < 0)
	{
	  werror("Strange: home directory was NULL, and chdir(\"/\") failed: %s\n",
		 strerror(errno));
	  return 0;
	}
    }
  else if (chdir(user->home->data) < 0)
    {
      werror("chdir to %s failed (using / instead): %s\n",
	     user->home ? (char *) user->home->data : "none",
	     strerror(errno));
      if (chdir("/") < 0)
	{
	  werror("chdir(\"/\") failed: %s\n", strerror(errno));
	  return 0;
	}
    }
  return 1;  
}

282 283
#if 0

284
struct setuid_service
285
{
286
  struct ssh_service super;
287

288
  struct unix_user *user;
289 290 291 292
  /* Service to start once we have changed to the correct uid. */
  struct ssh_service *service;
};

293 294
/* NOTE: This is used only if early forking (i.e., for directly after
 * user autentication) is enabled. */
295 296
static int do_setuid(struct ssh_service *c,
		     struct ssh_connection *connection)
297
{
298
  CAST(setuid_service, closure, c);  
299
  uid_t server_uid = getuid();
300 301
  int res = 0;

302
  if (server_uid != closure->user->uid)
303
    {
304
      if (server_uid)
305 306 307 308 309 310 311 312 313 314 315
	/* Not root */
	return LSH_AUTH_FAILED;

      switch(fork())
	{
	case -1:
	  /* Error */
	  werror("fork failed: %s\n", strerror(errno));
	  return LSH_FAIL | LSH_DIE;
	case 0:
	  /* Child */
316 317 318 319

	  if (!change_uid(closure->user))
	    return  LSH_FAIL | LSH_DIE | LSH_KILL_OTHERS;;

320 321 322 323 324 325 326 327 328 329
	  res |= LSH_KILL_OTHERS;
	  break;
	default:
	  /* Parent */
	  return LSH_OK | LSH_DIE;
	}
    }

  /* Change to user's home directory. FIXME: If the server is running
   * as the same user, perhaps it's better to use $HOME? */
330 331
  if (!change_dir(closure->user))
    fatal("can't chdir: giving up\n");
332 333 334 335

  /* Initialize environment, somehow. In particular, the HOME and
   * LOGNAME variables */

336
  return res | LOGIN(closure->service, closure->user);		     connection);
337 338
}

339 340 341
/* FIXME: This function is not quite adequate, as it does not pass the
 * user struct on to the started service. */

342 343 344
static struct ssh_service *do_login(struct login_method *closure,
				    struct unix_user *user,
				    struct ssh_service *service)
345
{
346
  struct setuid_service *res;
347

348 349 350 351 352 353
  MDEBUG(closure);
  
  NEW(res);
  res->super.init = do_setuid;
  res->user = user;
  res->service = service;
354

355 356
  return &res->super;
}
357

358 359 360 361 362 363 364 365
struct login_method *make_unix_login(void)
{
  struct login_method *self;

  NEW(self);
  self->login = do_login;

  return self;
366
}
367
#endif