Annotation of src/usr.bin/ssh/auth2.c, Revision 1.18
1.1 markus 1: /*
2: * Copyright (c) 2000 Markus Friedl. All rights reserved.
3: *
4: * Redistribution and use in source and binary forms, with or without
5: * modification, are permitted provided that the following conditions
6: * are met:
7: * 1. Redistributions of source code must retain the above copyright
8: * notice, this list of conditions and the following disclaimer.
9: * 2. Redistributions in binary form must reproduce the above copyright
10: * notice, this list of conditions and the following disclaimer in the
11: * documentation and/or other materials provided with the distribution.
12: *
13: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23: */
1.14 deraadt 24:
1.1 markus 25: #include "includes.h"
1.18 ! markus 26: RCSID("$OpenBSD: auth2.c,v 1.17 2000/10/11 19:59:52 markus Exp $");
1.1 markus 27:
28: #include <openssl/dsa.h>
29: #include <openssl/rsa.h>
30: #include <openssl/evp.h>
31:
32: #include "xmalloc.h"
33: #include "rsa.h"
34: #include "ssh.h"
35: #include "pty.h"
36: #include "packet.h"
37: #include "buffer.h"
38: #include "cipher.h"
39: #include "servconf.h"
40: #include "compat.h"
41: #include "channels.h"
42: #include "bufaux.h"
43: #include "ssh2.h"
44: #include "auth.h"
45: #include "session.h"
46: #include "dispatch.h"
47: #include "auth.h"
48: #include "key.h"
49: #include "kex.h"
50:
51: #include "dsa.h"
52: #include "uidswap.h"
1.10 markus 53: #include "auth-options.h"
1.1 markus 54:
55: /* import */
56: extern ServerOptions options;
57: extern unsigned char *session_id2;
58: extern int session_id2_len;
59:
1.18 ! markus 60: static Authctxt *x_authctxt = NULL;
! 61: static int one = 1;
! 62:
! 63: typedef struct Authmethod Authmethod;
! 64: struct Authmethod {
! 65: char *name;
! 66: int (*userauth)(Authctxt *authctxt);
! 67: int *enabled;
! 68: };
! 69:
1.1 markus 70: /* protocol */
71:
1.15 markus 72: void input_service_request(int type, int plen, void *ctxt);
73: void input_userauth_request(int type, int plen, void *ctxt);
74: void protocol_error(int type, int plen, void *ctxt);
1.1 markus 75:
76:
77: /* helper */
1.18 ! markus 78: Authmethod *authmethod_lookup(const char *name);
! 79: struct passwd *pwcopy(struct passwd *pw);
1.1 markus 80: int user_dsa_key_allowed(struct passwd *pw, Key *key);
1.18 ! markus 81: char *authmethods_get(void);
1.1 markus 82:
1.18 ! markus 83: /* auth */
! 84: int userauth_none(Authctxt *authctxt);
! 85: int userauth_passwd(Authctxt *authctxt);
! 86: int userauth_pubkey(Authctxt *authctxt);
! 87: int userauth_kbdint(Authctxt *authctxt);
! 88:
! 89: Authmethod authmethods[] = {
! 90: {"none",
! 91: userauth_none,
! 92: &one},
! 93: {"publickey",
! 94: userauth_pubkey,
! 95: &options.dsa_authentication},
! 96: {"keyboard-interactive",
! 97: userauth_kbdint,
! 98: &options.kbd_interactive_authentication},
! 99: {"password",
! 100: userauth_passwd,
! 101: &options.password_authentication},
! 102: {NULL, NULL, NULL}
1.1 markus 103: };
104:
105: /*
1.18 ! markus 106: * loop until authctxt->success == TRUE
1.1 markus 107: */
108:
109: void
110: do_authentication2()
111: {
1.18 ! markus 112: Authctxt *authctxt = xmalloc(sizeof(*authctxt));
! 113: memset(authctxt, 'a', sizeof(*authctxt));
! 114: authctxt->valid = 0;
! 115: authctxt->attempt = 0;
! 116: authctxt->success = 0;
! 117: x_authctxt = authctxt; /*XXX*/
! 118:
1.5 djm 119: #ifdef KRB4
1.18 ! markus 120: /* turn off kerberos, not supported by SSH2 */
1.4 markus 121: options.kerberos_authentication = 0;
1.5 djm 122: #endif
1.1 markus 123: dispatch_init(&protocol_error);
124: dispatch_set(SSH2_MSG_SERVICE_REQUEST, &input_service_request);
1.18 ! markus 125: dispatch_run(DISPATCH_BLOCK, &authctxt->success, authctxt);
1.1 markus 126: do_authenticated2();
127: }
128:
129: void
1.15 markus 130: protocol_error(int type, int plen, void *ctxt)
1.1 markus 131: {
132: log("auth: protocol error: type %d plen %d", type, plen);
133: packet_start(SSH2_MSG_UNIMPLEMENTED);
134: packet_put_int(0);
135: packet_send();
136: packet_write_wait();
137: }
138:
139: void
1.15 markus 140: input_service_request(int type, int plen, void *ctxt)
1.1 markus 141: {
1.18 ! markus 142: Authctxt *authctxt = ctxt;
1.1 markus 143: unsigned int len;
144: int accept = 0;
145: char *service = packet_get_string(&len);
146: packet_done();
147:
1.18 ! markus 148: if (authctxt == NULL)
! 149: fatal("input_service_request: no authctxt");
! 150:
1.1 markus 151: if (strcmp(service, "ssh-userauth") == 0) {
1.18 ! markus 152: if (!authctxt->success) {
1.1 markus 153: accept = 1;
154: /* now we can handle user-auth requests */
155: dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &input_userauth_request);
156: }
157: }
158: /* XXX all other service requests are denied */
159:
160: if (accept) {
161: packet_start(SSH2_MSG_SERVICE_ACCEPT);
162: packet_put_cstring(service);
163: packet_send();
164: packet_write_wait();
165: } else {
166: debug("bad service request %s", service);
167: packet_disconnect("bad service request %s", service);
168: }
169: xfree(service);
170: }
171:
172: void
1.15 markus 173: input_userauth_request(int type, int plen, void *ctxt)
1.1 markus 174: {
1.18 ! markus 175: Authctxt *authctxt = ctxt;
! 176: Authmethod *m = NULL;
! 177: char *user, *service, *method;
1.1 markus 178: int authenticated = 0;
179:
1.18 ! markus 180: if (authctxt == NULL)
! 181: fatal("input_userauth_request: no authctxt");
! 182: if (authctxt->attempt++ >= AUTH_FAIL_MAX)
1.1 markus 183: packet_disconnect("too many failed userauth_requests");
184:
1.18 ! markus 185: user = packet_get_string(NULL);
! 186: service = packet_get_string(NULL);
! 187: method = packet_get_string(NULL);
1.1 markus 188: debug("userauth-request for user %s service %s method %s", user, service, method);
1.18 ! markus 189: debug("attempt #%d", authctxt->attempt);
1.1 markus 190:
1.18 ! markus 191: if (authctxt->attempt == 1) {
! 192: /* setup auth context */
! 193: struct passwd *pw = NULL;
! 194: setproctitle("%s", user);
! 195: pw = getpwnam(user);
! 196: if (pw && allowed_user(pw) && strcmp(service, "ssh-connection")==0) {
! 197: authctxt->pw = pwcopy(pw);
! 198: authctxt->valid = 1;
! 199: debug2("input_userauth_request: setting up authctxt for %s", user);
! 200: } else {
! 201: log("input_userauth_request: illegal user %s", user);
! 202: }
! 203: authctxt->user = xstrdup(user);
! 204: authctxt->service = xstrdup(service);
! 205: } else if (authctxt->valid) {
! 206: if (strcmp(user, authctxt->user) != 0 ||
! 207: strcmp(service, authctxt->service) != 0) {
! 208: log("input_userauth_request: missmatch: (%s,%s)!=(%s,%s)",
! 209: user, service, authctxt->user, authctxt->service);
! 210: authctxt->valid = 0;
1.1 markus 211: }
212: }
1.18 ! markus 213:
! 214: m = authmethod_lookup(method);
! 215: if (m != NULL) {
! 216: debug2("input_userauth_request: try method %s", method);
! 217: authenticated = m->userauth(authctxt);
! 218: } else {
! 219: debug2("input_userauth_request: unsupported method %s", method);
! 220: }
! 221: if (!authctxt->valid && authenticated == 1) {
! 222: log("input_userauth_request: INTERNAL ERROR: authenticated invalid user %s service %s", user, method);
! 223: authenticated = 0;
! 224: }
! 225:
! 226: /* Special handling for root */
! 227: if (authenticated == 1 &&
! 228: authctxt->valid && authctxt->pw->pw_uid == 0 && !options.permit_root_login) {
1.3 markus 229: authenticated = 0;
1.18 ! markus 230: log("ROOT LOGIN REFUSED FROM %.200s", get_canonical_hostname());
1.3 markus 231: }
232:
1.18 ! markus 233: /* Log before sending the reply */
! 234: userauth_log(authctxt, authenticated, method);
! 235: userauth_reply(authctxt, authenticated);
! 236:
! 237: xfree(service);
! 238: xfree(user);
! 239: xfree(method);
! 240: }
! 241:
! 242:
! 243: void
! 244: userauth_log(Authctxt *authctxt, int authenticated, char *method)
! 245: {
! 246: void (*authlog) (const char *fmt,...) = verbose;
! 247: char *user = NULL, *authmsg = NULL;
! 248:
1.6 markus 249: /* Raise logging level */
250: if (authenticated == 1 ||
1.18 ! markus 251: !authctxt->valid ||
! 252: authctxt->attempt >= AUTH_FAIL_LOG ||
1.6 markus 253: strcmp(method, "password") == 0)
254: authlog = log;
255:
256: if (authenticated == 1) {
257: authmsg = "Accepted";
258: } else if (authenticated == 0) {
259: authmsg = "Failed";
260: } else {
261: authmsg = "Postponed";
262: }
1.18 ! markus 263:
! 264: if (authctxt->valid) {
! 265: user = authctxt->pw->pw_uid == 0 ? "ROOT" : authctxt->user;
! 266: } else {
! 267: user = "NOUSER";
! 268: }
! 269:
1.6 markus 270: authlog("%s %s for %.200s from %.200s port %d ssh2",
1.18 ! markus 271: authmsg,
! 272: method,
! 273: user,
! 274: get_remote_ipaddr(),
! 275: get_remote_port());
! 276: }
1.6 markus 277:
1.18 ! markus 278: void
! 279: userauth_reply(Authctxt *authctxt, int authenticated)
! 280: {
1.3 markus 281: /* XXX todo: check if multiple auth methods are needed */
1.1 markus 282: if (authenticated == 1) {
283: /* turn off userauth */
284: dispatch_set(SSH2_MSG_USERAUTH_REQUEST, &protocol_error);
285: packet_start(SSH2_MSG_USERAUTH_SUCCESS);
286: packet_send();
287: packet_write_wait();
288: /* now we can break out */
1.18 ! markus 289: authctxt->success = 1;
1.1 markus 290: } else if (authenticated == 0) {
1.18 ! markus 291: char *methods = authmethods_get();
1.1 markus 292: packet_start(SSH2_MSG_USERAUTH_FAILURE);
1.18 ! markus 293: packet_put_cstring(methods);
! 294: packet_put_char(0); /* XXX partial success, unused */
1.1 markus 295: packet_send();
296: packet_write_wait();
1.18 ! markus 297: xfree(methods);
! 298: } else {
! 299: /* do nothing, we did already send a reply */
1.1 markus 300: }
301: }
302:
303: int
1.18 ! markus 304: userauth_none(Authctxt *authctxt)
1.1 markus 305: {
1.18 ! markus 306: /* disable method "none", only allowed one time */
! 307: Authmethod *m = authmethod_lookup("none");
! 308: if (m != NULL)
! 309: m->enabled = NULL;
1.1 markus 310: packet_done();
1.18 ! markus 311: return authctxt->valid ? auth_password(authctxt->pw, "") : 0;
1.1 markus 312: }
1.18 ! markus 313:
1.1 markus 314: int
1.18 ! markus 315: userauth_passwd(Authctxt *authctxt)
1.1 markus 316: {
317: char *password;
318: int authenticated = 0;
319: int change;
320: unsigned int len;
321: change = packet_get_char();
322: if (change)
323: log("password change not supported");
324: password = packet_get_string(&len);
325: packet_done();
1.18 ! markus 326: if (authctxt->valid &&
! 327: auth_password(authctxt->pw, password) == 1)
1.1 markus 328: authenticated = 1;
329: memset(password, 0, len);
330: xfree(password);
331: return authenticated;
332: }
1.18 ! markus 333:
! 334: int
! 335: userauth_kbdint(Authctxt *authctxt)
! 336: {
! 337: int authenticated = 0;
! 338: char *lang = NULL;
! 339: char *devs = NULL;
! 340:
! 341: lang = packet_get_string(NULL);
! 342: devs = packet_get_string(NULL);
! 343: packet_done();
! 344:
! 345: debug("keyboard-interactive language %s devs %s", lang, devs);
! 346: #ifdef SKEY
! 347: /* XXX hardcoded, we should look at devs */
! 348: if (options.skey_authentication != 0)
! 349: authenticated = auth2_skey(authctxt);
! 350: #endif
! 351: xfree(lang);
! 352: xfree(devs);
! 353: return authenticated;
! 354: }
! 355:
1.1 markus 356: int
1.18 ! markus 357: userauth_pubkey(Authctxt *authctxt)
1.1 markus 358: {
359: Buffer b;
360: Key *key;
361: char *pkalg, *pkblob, *sig;
362: unsigned int alen, blen, slen;
363: int have_sig;
364: int authenticated = 0;
365:
1.18 ! markus 366: if (!authctxt->valid) {
! 367: debug2("userauth_pubkey: disabled because of invalid user");
1.8 markus 368: return 0;
369: }
1.1 markus 370: have_sig = packet_get_char();
371: pkalg = packet_get_string(&alen);
372: if (strcmp(pkalg, KEX_DSS) != 0) {
1.18 ! markus 373: log("bad pkalg %s", pkalg); /*XXX*/
1.1 markus 374: xfree(pkalg);
375: return 0;
376: }
377: pkblob = packet_get_string(&blen);
378: key = dsa_key_from_blob(pkblob, blen);
1.2 markus 379: if (key != NULL) {
380: if (have_sig) {
381: sig = packet_get_string(&slen);
382: packet_done();
383: buffer_init(&b);
1.11 markus 384: if (datafellows & SSH_COMPAT_SESSIONID_ENCODING) {
385: buffer_put_string(&b, session_id2, session_id2_len);
386: } else {
387: buffer_append(&b, session_id2, session_id2_len);
388: }
1.9 markus 389: /* reconstruct packet */
1.2 markus 390: buffer_put_char(&b, SSH2_MSG_USERAUTH_REQUEST);
1.18 ! markus 391: buffer_put_cstring(&b, authctxt->user);
1.9 markus 392: buffer_put_cstring(&b,
393: datafellows & SSH_BUG_PUBKEYAUTH ?
394: "ssh-userauth" :
1.18 ! markus 395: authctxt->service);
1.9 markus 396: buffer_put_cstring(&b, "publickey");
397: buffer_put_char(&b, have_sig);
398: buffer_put_cstring(&b, KEX_DSS);
399: buffer_put_string(&b, pkblob, blen);
1.1 markus 400: #ifdef DEBUG_DSS
1.2 markus 401: buffer_dump(&b);
1.1 markus 402: #endif
1.2 markus 403: /* test for correct signature */
1.18 ! markus 404: if (user_dsa_key_allowed(authctxt->pw, key) &&
1.2 markus 405: dsa_verify(key, sig, slen, buffer_ptr(&b), buffer_len(&b)) == 1)
406: authenticated = 1;
407: buffer_clear(&b);
408: xfree(sig);
409: } else {
1.18 ! markus 410: debug("test whether pkalg/pkblob are acceptable");
1.2 markus 411: packet_done();
1.18 ! markus 412:
1.2 markus 413: /* XXX fake reply and always send PK_OK ? */
1.6 markus 414: /*
415: * XXX this allows testing whether a user is allowed
416: * to login: if you happen to have a valid pubkey this
417: * message is sent. the message is NEVER sent at all
418: * if a user is not allowed to login. is this an
419: * issue? -markus
420: */
1.18 ! markus 421: if (user_dsa_key_allowed(authctxt->pw, key)) {
1.2 markus 422: packet_start(SSH2_MSG_USERAUTH_PK_OK);
423: packet_put_string(pkalg, alen);
424: packet_put_string(pkblob, blen);
425: packet_send();
426: packet_write_wait();
427: authenticated = -1;
428: }
1.1 markus 429: }
1.17 markus 430: if (authenticated != 1)
431: auth_clear_options();
1.2 markus 432: key_free(key);
1.1 markus 433: }
434: xfree(pkalg);
435: xfree(pkblob);
436: return authenticated;
437: }
438:
1.18 ! markus 439: /* get current user */
1.1 markus 440:
441: struct passwd*
442: auth_get_user(void)
443: {
1.18 ! markus 444: return (x_authctxt != NULL && x_authctxt->valid) ? x_authctxt->pw : NULL;
1.1 markus 445: }
446:
1.18 ! markus 447: #define DELIM ","
! 448:
! 449: char *
! 450: authmethods_get(void)
1.1 markus 451: {
1.18 ! markus 452: Authmethod *method = NULL;
! 453: unsigned int size = 0;
! 454: char *list;
1.1 markus 455:
1.18 ! markus 456: for (method = authmethods; method->name != NULL; method++) {
! 457: if (strcmp(method->name, "none") == 0)
! 458: continue;
! 459: if (method->enabled != NULL && *(method->enabled) != 0) {
! 460: if (size != 0)
! 461: size += strlen(DELIM);
! 462: size += strlen(method->name);
1.1 markus 463: }
1.18 ! markus 464: }
! 465: size++; /* trailing '\0' */
! 466: list = xmalloc(size);
! 467: list[0] = '\0';
! 468:
! 469: for (method = authmethods; method->name != NULL; method++) {
! 470: if (strcmp(method->name, "none") == 0)
! 471: continue;
! 472: if (method->enabled != NULL && *(method->enabled) != 0) {
! 473: if (list[0] != '\0')
! 474: strlcat(list, DELIM, size);
! 475: strlcat(list, method->name, size);
1.1 markus 476: }
477: }
1.18 ! markus 478: return list;
! 479: }
! 480:
! 481: Authmethod *
! 482: authmethod_lookup(const char *name)
! 483: {
! 484: Authmethod *method = NULL;
! 485: if (name != NULL)
! 486: for (method = authmethods; method->name != NULL; method++)
! 487: if (method->enabled != NULL &&
! 488: *(method->enabled) != 0 &&
! 489: strcmp(name, method->name) == 0)
! 490: return method;
! 491: debug2("Unrecognized authentication method name: %s", name ? name : "NULL");
! 492: return NULL;
1.1 markus 493: }
494:
495: /* return 1 if user allows given key */
496: int
497: user_dsa_key_allowed(struct passwd *pw, Key *key)
498: {
499: char line[8192], file[1024];
500: int found_key = 0;
501: unsigned int bits = -1;
502: FILE *f;
503: unsigned long linenum = 0;
504: struct stat st;
505: Key *found;
506:
1.18 ! markus 507: if (pw == NULL)
! 508: return 0;
! 509:
1.1 markus 510: /* Temporarily use the user's uid. */
511: temporarily_use_uid(pw->pw_uid);
512:
513: /* The authorized keys. */
514: snprintf(file, sizeof file, "%.500s/%.100s", pw->pw_dir,
515: SSH_USER_PERMITTED_KEYS2);
516:
517: /* Fail quietly if file does not exist */
518: if (stat(file, &st) < 0) {
519: /* Restore the privileged uid. */
520: restore_uid();
521: return 0;
522: }
523: /* Open the file containing the authorized keys. */
524: f = fopen(file, "r");
525: if (!f) {
526: /* Restore the privileged uid. */
527: restore_uid();
528: return 0;
529: }
530: if (options.strict_modes) {
531: int fail = 0;
532: char buf[1024];
533: /* Check open file in order to avoid open/stat races */
534: if (fstat(fileno(f), &st) < 0 ||
535: (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
536: (st.st_mode & 022) != 0) {
1.16 markus 537: snprintf(buf, sizeof buf,
538: "%s authentication refused for %.100s: "
539: "bad ownership or modes for '%s'.",
540: key_type(key), pw->pw_name, file);
1.1 markus 541: fail = 1;
542: } else {
543: /* Check path to SSH_USER_PERMITTED_KEYS */
544: int i;
545: static const char *check[] = {
546: "", SSH_USER_DIR, NULL
547: };
548: for (i = 0; check[i]; i++) {
549: snprintf(line, sizeof line, "%.500s/%.100s",
550: pw->pw_dir, check[i]);
551: if (stat(line, &st) < 0 ||
552: (st.st_uid != 0 && st.st_uid != pw->pw_uid) ||
553: (st.st_mode & 022) != 0) {
554: snprintf(buf, sizeof buf,
1.16 markus 555: "%s authentication refused for %.100s: "
1.1 markus 556: "bad ownership or modes for '%s'.",
1.16 markus 557: key_type(key), pw->pw_name, line);
1.1 markus 558: fail = 1;
559: break;
560: }
561: }
562: }
563: if (fail) {
564: fclose(f);
1.12 todd 565: log("%s",buf);
1.1 markus 566: restore_uid();
567: return 0;
568: }
569: }
570: found_key = 0;
1.16 markus 571: found = key_new(key->type);
1.1 markus 572:
573: while (fgets(line, sizeof(line), f)) {
1.10 markus 574: char *cp, *options = NULL;
1.1 markus 575: linenum++;
576: /* Skip leading whitespace, empty and comment lines. */
577: for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
578: ;
579: if (!*cp || *cp == '\n' || *cp == '#')
580: continue;
1.10 markus 581:
1.1 markus 582: bits = key_read(found, &cp);
1.10 markus 583: if (bits == 0) {
584: /* no key? check if there are options for this key */
585: int quoted = 0;
586: options = cp;
587: for (; *cp && (quoted || (*cp != ' ' && *cp != '\t')); cp++) {
588: if (*cp == '\\' && cp[1] == '"')
589: cp++; /* Skip both */
590: else if (*cp == '"')
591: quoted = !quoted;
592: }
593: /* Skip remaining whitespace. */
594: for (; *cp == ' ' || *cp == '\t'; cp++)
595: ;
596: bits = key_read(found, &cp);
597: if (bits == 0) {
598: /* still no key? advance to next line*/
599: continue;
600: }
601: }
602: if (key_equal(found, key) &&
603: auth_parse_options(pw, options, linenum) == 1) {
1.1 markus 604: found_key = 1;
605: debug("matching key found: file %s, line %ld",
606: file, linenum);
607: break;
608: }
609: }
610: restore_uid();
611: fclose(f);
612: key_free(found);
613: return found_key;
1.18 ! markus 614: }
! 615:
! 616: struct passwd *
! 617: pwcopy(struct passwd *pw)
! 618: {
! 619: struct passwd *copy = xmalloc(sizeof(*copy));
! 620: memset(copy, 0, sizeof(*copy));
! 621: copy->pw_name = xstrdup(pw->pw_name);
! 622: copy->pw_passwd = xstrdup(pw->pw_passwd);
! 623: copy->pw_uid = pw->pw_uid;
! 624: copy->pw_gid = pw->pw_gid;
! 625: copy->pw_class = xstrdup(pw->pw_class);
! 626: copy->pw_dir = xstrdup(pw->pw_dir);
! 627: copy->pw_shell = xstrdup(pw->pw_shell);
! 628: return copy;
1.1 markus 629: }