1
/* Internal SMB protocol implementation */
2
/* $Id: smb.c,v 1.32 2004/01/01 16:15:16 pasky Exp $ */
5
#define _GNU_SOURCE /* Needed for asprintf() */
18
#ifdef HAVE_SYS_TIME_H
19
#include <sys/time.h> /* FreeBSD needs this before resource.h */
21
#include <sys/types.h> /* FreeBSD needs this before resource.h */
22
#ifdef HAVE_SYS_RESOURCE_H
23
#include <sys/resource.h>
26
#include <fcntl.h> /* OS/2 needs this after sys/types.h */
34
#include "lowlevel/connect.h"
35
#include "lowlevel/select.h"
36
#include "osdep/osdep.h"
37
#include "protocol/protocol.h"
38
#include "sched/connection.h"
39
#include "util/memory.h"
40
#include "util/snprintf.h"
41
#include "util/string.h"
43
/* XXX: Nice cleanup target --pasky */
44
/* FIXME: we rely on smbclient output which may change in future,
45
* so i think we should use libsmbclient instead (or better in addition)
46
* This stuff is a quick hack, but it works ;). --Zas */
54
struct smb_connection_info {
55
enum smb_list_type list_type;
57
/* If this is 1, it means one socket is already off. The second one
58
* should call end_smb_connection() when it goes off as well. */
62
unsigned char text[1];
65
static void end_smb_connection(struct connection *conn);
68
/* Return 0 if @conn->cache was set. */
70
smb_get_cache(struct connection *conn)
72
if (conn->cache) return 0;
74
conn->cache = get_cache_entry(struri(conn->uri));
75
if (conn->cache) return 0;
77
abort_conn_with_state(conn, S_OUT_OF_MEM);
82
#define READ_SIZE 4096
85
smb_read_data(struct connection *conn, int sock, unsigned char *dst)
88
struct smb_connection_info *si = conn->info;
90
r = read(sock, dst, READ_SIZE);
92
retry_conn_with_state(conn, -errno);
98
set_handlers(conn->socket, NULL, NULL, NULL, NULL);
101
end_smb_connection(conn);
109
smb_read_text(struct connection *conn, int sock)
112
struct smb_connection_info *si = conn->info;
114
si = mem_realloc(si, sizeof(struct smb_connection_info) + si->textlen
115
+ READ_SIZE + 2); /* XXX: why +2 ? --Zas */
117
abort_conn_with_state(conn, S_OUT_OF_MEM);
122
r = smb_read_data(conn, sock, si->text + si->textlen);
125
if (!conn->from) set_connection_state(conn, S_GETH);
130
smb_got_data(struct connection *conn)
132
struct smb_connection_info *si = conn->info;
133
unsigned char buffer[READ_SIZE];
136
if (si->list_type != SMB_LIST_NONE) {
137
smb_read_text(conn, conn->data_socket);
141
r = smb_read_data(conn, conn->data_socket, buffer);
144
set_connection_state(conn, S_TRANS);
146
if (smb_get_cache(conn)) return;
149
if (add_fragment(conn->cache, conn->from, buffer, r) == 1)
157
smb_got_text(struct connection *conn)
159
smb_read_text(conn, conn->socket);
163
/* FIXME: split it. --Zas */
165
end_smb_connection(struct connection *conn)
167
struct smb_connection_info *si = conn->info;
169
if (smb_get_cache(conn)) return;
172
truncate_entry(conn->cache, conn->from, 1);
173
conn->cache->incomplete = 0;
177
if (si->textlen && si->text[si->textlen - 1] != '\n')
178
si->text[si->textlen++] = '\n';
179
si->text[si->textlen] = '\0';
181
if ((strstr(si->text, "NT_STATUS_FILE_IS_A_DIRECTORY")
182
|| strstr(si->text, "NT_STATUS_ACCESS_DENIED")
183
|| strstr(si->text, "ERRbadfile"))
185
&& conn->uri.data[conn->uri.datalen - 1] != '/'
186
&& conn->uri.data[conn->uri.datalen - 1] != '\\') {
187
if (conn->cache->redirect) mem_free(conn->cache->redirect);
188
conn->cache->redirect = stracpy(struri(conn->uri));
189
conn->cache->redirect_get = 1;
190
add_to_strn(&conn->cache->redirect, "/");
191
conn->cache->incomplete = 0;
194
unsigned char *line_start, *line_end, *line_end2;
199
if (!init_string(&page)) {
200
abort_conn_with_state(conn, S_OUT_OF_MEM);
204
add_to_string(&page, "<html><head><title>/");
205
add_bytes_to_string(&page, conn->uri.data, conn->uri.datalen);
206
add_to_string(&page, "</title></head><body><pre>");
208
line_start = si->text;
209
while ((line_end = strchr(line_start, '\n'))) {
212
/* FIXME: Just look if '\r' is right in front of '\n'?
214
line_end2 = strchr(line_start, '\r');
215
if (!line_end2 || line_end2 > line_end)
216
line_end2 = line_end;
217
line = memacpy(line_start, line_end2 - line_start);
219
/* And I got bored here with cleaning it up. --pasky */
221
if (si->list_type == SMB_LIST_SHARES) {
222
unsigned char *ll, *lll;
224
if (!*line) type = 0;
225
if (strstr(line, "Sharename")
226
&& strstr(line, "Type")) {
227
if (strstr(line, "Type")) {
228
pos = (unsigned char *)
229
strstr(line, "Type") - line;
236
if (strstr(line, "Server")
237
&& strstr(line, "Comment")) {
241
if (strstr(line, "Workgroup")
242
&& strstr(line, "Master")) {
243
pos = (unsigned char *) strstr(line, "Master") - line;
248
if (!type) goto print_as_is;
249
for (ll = line; *ll; ll++)
250
if (!WHITECHAR(*ll) && *ll != '-')
255
for (ll = line; *ll; ll++)
259
for (lll = ll; *lll/* && lll[1]*/; lll++)
260
if (WHITECHAR(*lll)/* && WHITECHAR(lll[1])*/)
268
if (!strstr(lll, "Disk"))
271
if (pos && pos < strlen(line)
272
&& WHITECHAR(*(llll = line + pos - 1))
274
while (llll > ll && WHITECHAR(*llll))
276
if (!WHITECHAR(*llll))
280
add_bytes_to_string(&page, line, ll - line);
281
add_to_string(&page, "<a href=\"");
282
add_bytes_to_string(&page, ll, lll - ll);
283
add_to_string(&page, "/\">");
284
add_bytes_to_string(&page, ll, lll - ll);
285
add_to_string(&page, "</a>");
286
add_to_string(&page, lll);
291
if (pos < strlen(line) && pos
292
&& WHITECHAR(line[pos - 1])
293
&& !WHITECHAR(line[pos])) {
296
for (ll = lll; *ll; ll++)
300
for (lll = ll; *lll; lll++)
306
add_bytes_to_string(&page, line, ll - line);
307
add_to_string(&page, "<a href=\"smb://");
308
add_bytes_to_string(&page, ll, lll - ll);
309
add_to_string(&page, "/\">");
310
add_bytes_to_string(&page, ll, lll - ll);
311
add_to_string(&page, "</a>");
312
add_to_string(&page, lll);
319
} else if (si->list_type == SMB_LIST_DIR) {
320
if (strstr(line, "NT_STATUS")) {
325
if (line_end2 - line_start >= 5
326
&& line_start[0] == ' '
327
&& line_start[1] == ' '
328
&& line_start[2] != ' ') {
331
unsigned char *p = line_start + 3;
332
unsigned char *url = p - 1;
334
while (p + 2 <= line_end2) {
335
if (p[0] == ' ' && p[1] == ' ')
336
goto is_a_file_entry;
343
while (pp < line_end2 && *pp == ' ')
345
while (pp < line_end2 && *pp != ' ') {
353
if (*url == '.' && p - url == 1) goto ignored;
355
add_to_string(&page, " <a href=\"");
356
add_bytes_to_string(&page, url, p - url);
357
if (dir) add_char_to_string(&page, '/');
358
add_to_string(&page, "\">");
359
add_bytes_to_string(&page, url, p - url);
360
add_to_string(&page, "</a>");
361
add_bytes_to_string(&page, p, line_end - p);
369
add_bytes_to_string(&page, line_start, line_end2 - line_start);
372
add_char_to_string(&page, '\n');
374
line_start = line_end + 1;
378
add_to_string(&page, "</pre></body></html>");
380
add_fragment(conn->cache, 0, page.source, page.length);
381
conn->from += page.length;
382
truncate_entry(conn->cache, page.length, 1);
383
conn->cache->incomplete = 0;
386
if (!conn->cache->head)
387
conn->cache->head = stracpy("\r\n");
388
add_to_strn(&conn->cache->head, "Content-Type: text/html\r\n");
392
close_socket(conn, &conn->socket);
393
close_socket(conn, &conn->data_socket);
394
abort_conn_with_state(conn, S_OK);
398
/* Close all non-terminal file descriptors. */
400
close_all_non_term_fd(void)
407
if (!getrlimit(RLIMIT_NOFILE, &lim))
410
for (n = 3; n < max; n++)
415
smb_func(struct connection *conn)
417
int out_pipe[2] = { -1, -1 };
418
int err_pipe[2] = { -1, -1 };
419
unsigned char *share, *dir;
422
struct smb_connection_info *si;
424
si = mem_calloc(1, sizeof(struct smb_connection_info) + 2);
426
abort_conn_with_state(conn, S_OUT_OF_MEM);
431
p = strchr(conn->uri.data, '/');
432
if (p && p - conn->uri.data < conn->uri.datalen) {
433
share = memacpy(conn->uri.data, p - conn->uri.data);
435
/* FIXME: ensure @dir do not contain dangerous chars. --Zas */
437
} else if (conn->uri.datalen) {
438
if (smb_get_cache(conn)) return;
440
if (conn->cache->redirect) mem_free(conn->cache->redirect);
441
conn->cache->redirect = stracpy(struri(conn->uri));
442
conn->cache->redirect_get = 1;
443
add_to_strn(&conn->cache->redirect, "/");
445
conn->cache->incomplete = 0;
446
abort_conn_with_state(conn, S_OK);
455
abort_conn_with_state(conn, S_OUT_OF_MEM);
459
dirlen = strlen(dir);
461
si->list_type = SMB_LIST_SHARES;
462
} else if (!dirlen || dir[dirlen - 1] == '/'
463
|| dir[dirlen - 1] == '\\') {
464
si->list_type = SMB_LIST_DIR;
467
if (c_pipe(out_pipe) || c_pipe(err_pipe)) {
470
if (out_pipe[0] >= 0) close(out_pipe[0]);
471
if (out_pipe[1] >= 0) close(out_pipe[1]);
473
abort_conn_with_state(conn, -s_errno);
488
retry_conn_with_state(conn, -s_errno);
493
#define MAX_SMBCLIENT_ARGS 32
495
unsigned char *v[MAX_SMBCLIENT_ARGS];
498
dup2(out_pipe[1], 1);
500
dup2(err_pipe[1], 2);
502
dup2(open("/dev/null", O_RDONLY), 0);
504
close_all_non_term_fd();
508
v[n++] = "smbclient";
510
/* FIXME: handle alloc failures. */
511
/* At this point, we are the child process.
512
* Maybe we just don't care if the child kills itself
513
* dereferencing a NULL pointer... -- Miciah */
514
/* Leaving random core files after itself is not what a nice
515
* program does. Also, the user might also want to know, why
516
* the hell does he see nothing on the screen. --pasky */
519
v[n++] = "-L"; /* get a list of shares available on a host */
520
v[n++] = memacpy(conn->uri.host, conn->uri.hostlen);
523
/* Construct path. */
524
asprintf((char **) &v[n++], "//%.*s/%s",
525
conn->uri.hostlen, conn->uri.host, share);
526
/* XXX: add password to argument if any. TODO: Recheck
527
* if correct. --Zas. */
528
if (conn->uri.passwordlen && !conn->uri.userlen) {
529
v[n++] = memacpy(conn->uri.password, conn->uri.passwordlen);
533
v[n++] = "-N"; /* don't ask for a password */
534
v[n++] = "-E"; /* write messages to stderr instead of stdout */
535
v[n++] = "-d 0"; /* disable debug mode. */
537
if (conn->uri.portlen) {
538
v[n++] = "-p"; /* connect to the specified port */
539
v[n++] = memacpy(conn->uri.port, conn->uri.portlen);
542
if (conn->uri.userlen) {
543
v[n++] = "-U"; /* set the network username */
544
if (!conn->uri.passwordlen) {
546
v[n++] = memacpy(conn->uri.user, conn->uri.userlen);
549
asprintf((char **) &v[n++], "%.*s%%%.*s",
550
conn->uri.userlen, conn->uri.user,
551
conn->uri.passwordlen, conn->uri.password);
556
/* FIXME: use si->list_type here ?? --Zas */
557
if (!dirlen || dir[dirlen - 1] == '/' || dir[dirlen - 1] == '\\') {
559
v[n++] = "-D"; /* start from directory */
562
v[n++] = "-c"; /* execute semicolon separated commands */
566
unsigned char *s = straconcat("get \"", dir, "\" -", NULL);
567
unsigned char *ss = s;
569
v[n++] = "-c"; /* execute semicolon separated commands */
570
while ((ss = strchr(ss, '/'))) *ss = '\\';
576
assert(n < MAX_SMBCLIENT_ARGS);
578
execvp("smbclient", (char **) v);
580
fprintf(stderr, "smbclient not found in $PATH");
582
#undef MAX_SMBCLIENT_ARGS
587
conn->data_socket = out_pipe[0];
588
conn->socket = err_pipe[0];
593
set_handlers(out_pipe[0], (void (*)(void *)) smb_got_data, NULL, NULL, conn);
594
set_handlers(err_pipe[0], (void (*)(void *)) smb_got_text, NULL, NULL, conn);
595
set_connection_state(conn, S_CONN);
599
struct protocol_backend smb_protocol_backend = {
602
/* handler: */ smb_func,
603
/* external_handler: */ NULL,
604
/* free_syntax: */ 0,
605
/* need_slashes: */ 1,
606
/* need_slash_after_host: */ 1,
609
#endif /* CONFIG_SMB */