Mercurial > dropbear
comparison svr-authpam.c @ 925:bae0b34bc059 pam
Better PAM through recursion
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 12 Mar 2014 23:40:02 +0800 |
parents | fee485ce81eb |
children | 696205e3dc99 |
comparison
equal
deleted
inserted
replaced
923:25b7ed9fe854 | 925:bae0b34bc059 |
---|---|
28 #include "includes.h" | 28 #include "includes.h" |
29 #include "session.h" | 29 #include "session.h" |
30 #include "buffer.h" | 30 #include "buffer.h" |
31 #include "dbutil.h" | 31 #include "dbutil.h" |
32 #include "auth.h" | 32 #include "auth.h" |
33 #include "ssh.h" | |
33 | 34 |
34 #ifdef ENABLE_SVR_PAM_AUTH | 35 #ifdef ENABLE_SVR_PAM_AUTH |
35 | 36 |
36 #if defined(HAVE_SECURITY_PAM_APPL_H) | 37 #if defined(HAVE_SECURITY_PAM_APPL_H) |
37 #include <security/pam_appl.h> | 38 #include <security/pam_appl.h> |
38 #elif defined (HAVE_PAM_PAM_APPL_H) | 39 #elif defined (HAVE_PAM_PAM_APPL_H) |
39 #include <pam/pam_appl.h> | 40 #include <pam/pam_appl.h> |
40 #endif | 41 #endif |
41 | 42 |
42 struct UserDataS { | 43 enum |
43 char* user; | 44 { |
44 char* passwd; | 45 DROPBEAR_PAM_RETCODE_FILL = 100, |
46 DROPBEAR_PAM_RETCODE_SKIP = 101, | |
45 }; | 47 }; |
48 | |
49 | |
50 void recv_msg_userauth_info_response() { | |
51 unsigned int i, p; | |
52 unsigned int num_ssh_resp; | |
53 if (!ses.authstate.pam_response) { | |
54 /* A response was sent unprompted */ | |
55 send_msg_userauth_failure(0, 1); | |
56 return; | |
57 } | |
58 | |
59 if (ses.recursion_count != 2) { | |
60 dropbear_exit("PAM failure"); | |
61 } | |
62 | |
63 num_ssh_resp = buf_getint(ses.payload); | |
64 ses.authstate.pam_status = DROPBEAR_SUCCESS; | |
65 | |
66 for (i = 0, p = 0; i < ses.authstate.pam_num_response; i++) { | |
67 struct pam_response *resp = ses.authstate.pam_response[i]; | |
68 resp->resp = NULL; | |
69 | |
70 if (resp->resp_retcode == DROPBEAR_PAM_RETCODE_FILL) { | |
71 if (p >= num_ssh_resp) { | |
72 TRACE(("Too many PAM responses")) | |
73 ses.authstate.pam_status = DROPBEAR_FAILURE; | |
74 } else { | |
75 /* TODO convert to UTF8? */ | |
76 resp->resp = buf_getstring(ses.payload, NULL); | |
77 } | |
78 p++; | |
79 } | |
80 } | |
81 | |
82 if (p != num_ssh_resp) { | |
83 TRACE(("Not enough PAM responses")) | |
84 ses.authstate.pam_status = DROPBEAR_FAILURE; | |
85 } | |
86 | |
87 ses.exit_recursion = 1; | |
88 } | |
89 | |
90 static void | |
91 send_msg_userauth_info_request(unsigned int num_msg, const struct pam_message **msgs, | |
92 struct pam_response **respp) { | |
93 unsigned int i; | |
94 unsigned int pos, instruction_size, instruction_count; | |
95 CHECKCLEARTOWRITE(); | |
96 | |
97 buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_INFO_REQUEST); | |
98 | |
99 /* name */ | |
100 buf_putstring(ses.writepayload, ses.authstate.pw_name, 0); | |
101 | |
102 /* any informational messages are send as an instruction */ | |
103 pos = ses.writepayload->pos; | |
104 /* will be filled out later if required */ | |
105 buf_putint(ses.writepayload, 0); | |
106 instruction_size = 0; | |
107 instruction_count = 0; | |
108 for (i = 0; i < num_msg; i++) { | |
109 const struct pam_message *msg = msgs[i]; | |
110 if (msg->msg_style == PAM_ERROR_MSG) | |
111 { | |
112 buf_putbytes(ses.writepayload, "Error: ", strlen("Error: ")); | |
113 instruction_size += strlen("Error: "); | |
114 } | |
115 if (msg->msg_style == PAM_ERROR_MSG || msg->msg_style == PAM_TEXT_INFO) | |
116 { | |
117 buf_putbytes(ses.writepayload, msg->msg, strlen(msg->msg)); | |
118 buf_putbyte(ses.writepayload, '\n'); | |
119 instruction_size += strlen(msg->msg)+1; | |
120 instruction_count++; | |
121 respp[i]->resp_retcode = DROPBEAR_PAM_RETCODE_SKIP; | |
122 } | |
123 else | |
124 { | |
125 respp[i]->resp_retcode = DROPBEAR_PAM_RETCODE_FILL; | |
126 } | |
127 } | |
128 | |
129 if (instruction_size > 0) | |
130 { | |
131 /* Remove trailing newline */ | |
132 instruction_size--; | |
133 buf_incrlen(ses.writepayload, -1); | |
134 | |
135 /* Put the instruction string length */ | |
136 buf_setpos(ses.writepayload, pos); | |
137 buf_putint(ses.writepayload, instruction_size); | |
138 buf_setpos(ses.writepayload, ses.writepayload->len); | |
139 } | |
140 | |
141 /* language (deprecated) */ | |
142 buf_putstring(ses.writepayload, "", 0); | |
143 | |
144 /* num-prompts */ | |
145 buf_putint(ses.writepayload, num_msg-instruction_count); | |
146 | |
147 for (i = 0; i < num_msg; i++) { | |
148 const struct pam_message *msg = msgs[i]; | |
149 if (msg->msg_style != PAM_PROMPT_ECHO_OFF && msg->msg_style != PAM_PROMPT_ECHO_ON) { | |
150 /* was handled in "instruction" above */ | |
151 continue; | |
152 } | |
153 | |
154 /* prompt */ | |
155 buf_putstring(ses.writepayload, msg->msg, strlen(msg->msg)); | |
156 | |
157 /* echo */ | |
158 buf_putbool(ses.writepayload, msg->msg_style == PAM_PROMPT_ECHO_ON); | |
159 } | |
160 | |
161 encrypt_packet(); | |
162 } | |
46 | 163 |
47 /* PAM conversation function - for now we only handle one message */ | 164 /* PAM conversation function - for now we only handle one message */ |
48 int | 165 int |
49 pamConvFunc(int num_msg, | 166 pamConvFunc(int num_msg, |
50 const struct pam_message **msg, | 167 const struct pam_message **msgs, |
51 struct pam_response **respp, | 168 struct pam_response **respp, |
52 void *appdata_ptr) { | 169 void *UNUSED(appdata_ptr)) { |
53 | 170 |
54 int rc = PAM_SUCCESS; | 171 int ret = PAM_SYSTEM_ERR; |
55 struct pam_response* resp = NULL; | |
56 struct UserDataS* userDatap = (struct UserDataS*) appdata_ptr; | |
57 unsigned int msg_len = 0; | |
58 unsigned int i = 0; | |
59 char * compare_message = NULL; | |
60 | 172 |
61 TRACE(("enter pamConvFunc")) | 173 TRACE(("enter pamConvFunc")) |
62 | 174 |
63 if (num_msg != 1) { | 175 if (ses.recursion_count != 1) { |
64 /* If you're getting here - Dropbear probably can't support your pam | 176 dropbear_exit("PAM failure"); |
65 * modules. This whole file is a bit of a hack around lack of | 177 } |
66 * asynchronocity in PAM anyway. */ | 178 |
67 dropbear_log(LOG_INFO, "pamConvFunc() called with >1 messages: not supported."); | 179 *respp = m_malloc(sizeof(struct pam_response) * num_msg); |
68 return PAM_CONV_ERR; | 180 |
69 } | 181 send_msg_userauth_info_request(num_msg, msgs, respp); |
70 | 182 |
71 /* make a copy we can strip */ | 183 ses.authstate.pam_num_response = num_msg; |
72 compare_message = m_strdup((*msg)->msg); | 184 ses.authstate.pam_response = respp; |
185 ses.authstate.pam_status = DROPBEAR_FAILURE; | |
73 | 186 |
74 /* Make the string lowercase. */ | 187 buf_free(ses.payload); |
75 msg_len = strlen(compare_message); | 188 ses.payload = NULL; |
76 for (i = 0; i < msg_len; i++) { | 189 |
77 compare_message[i] = tolower(compare_message[i]); | 190 /* Recurse! This will return once a SSH_MSG_USERAUTH_INFO_RESPONSE |
78 } | 191 has been received, with the ses.authstate.pam_* fields populated */ |
79 | 192 session_loop(); |
80 /* If the string ends with ": ", remove the space. | 193 |
81 ie "login: " vs "login:" */ | 194 if (ses.authstate.pam_status == DROPBEAR_FAILURE) { |
82 if (msg_len > 2 | 195 ret = PAM_CONV_ERR; |
83 && compare_message[msg_len-2] == ':' | 196 m_free(*respp); |
84 && compare_message[msg_len-1] == ' ') { | 197 } else { |
85 compare_message[msg_len-1] = '\0'; | 198 ses.authstate.pam_response = NULL; |
86 } | 199 ret = PAM_SUCCESS; |
87 | 200 } |
88 switch((*msg)->msg_style) { | 201 |
89 | 202 return ret; |
90 case PAM_PROMPT_ECHO_OFF: | 203 } |
91 | 204 |
92 if (!(strcmp(compare_message, "password:") == 0)) { | |
93 /* We don't recognise the prompt as asking for a password, | |
94 so can't handle it. Add more above as required for | |
95 different pam modules/implementations. If you need | |
96 to add an entry here please mail the Dropbear developer */ | |
97 dropbear_log(LOG_NOTICE, "PAM unknown prompt '%s' (no echo)", | |
98 compare_message); | |
99 rc = PAM_CONV_ERR; | |
100 break; | |
101 } | |
102 | |
103 /* You have to read the PAM module-writers' docs (do we look like | |
104 * module writers? no.) to find out that the module will | |
105 * free the pam_response and its resp element - ie we _must_ malloc | |
106 * it here */ | |
107 resp = (struct pam_response*) m_malloc(sizeof(struct pam_response)); | |
108 memset(resp, 0, sizeof(struct pam_response)); | |
109 | |
110 resp->resp = m_strdup(userDatap->passwd); | |
111 m_burn(userDatap->passwd, strlen(userDatap->passwd)); | |
112 (*respp) = resp; | |
113 break; | |
114 | |
115 | |
116 case PAM_PROMPT_ECHO_ON: | |
117 | |
118 if (!( | |
119 (strcmp(compare_message, "login:" ) == 0) | |
120 || (strcmp(compare_message, "please enter username:") == 0) | |
121 || (strcmp(compare_message, "username:") == 0) | |
122 )) { | |
123 /* We don't recognise the prompt as asking for a username, | |
124 so can't handle it. Add more above as required for | |
125 different pam modules/implementations. If you need | |
126 to add an entry here please mail the Dropbear developer */ | |
127 dropbear_log(LOG_NOTICE, "PAM unknown prompt '%s' (with echo)", | |
128 compare_message); | |
129 rc = PAM_CONV_ERR; | |
130 break; | |
131 } | |
132 | |
133 /* You have to read the PAM module-writers' docs (do we look like | |
134 * module writers? no.) to find out that the module will | |
135 * free the pam_response and its resp element - ie we _must_ malloc | |
136 * it here */ | |
137 resp = (struct pam_response*) m_malloc(sizeof(struct pam_response)); | |
138 memset(resp, 0, sizeof(struct pam_response)); | |
139 | |
140 resp->resp = m_strdup(userDatap->user); | |
141 TRACE(("userDatap->user='%s'", userDatap->user)) | |
142 (*respp) = resp; | |
143 break; | |
144 | |
145 case PAM_ERROR_MSG: | |
146 case PAM_TEXT_INFO: | |
147 | |
148 if (msg_len > 0) { | |
149 buffer * pam_err = buf_new(msg_len + 4); | |
150 buf_setpos(pam_err, 0); | |
151 buf_putbytes(pam_err, "\r\n", 2); | |
152 buf_putbytes(pam_err, (*msg)->msg, msg_len); | |
153 buf_putbytes(pam_err, "\r\n", 2); | |
154 buf_setpos(pam_err, 0); | |
155 | |
156 send_msg_userauth_banner(pam_err); | |
157 buf_free(pam_err); | |
158 } | |
159 break; | |
160 | |
161 default: | |
162 TRACE(("Unknown message type")) | |
163 rc = PAM_CONV_ERR; | |
164 break; | |
165 } | |
166 | |
167 m_free(compare_message); | |
168 TRACE(("leave pamConvFunc, rc %d", rc)) | |
169 | |
170 return rc; | |
171 } | |
172 | |
173 /* Process a password auth request, sending success or failure messages as | |
174 * appropriate. To the client it looks like it's doing normal password auth (as | |
175 * opposed to keyboard-interactive or something), so the pam module has to be | |
176 * fairly standard (ie just "what's your username, what's your password, OK"). | |
177 * | |
178 * Keyboard interactive would be a lot nicer, but since PAM is synchronous, it | |
179 * gets very messy trying to send the interactive challenges, and read the | |
180 * interactive responses, over the network. */ | |
181 void svr_auth_pam() { | 205 void svr_auth_pam() { |
182 | 206 int rc; |
183 struct UserDataS userData = {NULL, NULL}; | |
184 struct pam_conv pamConv = { | 207 struct pam_conv pamConv = { |
185 pamConvFunc, | 208 pamConvFunc, |
186 &userData /* submitted to pamvConvFunc as appdata_ptr */ | 209 NULL |
187 }; | 210 }; |
188 | 211 |
189 pam_handle_t* pamHandlep = NULL; | 212 pam_handle_t* pamHandlep = NULL; |
190 | 213 |
191 unsigned char * password = NULL; | 214 /* Ignore the payload, it has "language" and "submethods" */ |
192 unsigned int passwordlen; | |
193 | |
194 int rc = PAM_SUCCESS; | |
195 unsigned char changepw; | |
196 | |
197 /* check if client wants to change password */ | |
198 changepw = buf_getbool(ses.payload); | |
199 if (changepw) { | |
200 /* not implemented by this server */ | |
201 send_msg_userauth_failure(0, 1); | |
202 goto cleanup; | |
203 } | |
204 | |
205 password = buf_getstring(ses.payload, &passwordlen); | |
206 | |
207 /* used to pass data to the PAM conversation function - don't bother with | |
208 * strdup() etc since these are touched only by our own conversation | |
209 * function (above) which takes care of it */ | |
210 userData.user = ses.authstate.pw_name; | |
211 userData.passwd = password; | |
212 | 215 |
213 /* Init pam */ | 216 /* Init pam */ |
214 if ((rc = pam_start("sshd", NULL, &pamConv, &pamHandlep)) != PAM_SUCCESS) { | 217 if ((rc = pam_start("sshd", ses.authstate.pw_name, &pamConv, &pamHandlep)) != PAM_SUCCESS) { |
215 dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s", | 218 dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s", |
216 rc, pam_strerror(pamHandlep, rc)); | 219 rc, pam_strerror(pamHandlep, rc)); |
217 goto cleanup; | 220 goto cleanup; |
218 } | 221 } |
219 | 222 |
258 ses.authstate.pw_name, | 261 ses.authstate.pw_name, |
259 svr_ses.addrstring); | 262 svr_ses.addrstring); |
260 send_msg_userauth_success(); | 263 send_msg_userauth_success(); |
261 | 264 |
262 cleanup: | 265 cleanup: |
263 if (password != NULL) { | |
264 m_burn(password, passwordlen); | |
265 m_free(password); | |
266 } | |
267 if (pamHandlep != NULL) { | 266 if (pamHandlep != NULL) { |
268 TRACE(("pam_end")) | 267 TRACE(("pam_end")) |
269 (void) pam_end(pamHandlep, 0 /* pam_status */); | 268 (void) pam_end(pamHandlep, 0 /* pam_status */); |
270 } | 269 } |
271 } | 270 } |