diff options
-rw-r--r-- | Makefile.GNU | 4 | ||||
-rw-r--r-- | auth-passwd.c | 31 | ||||
-rw-r--r-- | includes.h | 8 | ||||
-rw-r--r-- | sshd.c | 208 |
4 files changed, 147 insertions, 104 deletions
diff --git a/Makefile.GNU b/Makefile.GNU index f36bdb3df..097199527 100644 --- a/Makefile.GNU +++ b/Makefile.GNU | |||
@@ -1,8 +1,8 @@ | |||
1 | OPT_FLAGS=-g | 1 | OPT_FLAGS=-g |
2 | CFLAGS=$(OPT_FLAGS) -Wall -DETCDIR=\"/etc/ssh\" -DHAVE_PAM | 2 | CFLAGS=$(OPT_FLAGS) -Wall -DETCDIR=\"/etc/ssh\" -DHAVE_PAM -DHAVE_PWDB |
3 | TARGETS=bin/libssh.a bin/ssh bin/sshd bin/ssh-add bin/ssh-keygen bin/ssh-agent bin/scp | 3 | TARGETS=bin/libssh.a bin/ssh bin/sshd bin/ssh-add bin/ssh-keygen bin/ssh-agent bin/scp |
4 | LFLAGS=-L./bin | 4 | LFLAGS=-L./bin |
5 | LIBS=-lssh -lcrypto -lz -lutil -lpam -ldl | 5 | LIBS=-lssh -lcrypto -lz -lutil -lpwdb -lpam -ldl |
6 | AR=ar | 6 | AR=ar |
7 | RANLIB=ranlib | 7 | RANLIB=ranlib |
8 | 8 | ||
diff --git a/auth-passwd.c b/auth-passwd.c index 7d6846789..61f66fedd 100644 --- a/auth-passwd.c +++ b/auth-passwd.c | |||
@@ -15,7 +15,7 @@ the password is valid for the user. | |||
15 | */ | 15 | */ |
16 | 16 | ||
17 | #include "includes.h" | 17 | #include "includes.h" |
18 | RCSID("$Id: auth-passwd.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); | 18 | RCSID("$Id: auth-passwd.c,v 1.2 1999/10/27 13:42:05 damien Exp $"); |
19 | 19 | ||
20 | #include "packet.h" | 20 | #include "packet.h" |
21 | #include "ssh.h" | 21 | #include "ssh.h" |
@@ -26,14 +26,6 @@ RCSID("$Id: auth-passwd.c,v 1.1 1999/10/27 03:42:43 damien Exp $"); | |||
26 | extern char *ticket; | 26 | extern char *ticket; |
27 | #endif /* KRB4 */ | 27 | #endif /* KRB4 */ |
28 | 28 | ||
29 | #ifdef HAVE_PAM | ||
30 | #include <security/pam_appl.h> | ||
31 | extern pam_handle_t *pamh; | ||
32 | extern int retval; | ||
33 | extern char* pampasswd; | ||
34 | extern int origretval; | ||
35 | #endif /* HAVE_PAM */ | ||
36 | |||
37 | /* Tries to authenticate the user using password. Returns true if | 29 | /* Tries to authenticate the user using password. Returns true if |
38 | authentication succeeds. */ | 30 | authentication succeeds. */ |
39 | 31 | ||
@@ -58,26 +50,6 @@ int auth_password(struct passwd *pw, const char *password) | |||
58 | if (pw == NULL) | 50 | if (pw == NULL) |
59 | return 0; | 51 | return 0; |
60 | 52 | ||
61 | #ifdef HAVE_PAM | ||
62 | retval = origretval; | ||
63 | |||
64 | pampasswd = xstrdup(password); | ||
65 | |||
66 | if (retval == PAM_SUCCESS) | ||
67 | retval = pam_authenticate ((pam_handle_t *)pamh, 0); | ||
68 | |||
69 | if (retval == PAM_SUCCESS) | ||
70 | retval = pam_acct_mgmt ((pam_handle_t *)pamh, 0); | ||
71 | |||
72 | xfree(pampasswd); | ||
73 | |||
74 | if (retval == PAM_SUCCESS) | ||
75 | retval = pam_open_session ((pam_handle_t *)pamh, 0); | ||
76 | |||
77 | return (retval == PAM_SUCCESS); | ||
78 | |||
79 | #else /* HAVE_PAM */ | ||
80 | |||
81 | #ifdef SKEY | 53 | #ifdef SKEY |
82 | if (options.skey_authentication == 1) { | 54 | if (options.skey_authentication == 1) { |
83 | if (strncasecmp(password, "s/key", 5) == 0) { | 55 | if (strncasecmp(password, "s/key", 5) == 0) { |
@@ -205,5 +177,4 @@ int auth_password(struct passwd *pw, const char *password) | |||
205 | 177 | ||
206 | /* Authentication is accepted if the encrypted passwords are identical. */ | 178 | /* Authentication is accepted if the encrypted passwords are identical. */ |
207 | return (strcmp(encrypted_password, pw->pw_passwd) == 0); | 179 | return (strcmp(encrypted_password, pw->pw_passwd) == 0); |
208 | #endif /* HAVE_PAM */ | ||
209 | } | 180 | } |
diff --git a/includes.h b/includes.h index 8fa174bd6..b2e8c1e78 100644 --- a/includes.h +++ b/includes.h | |||
@@ -61,6 +61,14 @@ static /**/const char *const rcsid[] = { (char *)rcsid, "\100(#)" msg } | |||
61 | #include "mktemp.h" | 61 | #include "mktemp.h" |
62 | #include "strlcpy.h" | 62 | #include "strlcpy.h" |
63 | 63 | ||
64 | #ifdef HAVE_PAM | ||
65 | #include <security/pam_appl.h> | ||
66 | #endif /* HAVE_PAM */ | ||
67 | |||
68 | #ifdef HAVE_PWDB | ||
69 | #include <pwdb/pwdb_map.h> | ||
70 | #endif /* HAVE_PWDB */ | ||
71 | |||
64 | /* Define this to be the path of the xauth program. */ | 72 | /* Define this to be the path of the xauth program. */ |
65 | #ifndef XAUTH_PATH | 73 | #ifndef XAUTH_PATH |
66 | #define XAUTH_PATH "/usr/X11R6/bin/xauth" | 74 | #define XAUTH_PATH "/usr/X11R6/bin/xauth" |
@@ -18,7 +18,7 @@ agent connections. | |||
18 | */ | 18 | */ |
19 | 19 | ||
20 | #include "includes.h" | 20 | #include "includes.h" |
21 | RCSID("$Id: sshd.c,v 1.1 1999/10/27 03:42:46 damien Exp $"); | 21 | RCSID("$Id: sshd.c,v 1.2 1999/10/27 13:42:05 damien Exp $"); |
22 | 22 | ||
23 | #include "xmalloc.h" | 23 | #include "xmalloc.h" |
24 | #include "rsa.h" | 24 | #include "rsa.h" |
@@ -47,14 +47,6 @@ int deny_severity = LOG_WARNING; | |||
47 | char *ticket = NULL; | 47 | char *ticket = NULL; |
48 | #endif /* KRB4 */ | 48 | #endif /* KRB4 */ |
49 | 49 | ||
50 | #ifdef HAVE_PAM | ||
51 | #include <security/pam_appl.h> | ||
52 | struct pam_handle_t *pamh=NULL; | ||
53 | char *pampasswd=NULL; | ||
54 | int retval; | ||
55 | int origretval; | ||
56 | #endif /* HAVE_PAM */ | ||
57 | |||
58 | /* Local Xauthority file. */ | 50 | /* Local Xauthority file. */ |
59 | char *xauthfile = NULL; | 51 | char *xauthfile = NULL; |
60 | 52 | ||
@@ -139,69 +131,127 @@ void do_child(const char *command, struct passwd *pw, const char *term, | |||
139 | #ifdef HAVE_PAM | 131 | #ifdef HAVE_PAM |
140 | static int pamconv(int num_msg, const struct pam_message **msg, | 132 | static int pamconv(int num_msg, const struct pam_message **msg, |
141 | struct pam_response **resp, void *appdata_ptr); | 133 | struct pam_response **resp, void *appdata_ptr); |
134 | void do_pam_authentication(const char *username, const char *password, | ||
135 | const char *remote_user, const char *remote_host); | ||
136 | void pam_cleanup_proc(void *context); | ||
142 | 137 | ||
143 | static struct pam_conv conv = { | 138 | static struct pam_conv conv = { |
144 | pamconv, | 139 | pamconv, |
145 | NULL | 140 | NULL |
146 | }; | 141 | }; |
142 | struct pam_handle_t *pamh = NULL; | ||
143 | const char *pampasswd = NULL; | ||
147 | 144 | ||
148 | static int pamconv(int num_msg, const struct pam_message **msg, | 145 | static int pamconv(int num_msg, const struct pam_message **msg, |
149 | struct pam_response **resp, void *appdata_ptr) | 146 | struct pam_response **resp, void *appdata_ptr) |
150 | { | 147 | { |
151 | int count = 0; | 148 | int count = 0; |
152 | int replies = 0; | ||
153 | struct pam_response *reply = NULL; | 149 | struct pam_response *reply = NULL; |
154 | int size = sizeof(struct pam_response); | ||
155 | 150 | ||
151 | /* PAM will free this later */ | ||
152 | reply = malloc(num_msg * sizeof(*reply)); | ||
153 | if (reply == NULL) | ||
154 | return PAM_CONV_ERR; | ||
155 | |||
156 | for(count = 0; count < num_msg; count++) | 156 | for(count = 0; count < num_msg; count++) |
157 | { | 157 | { |
158 | switch (msg[count]->msg_style) | 158 | switch (msg[count]->msg_style) |
159 | { | 159 | { |
160 | case PAM_PROMPT_ECHO_ON: | ||
161 | case PAM_PROMPT_ECHO_OFF: | 160 | case PAM_PROMPT_ECHO_OFF: |
162 | if (reply == NULL) | 161 | if (pampasswd == NULL) |
163 | reply = xmalloc(size); | 162 | { |
164 | else | ||
165 | reply = realloc(reply, size); | ||
166 | |||
167 | if (reply == NULL) | ||
168 | return PAM_CONV_ERR; | ||
169 | |||
170 | size += sizeof(struct pam_response); | ||
171 | |||
172 | reply[replies].resp_retcode = PAM_SUCCESS; | ||
173 | |||
174 | reply[replies++].resp = xstrdup(pampasswd); | ||
175 | /* PAM frees resp */ | ||
176 | break; | ||
177 | |||
178 | case PAM_TEXT_INFO: | ||
179 | /* ignore it... */ | ||
180 | break; | ||
181 | |||
182 | case PAM_ERROR_MSG: | ||
183 | default: | ||
184 | /* Must be an error of some sort... */ | ||
185 | if (reply != NULL) | ||
186 | free(reply); | 163 | free(reply); |
164 | return PAM_CONV_ERR; | ||
165 | } | ||
166 | reply[count].resp_retcode = PAM_SUCCESS; | ||
167 | reply[count].resp = xstrdup(pampasswd); | ||
168 | break; | ||
169 | |||
170 | case PAM_TEXT_INFO: | ||
171 | reply[count].resp_retcode = PAM_SUCCESS; | ||
172 | reply[count].resp = xstrdup(""); | ||
173 | break; | ||
187 | 174 | ||
188 | return PAM_CONV_ERR; | 175 | case PAM_PROMPT_ECHO_ON: |
189 | } | 176 | case PAM_ERROR_MSG: |
177 | default: | ||
178 | free(reply); | ||
179 | return PAM_CONV_ERR; | ||
180 | } | ||
190 | } | 181 | } |
191 | 182 | ||
192 | if (reply != NULL) | 183 | *resp = reply; |
193 | *resp = reply; | ||
194 | 184 | ||
195 | return PAM_SUCCESS; | 185 | return PAM_SUCCESS; |
196 | } | 186 | } |
197 | 187 | ||
198 | void pam_cleanup_proc(void *context) | 188 | void pam_cleanup_proc(void *context) |
199 | { | 189 | { |
200 | if (retval == PAM_SUCCESS) | 190 | int retval; |
191 | |||
192 | if (pamh != NULL) | ||
193 | { | ||
201 | retval = pam_close_session((pam_handle_t *)pamh, 0); | 194 | retval = pam_close_session((pam_handle_t *)pamh, 0); |
202 | 195 | ||
203 | if (pam_end((pam_handle_t *)pamh, retval) != PAM_SUCCESS) | 196 | if (pam_end((pam_handle_t *)pamh, retval) != PAM_SUCCESS) |
204 | log("Cannot release PAM authentication."); | 197 | log("Cannot release PAM authentication."); |
198 | } | ||
199 | } | ||
200 | |||
201 | void do_pam_authentication(const char *username, const char *password, const char *remote_user, const char *remote_host) | ||
202 | { | ||
203 | int pam_auth_ok = 1; | ||
204 | |||
205 | pampasswd = password; | ||
206 | |||
207 | do | ||
208 | { | ||
209 | if (PAM_SUCCESS != pam_start("ssh", username, &conv, (pam_handle_t**)&pamh)) | ||
210 | { | ||
211 | pam_auth_ok = 0; | ||
212 | break; | ||
213 | } | ||
214 | |||
215 | fatal_add_cleanup(&pam_cleanup_proc, NULL); | ||
216 | |||
217 | if (remote_host && (PAM_SUCCESS != pam_set_item((pam_handle_t *)pamh, PAM_RHOST, remote_host))) | ||
218 | { | ||
219 | pam_auth_ok = 0; | ||
220 | break; | ||
221 | } | ||
222 | |||
223 | if (remote_user && (PAM_SUCCESS != pam_set_item((pam_handle_t *)pamh, PAM_RUSER, remote_user))) | ||
224 | { | ||
225 | pam_auth_ok = 0; | ||
226 | break; | ||
227 | } | ||
228 | |||
229 | if (PAM_SUCCESS != pam_authenticate((pam_handle_t *)pamh, 0)) | ||
230 | { | ||
231 | pam_auth_ok = 0; | ||
232 | break; | ||
233 | } | ||
234 | |||
235 | if (PAM_SUCCESS != pam_acct_mgmt((pam_handle_t *)pamh, 0)) | ||
236 | { | ||
237 | pam_auth_ok = 0; | ||
238 | break; | ||
239 | } | ||
240 | |||
241 | if (PAM_SUCCESS != pam_open_session((pam_handle_t *)pamh, 0)) | ||
242 | { | ||
243 | pam_auth_ok = 0; | ||
244 | break; | ||
245 | } | ||
246 | } while (0); | ||
247 | |||
248 | if (!pam_auth_ok) | ||
249 | { | ||
250 | packet_start(SSH_SMSG_FAILURE); | ||
251 | packet_send(); | ||
252 | packet_write_wait(); | ||
253 | packet_disconnect("PAM authentication failed."); | ||
254 | } | ||
205 | } | 255 | } |
206 | #endif /* HAVE_PAM */ | 256 | #endif /* HAVE_PAM */ |
207 | 257 | ||
@@ -788,13 +838,19 @@ main(int ac, char **av) | |||
788 | log("Closing connection to %.100s", inet_ntoa(sin.sin_addr)); | 838 | log("Closing connection to %.100s", inet_ntoa(sin.sin_addr)); |
789 | 839 | ||
790 | #ifdef HAVE_PAM | 840 | #ifdef HAVE_PAM |
791 | if (retval == PAM_SUCCESS) | 841 | { |
792 | retval = pam_close_session((pam_handle_t *)pamh, 0); | 842 | int retval; |
843 | |||
844 | if (pamh != NULL) | ||
845 | { | ||
846 | retval = pam_close_session((pam_handle_t *)pamh, 0); | ||
793 | 847 | ||
794 | if (pam_end((pam_handle_t *)pamh, retval) != PAM_SUCCESS) | 848 | if (pam_end((pam_handle_t *)pamh, retval) != PAM_SUCCESS) |
795 | log("Cannot release PAM authentication."); | 849 | log("Cannot release PAM authentication."); |
796 | 850 | ||
797 | fatal_remove_cleanup(&pam_cleanup_proc, NULL); | 851 | fatal_remove_cleanup(&pam_cleanup_proc, NULL); |
852 | } | ||
853 | } | ||
798 | #endif /* HAVE_PAM */ | 854 | #endif /* HAVE_PAM */ |
799 | 855 | ||
800 | packet_close(); | 856 | packet_close(); |
@@ -1078,14 +1134,11 @@ do_authentication(char *user, int privileged_port) | |||
1078 | int type; | 1134 | int type; |
1079 | int authenticated = 0; | 1135 | int authenticated = 0; |
1080 | int authentication_failures = 0; | 1136 | int authentication_failures = 0; |
1081 | char *password; | 1137 | char *password = NULL; |
1082 | struct passwd *pw, pwcopy; | 1138 | struct passwd *pw, pwcopy; |
1083 | char *client_user; | 1139 | char *client_user = NULL; |
1084 | unsigned int client_host_key_bits; | 1140 | unsigned int client_host_key_bits; |
1085 | BIGNUM *client_host_key_e, *client_host_key_n; | 1141 | BIGNUM *client_host_key_e, *client_host_key_n; |
1086 | #ifdef HAVE_PAM | ||
1087 | int pam_auth_ok; | ||
1088 | #endif /* HAVE_PAM */ | ||
1089 | 1142 | ||
1090 | #ifdef AFS | 1143 | #ifdef AFS |
1091 | /* If machine has AFS, set process authentication group. */ | 1144 | /* If machine has AFS, set process authentication group. */ |
@@ -1097,21 +1150,7 @@ do_authentication(char *user, int privileged_port) | |||
1097 | 1150 | ||
1098 | /* Verify that the user is a valid user. */ | 1151 | /* Verify that the user is a valid user. */ |
1099 | pw = getpwnam(user); | 1152 | pw = getpwnam(user); |
1100 | #ifdef HAVE_PAM | ||
1101 | if ((pw != NULL) && allowed_user(pw)) | ||
1102 | { | ||
1103 | /* Initialise PAM */ | ||
1104 | retval = pam_start("ssh", pw->pw_name, &conv, (pam_handle_t **)&pamh); | ||
1105 | fatal_add_cleanup(&pam_cleanup_proc, NULL); | ||
1106 | origretval = retval; | ||
1107 | if (retval == PAM_SUCCESS) | ||
1108 | pam_auth_ok = 1; | ||
1109 | } | ||
1110 | |||
1111 | if (pam_auth_ok == 0) | ||
1112 | #else /* HAVE_PAM */ | ||
1113 | if (!pw || !allowed_user(pw)) | 1153 | if (!pw || !allowed_user(pw)) |
1114 | #endif /* HAVE_PAM */ | ||
1115 | { | 1154 | { |
1116 | /* The user does not exist or access is denied, | 1155 | /* The user does not exist or access is denied, |
1117 | but fake indication that authentication is needed. */ | 1156 | but fake indication that authentication is needed. */ |
@@ -1306,12 +1345,16 @@ do_authentication(char *user, int privileged_port) | |||
1306 | log("Rhosts authentication accepted for %.100s, remote %.100s on %.700s.", | 1345 | log("Rhosts authentication accepted for %.100s, remote %.100s on %.700s.", |
1307 | user, client_user, get_canonical_hostname()); | 1346 | user, client_user, get_canonical_hostname()); |
1308 | authenticated = 1; | 1347 | authenticated = 1; |
1348 | #ifndef HAVE_PAM | ||
1309 | xfree(client_user); | 1349 | xfree(client_user); |
1350 | #endif /* HAVE_PAM */ | ||
1310 | break; | 1351 | break; |
1311 | } | 1352 | } |
1312 | log("Rhosts authentication failed for %.100s, remote %.100s.", | 1353 | log("Rhosts authentication failed for %.100s, remote %.100s.", |
1313 | user, client_user); | 1354 | user, client_user); |
1355 | #ifndef HAVE_PAM | ||
1314 | xfree(client_user); | 1356 | xfree(client_user); |
1357 | #endif /* HAVE_PAM */ | ||
1315 | break; | 1358 | break; |
1316 | 1359 | ||
1317 | case SSH_CMSG_AUTH_RHOSTS_RSA: | 1360 | case SSH_CMSG_AUTH_RHOSTS_RSA: |
@@ -1354,14 +1397,18 @@ do_authentication(char *user, int privileged_port) | |||
1354 | { | 1397 | { |
1355 | /* Authentication accepted. */ | 1398 | /* Authentication accepted. */ |
1356 | authenticated = 1; | 1399 | authenticated = 1; |
1400 | #ifndef HAVE_PAM | ||
1357 | xfree(client_user); | 1401 | xfree(client_user); |
1402 | #endif /* HAVE_PAM */ | ||
1358 | BN_clear_free(client_host_key_e); | 1403 | BN_clear_free(client_host_key_e); |
1359 | BN_clear_free(client_host_key_n); | 1404 | BN_clear_free(client_host_key_n); |
1360 | break; | 1405 | break; |
1361 | } | 1406 | } |
1362 | log("Rhosts authentication failed for %.100s, remote %.100s.", | 1407 | log("Rhosts authentication failed for %.100s, remote %.100s.", |
1363 | user, client_user); | 1408 | user, client_user); |
1364 | xfree(client_user); | 1409 | #ifndef HAVE_PAM |
1410 | xfree(client_user); | ||
1411 | #endif /* HAVE_PAM */ | ||
1365 | BN_clear_free(client_host_key_e); | 1412 | BN_clear_free(client_host_key_e); |
1366 | BN_clear_free(client_host_key_n); | 1413 | BN_clear_free(client_host_key_n); |
1367 | break; | 1414 | break; |
@@ -1412,6 +1459,12 @@ do_authentication(char *user, int privileged_port) | |||
1412 | packet_integrity_check(plen, 4 + passw_len, type); | 1459 | packet_integrity_check(plen, 4 + passw_len, type); |
1413 | } | 1460 | } |
1414 | 1461 | ||
1462 | #ifdef HAVE_PAM | ||
1463 | /* Authentication will be handled later */ | ||
1464 | /* keep password around until then */ | ||
1465 | authenticated = 1; | ||
1466 | break; | ||
1467 | #else /* HAVE_PAM */ | ||
1415 | /* Try authentication with the password. */ | 1468 | /* Try authentication with the password. */ |
1416 | if (auth_password(pw, password)) | 1469 | if (auth_password(pw, password)) |
1417 | { | 1470 | { |
@@ -1427,6 +1480,7 @@ do_authentication(char *user, int privileged_port) | |||
1427 | memset(password, 0, strlen(password)); | 1480 | memset(password, 0, strlen(password)); |
1428 | xfree(password); | 1481 | xfree(password); |
1429 | break; | 1482 | break; |
1483 | #endif /* HAVE_PAM */ | ||
1430 | 1484 | ||
1431 | case SSH_CMSG_AUTH_TIS: | 1485 | case SSH_CMSG_AUTH_TIS: |
1432 | /* TIS Authentication is unsupported */ | 1486 | /* TIS Authentication is unsupported */ |
@@ -1464,6 +1518,20 @@ do_authentication(char *user, int privileged_port) | |||
1464 | get_canonical_hostname()); | 1518 | get_canonical_hostname()); |
1465 | } | 1519 | } |
1466 | 1520 | ||
1521 | #ifdef HAVE_PAM | ||
1522 | do_pam_authentication(pw->pw_name, password, client_user, get_canonical_hostname()); | ||
1523 | |||
1524 | /* Clean up */ | ||
1525 | if (client_user != NULL) | ||
1526 | xfree(client_user); | ||
1527 | |||
1528 | if (password != NULL) | ||
1529 | { | ||
1530 | memset(password, 0, strlen(password)); | ||
1531 | xfree(password); | ||
1532 | } | ||
1533 | #endif /* HAVE_PAM */ | ||
1534 | |||
1467 | /* The user has been authenticated and accepted. */ | 1535 | /* The user has been authenticated and accepted. */ |
1468 | packet_start(SSH_SMSG_SUCCESS); | 1536 | packet_start(SSH_SMSG_SUCCESS); |
1469 | packet_send(); | 1537 | packet_send(); |
@@ -2151,10 +2219,6 @@ void do_child(const char *command, struct passwd *pw, const char *term, | |||
2151 | exit(254); | 2219 | exit(254); |
2152 | } | 2220 | } |
2153 | 2221 | ||
2154 | /* Set login name in the kernel. */ | ||
2155 | if (setlogin(pw->pw_name) < 0) | ||
2156 | error("setlogin failed: %s", strerror(errno)); | ||
2157 | |||
2158 | /* Set uid, gid, and groups. */ | 2222 | /* Set uid, gid, and groups. */ |
2159 | /* Login(1) does this as well, and it needs uid 0 for the "-h" switch, | 2223 | /* Login(1) does this as well, and it needs uid 0 for the "-h" switch, |
2160 | so we let login(1) to this for us. */ | 2224 | so we let login(1) to this for us. */ |