22static size_t utf16le(
const char *s, uint8_t *out,
size_t cap)
29 if ((n * 2 + 2) > cap)
31 out[n * 2] = (uint8_t)s[n];
38static void find_av_timestamp(
const uint8_t *ti,
size_t ti_len, uint8_t out[8])
42 while (p + 4 <= ti_len)
44 uint16_t
id = (uint16_t)(ti[p] | (ti[p + 1] << 8));
45 uint16_t len = (uint16_t)(ti[p + 2] | (ti[p + 3] << 8));
49 if (
id == 7 && len == 8 && p + 8 <= ti_len)
51 memcpy(out, ti + p, 8);
58static bool read_exact(SmbRecvFn recv,
void *ctx, uint8_t *buf,
size_t n)
63 int r = recv(ctx, buf + got, n - got);
72static bool send_msg(SmbSendFn send,
void *ctx, uint8_t *frame,
size_t msg_len)
75 frame[1] = (uint8_t)(msg_len >> 16);
76 frame[2] = (uint8_t)(msg_len >> 8);
77 frame[3] = (uint8_t)msg_len;
78 size_t total = 4 + msg_len;
79 return send(ctx, frame, total) == (int)total;
83static int recv_msg(SmbRecvFn recv,
void *ctx, uint8_t *rx,
size_t cap)
86 if (!read_exact(recv, ctx, pre, 4) || pre[0] != 0x00)
88 size_t len = ((size_t)pre[1] << 16) | ((size_t)pre[2] << 8) | pre[3];
89 if (len == 0 || len > cap)
91 if (!read_exact(recv, ctx, rx, len))
109static SmbClientCtx s_smb;
113static int smb_round_trip(SmbSendFn send, SmbRecvFn recv,
void *ctx,
size_t mlen, SmbResult *res)
115 if (!send_msg(send, ctx, s_smb.tx, mlen))
117 *res = SmbResult::SMB_ERR_IO;
120 int rl = recv_msg(recv, ctx, s_smb.rx,
sizeof(s_smb.rx));
123 *res = (rl == -2) ? SmbResult::SMB_ERR_OVERFLOW : SmbResult::SMB_ERR_IO;
130static SmbResult smb_negotiate(SmbSendFn send, SmbRecvFn recv,
void *ctx)
133 esp_fill_random(guid, 16);
134 size_t mlen = smb2_build_negotiate(s_smb.tx + 4,
sizeof(s_smb.tx) - 4, guid,
135 Smb2SecurityMode::SMB2_NEGOTIATE_SIGNING_ENABLED);
137 return SmbResult::SMB_ERR_OVERFLOW;
138 SmbResult rt = SmbResult::SMB_ERR_IO;
139 int rl = smb_round_trip(send, recv, ctx, mlen, &rt);
142 Smb2NegotiateResp neg;
143 if (!smb2_parse_negotiate_response(s_smb.rx, (
size_t)rl, &neg))
144 return SmbResult::SMB_ERR_PROTOCOL;
145 return SmbResult::SMB_OK;
150static SmbResult smb_session_setup(
const SmbConfig *cfg,
const char *domain, SmbSendFn send, SmbRecvFn recv,
void *ctx,
151 uint64_t *session_id)
156 size_t ntneg_n = ntlmssp_build_negotiate(ntneg,
sizeof(ntneg), NtlmsspFlags::NTLMSSP_CLIENT_DEFAULT_FLAGS);
157 size_t sp1_n = spnego_wrap_negotiate(ntneg, ntneg_n, sp1,
sizeof(sp1));
158 size_t mlen = smb2_build_session_setup(s_smb.tx + 4,
sizeof(s_smb.tx) - 4, 1, 0,
159 Smb2SecurityMode::SMB2_NEGOTIATE_SIGNING_ENABLED, sp1, sp1_n);
161 return SmbResult::SMB_ERR_OVERFLOW;
162 SmbResult rt = SmbResult::SMB_ERR_IO;
163 int rl = smb_round_trip(send, recv, ctx, mlen, &rt);
167 if (!smb2_parse_header(s_smb.rx, (
size_t)rl, &h1) || h1.status != Smb2Status::SMB2_STATUS_MORE_PROCESSING_REQUIRED)
168 return SmbResult::SMB_ERR_AUTH;
169 *session_id = h1.session_id;
170 Smb2SessionSetupResp ss1;
171 if (!smb2_parse_session_setup_response(s_smb.rx, (
size_t)rl, &ss1) || !ss1.sec_buf)
172 return SmbResult::SMB_ERR_PROTOCOL;
173 const uint8_t *chal_tok =
nullptr;
175 if (!spnego_parse_response(ss1.sec_buf, ss1.sec_buf_len, &chal_tok, &chal_len))
176 return SmbResult::SMB_ERR_PROTOCOL;
178 if (!ntlmssp_parse_challenge(chal_tok, chal_len, &ch))
179 return SmbResult::SMB_ERR_PROTOCOL;
184 ntlm_nt_hash(cfg->pass, nt_hash);
185 if (!ntlm_ntowfv2(nt_hash, cfg->user, domain, owf))
186 return SmbResult::SMB_ERR_OVERFLOW;
190 esp_fill_random(cli_chal, 8);
191 find_av_timestamp(ch.target_info, ch.target_info_len, ts);
192 size_t nt_len = ntlm_v2_response(owf, ch.server_challenge, cli_chal, ts, ch.target_info, ch.target_info_len,
193 s_smb.nt_resp,
sizeof(s_smb.nt_resp), skey);
195 return SmbResult::SMB_ERR_OVERFLOW;
196 size_t ntauth_n = ntlmssp_build_authenticate(s_smb.ntauth,
sizeof(s_smb.ntauth),
nullptr, 0, s_smb.nt_resp, nt_len,
197 domain, cfg->user, cfg->workstation, ch.flags);
199 return SmbResult::SMB_ERR_OVERFLOW;
200 size_t sp2_n = spnego_wrap_authenticate(s_smb.ntauth, ntauth_n, s_smb.sp2,
sizeof(s_smb.sp2));
202 return SmbResult::SMB_ERR_OVERFLOW;
205 mlen = smb2_build_session_setup(s_smb.tx + 4,
sizeof(s_smb.tx) - 4, 2, *session_id,
206 Smb2SecurityMode::SMB2_NEGOTIATE_SIGNING_ENABLED, s_smb.sp2, sp2_n);
208 return SmbResult::SMB_ERR_OVERFLOW;
209 rl = smb_round_trip(send, recv, ctx, mlen, &rt);
213 if (!smb2_parse_header(s_smb.rx, (
size_t)rl, &h2))
214 return SmbResult::SMB_ERR_PROTOCOL;
215 if (h2.status != Smb2Status::SMB2_STATUS_SUCCESS)
216 return SmbResult::SMB_ERR_AUTH;
217 return SmbResult::SMB_OK;
221static SmbResult smb_tree_connect(
const SmbConfig *cfg, uint64_t session_id, SmbSendFn send, SmbRecvFn recv,
void *ctx,
224 size_t utf16_n = utf16le(cfg->share, s_smb.utf16,
sizeof(s_smb.utf16));
226 return SmbResult::SMB_ERR_OVERFLOW;
227 size_t mlen = smb2_build_tree_connect(s_smb.tx + 4,
sizeof(s_smb.tx) - 4, 3, session_id, s_smb.utf16, utf16_n);
229 return SmbResult::SMB_ERR_OVERFLOW;
230 SmbResult rt = SmbResult::SMB_ERR_IO;
231 int rl = smb_round_trip(send, recv, ctx, mlen, &rt);
235 Smb2TreeConnectResp tc;
236 if (!smb2_parse_header(s_smb.rx, (
size_t)rl, &h3) || h3.status != Smb2Status::SMB2_STATUS_SUCCESS)
237 return SmbResult::SMB_ERR_PROTOCOL;
238 if (!smb2_parse_tree_connect_response(s_smb.rx, (
size_t)rl, &tc))
239 return SmbResult::SMB_ERR_PROTOCOL;
240 *tree_id = h3.tree_id;
241 return SmbResult::SMB_OK;
245static SmbResult smb_create(
const SmbConfig *cfg, SmbHandle *h, uint64_t session_id, uint32_t tree_id, SmbSendFn send,
246 SmbRecvFn recv,
void *ctx)
248 size_t utf16_n = utf16le(cfg->path, s_smb.utf16,
sizeof(s_smb.utf16));
250 return SmbResult::SMB_ERR_OVERFLOW;
252 smb2_build_create(s_smb.tx + 4,
sizeof(s_smb.tx) - 4, 4, session_id, tree_id, cfg->desired_access,
253 Smb2ShareAccess::SMB2_FILE_SHARE_READ | Smb2ShareAccess::SMB2_FILE_SHARE_WRITE,
254 cfg->disposition, Smb2CreateOptions::SMB2_FILE_NON_DIRECTORY_FILE, s_smb.utf16, utf16_n);
256 return SmbResult::SMB_ERR_OVERFLOW;
257 SmbResult rt = SmbResult::SMB_ERR_IO;
258 int rl = smb_round_trip(send, recv, ctx, mlen, &rt);
263 if (!smb2_parse_header(s_smb.rx, (
size_t)rl, &h4) || h4.status != Smb2Status::SMB2_STATUS_SUCCESS)
264 return SmbResult::SMB_ERR_PROTOCOL;
265 if (!smb2_parse_create_response(s_smb.rx, (
size_t)rl, &cr))
266 return SmbResult::SMB_ERR_PROTOCOL;
267 h->session_id = session_id;
268 h->tree_id = tree_id;
269 memcpy(h->file_id, cr.file_id, 16);
270 h->file_size = cr.end_of_file;
271 h->next_message_id = 5;
272 return SmbResult::SMB_OK;
275SmbResult smb_open(
const SmbConfig *cfg, SmbHandle *h, SmbSendFn send, SmbRecvFn recv,
void *ctx)
277 if (!cfg || !h || !send || !recv || !cfg->user || !cfg->pass || !cfg->share || !cfg->path)
278 return SmbResult::SMB_ERR_ARG;
280 const char *domain = cfg->domain ? cfg->domain :
"";
282 SmbResult r = smb_negotiate(send, recv, ctx);
283 if (r != SmbResult::SMB_OK)
286 uint64_t session_id = 0;
287 r = smb_session_setup(cfg, domain, send, recv, ctx, &session_id);
288 if (r != SmbResult::SMB_OK)
291 uint32_t tree_id = 0;
292 r = smb_tree_connect(cfg, session_id, send, recv, ctx, &tree_id);
293 if (r != SmbResult::SMB_OK)
296 return smb_create(cfg, h, session_id, tree_id, send, recv, ctx);
299SmbResult smb_close(SmbHandle *h, SmbSendFn send, SmbRecvFn recv,
void *ctx)
301 if (!h || !send || !recv)
302 return SmbResult::SMB_ERR_ARG;
305 size_t mlen = smb2_build_close(tx + 4,
sizeof(tx) - 4, h->next_message_id, h->session_id, h->tree_id, h->file_id);
307 return SmbResult::SMB_ERR_OVERFLOW;
308 if (!send_msg(send, ctx, tx, mlen))
309 return SmbResult::SMB_ERR_IO;
310 int rl = recv_msg(recv, ctx, rx,
sizeof(rx));
312 return rl == -2 ? SmbResult::SMB_ERR_OVERFLOW : SmbResult::SMB_ERR_IO;
315 if (!smb2_parse_header(rx, (
size_t)rl, &hd) || hd.status != Smb2Status::SMB2_STATUS_SUCCESS)
316 return SmbResult::SMB_ERR_PROTOCOL;
317 if (!smb2_parse_close_response(rx, (
size_t)rl, &cl))
318 return SmbResult::SMB_ERR_PROTOCOL;
319 h->next_message_id++;
320 return SmbResult::SMB_OK;
323SmbResult smb_read(SmbHandle *h, uint64_t offset, uint8_t *out,
size_t cap,
size_t *out_len, SmbSendFn send,
324 SmbRecvFn recv,
void *ctx)
326 if (!h || !out || !out_len || !send || !recv)
327 return SmbResult::SMB_ERR_ARG;
333 size_t want = cap - total;
334 if (want > chunk_max)
336 size_t mlen = smb2_build_read(s_smb.tx + 4,
sizeof(s_smb.tx) - 4, h->next_message_id, h->session_id, h->tree_id,
337 h->file_id, (uint32_t)want, offset + total);
339 return SmbResult::SMB_ERR_OVERFLOW;
340 SmbResult rt = SmbResult::SMB_ERR_IO;
341 int rl = smb_round_trip(send, recv, ctx, mlen, &rt);
345 if (!smb2_parse_header(s_smb.rx, (
size_t)rl, &hd))
346 return SmbResult::SMB_ERR_PROTOCOL;
347 h->next_message_id++;
348 if (hd.status == Smb2Status::SMB2_STATUS_END_OF_FILE)
350 if (hd.status != Smb2Status::SMB2_STATUS_SUCCESS)
351 return SmbResult::SMB_ERR_PROTOCOL;
353 if (!smb2_parse_read_response(s_smb.rx, (
size_t)rl, &r) || r.data_len > want)
354 return SmbResult::SMB_ERR_PROTOCOL;
357 memcpy(out + total, r.data, r.data_len);
359 if (r.data_len < want)
363 return SmbResult::SMB_OK;
366SmbResult smb_write(SmbHandle *h, uint64_t offset,
const uint8_t *data,
size_t len,
size_t *written, SmbSendFn send,
367 SmbRecvFn recv,
void *ctx)
369 if (!h || !data || !written || !send || !recv)
370 return SmbResult::SMB_ERR_ARG;
376 size_t want = len - total;
377 if (want > chunk_max)
379 size_t mlen = smb2_build_write(s_smb.tx + 4,
sizeof(s_smb.tx) - 4, h->next_message_id, h->session_id,
380 h->tree_id, h->file_id, data + total, want, offset + total);
382 return SmbResult::SMB_ERR_OVERFLOW;
383 if (!send_msg(send, ctx, s_smb.tx, mlen))
384 return SmbResult::SMB_ERR_IO;
385 int rl = recv_msg(recv, ctx, s_smb.rx,
sizeof(s_smb.rx));
387 return rl == -2 ? SmbResult::SMB_ERR_OVERFLOW : SmbResult::SMB_ERR_IO;
389 if (!smb2_parse_header(s_smb.rx, (
size_t)rl, &hd))
390 return SmbResult::SMB_ERR_PROTOCOL;
391 h->next_message_id++;
392 if (hd.status != Smb2Status::SMB2_STATUS_SUCCESS)
393 return SmbResult::SMB_ERR_PROTOCOL;
395 if (!smb2_parse_write_response(s_smb.rx, (
size_t)rl, &w) || w.count == 0 || w.count > want)
396 return SmbResult::SMB_ERR_PROTOCOL;
399 if (offset + total > h->file_size)
400 h->file_size = offset + total;
402 return SmbResult::SMB_OK;
#define DETWS_SMB_BUF
SMB2 client work-buffer size (bytes) for smb_client's request/response framing.
NTLMv2 response computation (MS-NLMP §3.3.2) for the SMB2 client (DETWS_ENABLE_SMB).
NTLMSSP message codec (MS-NLMP §2.2.1) for the SMB2 client (DETWS_ENABLE_SMB).
SMB2 client wire codec (MS-SMB2), DETWS_ENABLE_SMB - increment 1: the transport frame,...
SMB2 client dialogue engine (DETWS_ENABLE_SMB) - drives the smb2 / ntlm / spnego wire codecs through ...
SPNEGO (RFC 4178) GSS-API wrapping of the NTLMSSP tokens for the SMB2 client (DETWS_ENABLE_SMB).