Fossil SCM

fossil-scm / src / http_transport.c
Blame History Raw 494 lines
1
/*
2
** Copyright (c) 2009 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This module implements the transport layer for the client side HTTP
19
** connection. The purpose of this layer is to provide a common interface
20
** for both HTTP and HTTPS and to provide a common "fetch one line"
21
** interface that is used for parsing the reply.
22
*/
23
#include "config.h"
24
#include "http_transport.h"
25
26
/*
27
** State information
28
*/
29
static struct {
30
int isOpen; /* True when the transport layer is open */
31
char *pBuf; /* Buffer used to hold the reply */
32
int nAlloc; /* Space allocated for transportBuf[] */
33
int nUsed ; /* Space of transportBuf[] used */
34
int iCursor; /* Next unread by in transportBuf[] */
35
i64 nSent; /* Number of bytes sent */
36
i64 nRcvd; /* Number of bytes received */
37
FILE *pFile; /* File I/O for FILE: */
38
char *zOutFile; /* Name of outbound file for FILE: */
39
char *zInFile; /* Name of inbound file for FILE: */
40
FILE *pLog; /* Log output here */
41
} transport = {
42
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
43
};
44
45
/*
46
** Information about the connection to the SSH subprocess when
47
** using the ssh:// sync method.
48
*/
49
static int sshPid; /* Process id of ssh subprocess */
50
static int sshIn; /* From ssh subprocess to this process */
51
static FILE *sshOut; /* From this to ssh subprocess */
52
53
54
/*
55
** Return the current transport error message.
56
*/
57
const char *transport_errmsg(UrlData *pUrlData){
58
#ifdef FOSSIL_ENABLE_SSL
59
if( pUrlData->isHttps ){
60
return ssl_errmsg();
61
}
62
#endif
63
return socket_errmsg();
64
}
65
66
/*
67
** Retrieve send/receive counts from the transport layer. If "resetFlag"
68
** is true, then reset the counts.
69
*/
70
void transport_stats(i64 *pnSent, i64 *pnRcvd, int resetFlag){
71
if( pnSent ) *pnSent = transport.nSent;
72
if( pnRcvd ) *pnRcvd = transport.nRcvd;
73
if( resetFlag ){
74
transport.nSent = 0;
75
transport.nRcvd = 0;
76
}
77
}
78
79
/*
80
** Check zFossil to see if it is a reasonable "fossil" command to
81
** run on the server. Do not allow an attacker to substitute something
82
** like "/bin/rm".
83
*/
84
static int is_safe_fossil_command(const char *zFossil){
85
static const char *const azSafe[] = { "*/fossil", "*/fossil.exe", "*/echo" };
86
int i;
87
for(i=0; i<(int)(sizeof(azSafe)/sizeof(azSafe[0])); i++){
88
if( sqlite3_strglob(azSafe[i], zFossil)==0 ) return 1;
89
if( strcmp(azSafe[i]+2, zFossil)==0 ) return 1;
90
}
91
return 0;
92
}
93
94
/*
95
** Default SSH command
96
*/
97
#if 0 /* was: defined(_WIN32). Windows generally has ssh now. */
98
static const char zDefaultSshCmd[] = "plink -ssh";
99
#else
100
static const char zDefaultSshCmd[] = "ssh -e none";
101
#endif
102
103
/*
104
** Initialize a Blob to the name of the configured SSH command.
105
*/
106
void transport_ssh_command(Blob *p){
107
char *zSsh; /* The base SSH command */
108
zSsh = g.zSshCmd;
109
if( zSsh==0 || zSsh[0]==0 ){
110
zSsh = db_get("ssh-command", zDefaultSshCmd);
111
}
112
blob_init(p, zSsh, -1);
113
}
114
115
/*
116
** SSH initialization of the transport layer
117
*/
118
int transport_ssh_open(UrlData *pUrlData){
119
/* For SSH we need to create and run SSH fossil http
120
** to talk to the remote machine.
121
*/
122
Blob zCmd; /* The SSH command */
123
char *zHost; /* The host name to contact */
124
125
fossil_free(g.zIpAddr);
126
g.zIpAddr = fossil_strdup(pUrlData->name);
127
transport_ssh_command(&zCmd);
128
if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
129
blob_appendf(&zCmd, " -p %d", pUrlData->port);
130
}
131
blob_appendf(&zCmd, " -T --"); /* End of switches */
132
if( pUrlData->user && pUrlData->user[0] ){
133
zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
134
blob_append_escaped_arg(&zCmd, zHost, 0);
135
fossil_free(zHost);
136
}else{
137
blob_append_escaped_arg(&zCmd, pUrlData->name, 0);
138
}
139
if( (pUrlData->flags & URL_SSH_EXE)!=0
140
&& !is_safe_fossil_command(pUrlData->fossil)
141
){
142
fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
143
"the server.", pUrlData->fossil);
144
}
145
if( (pUrlData->flags & URL_SSH_EXE)==0
146
&& (pUrlData->flags & URL_SSH_PATH)!=0
147
){
148
ssh_add_path_argument(&zCmd);
149
}
150
blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1);
151
blob_append(&zCmd, " test-http", 10);
152
if( pUrlData->path && pUrlData->path[0] ){
153
blob_append_escaped_arg(&zCmd, pUrlData->path, 1);
154
}else{
155
fossil_fatal("ssh:// URI does not specify a path to the repository");
156
}
157
if( g.fSshTrace || g.fHttpTrace ){
158
fossil_print("RUN %s\n", blob_str(&zCmd)); /* Show the whole SSH command */
159
}
160
popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid, 0);
161
if( sshPid==0 ){
162
socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
163
}
164
blob_reset(&zCmd);
165
return sshPid==0;
166
}
167
168
/*
169
** Open a connection to the server. The server is defined by the following
170
** variables:
171
**
172
** pUrlData->name Name of the server. Ex: fossil-scm.org
173
** pUrlData->port TCP/IP port. Ex: 80
174
** pUrlData->isHttps Use TLS for the connection
175
**
176
** Return the number of errors.
177
*/
178
int transport_open(UrlData *pUrlData){
179
int rc = 0;
180
if( transport.isOpen==0 ){
181
if( pUrlData->isSsh ){
182
rc = transport_ssh_open(pUrlData);
183
if( rc==0 ) transport.isOpen = 1;
184
}else if( pUrlData->isHttps ){
185
#ifdef FOSSIL_ENABLE_SSL
186
rc = ssl_open_client(pUrlData);
187
if( rc==0 ) transport.isOpen = 1;
188
#else
189
socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
190
rc = 1;
191
#endif
192
}else if( pUrlData->isFile ){
193
if( !db_looks_like_a_repository(pUrlData->name) ){
194
fossil_fatal("not a fossil repository: \"%s\"", pUrlData->name);
195
}
196
transport.zOutFile = fossil_temp_filename();
197
transport.zInFile = fossil_temp_filename();
198
transport.pFile = fossil_fopen(transport.zOutFile, "wb");
199
if( transport.pFile==0 ){
200
fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
201
}
202
transport.isOpen = 1;
203
}else{
204
rc = socket_open(pUrlData);
205
if( rc==0 ) transport.isOpen = 1;
206
}
207
}
208
return rc;
209
}
210
211
/*
212
** Close the current connection
213
*/
214
void transport_close(UrlData *pUrlData){
215
if( transport.isOpen ){
216
free(transport.pBuf);
217
transport.pBuf = 0;
218
transport.nAlloc = 0;
219
transport.nUsed = 0;
220
transport.iCursor = 0;
221
if( transport.pLog ){
222
fclose(transport.pLog);
223
transport.pLog = 0;
224
}
225
if( pUrlData->isSsh ){
226
transport_ssh_close();
227
}else if( pUrlData->isHttps ){
228
#ifdef FOSSIL_ENABLE_SSL
229
ssl_close_client();
230
#endif
231
}else if( pUrlData->isFile ){
232
if( transport.pFile ){
233
fclose(transport.pFile);
234
transport.pFile = 0;
235
}
236
file_delete(transport.zInFile);
237
file_delete(transport.zOutFile);
238
sqlite3_free(transport.zInFile);
239
sqlite3_free(transport.zOutFile);
240
}else{
241
socket_close();
242
}
243
transport.isOpen = 0;
244
}
245
}
246
247
/*
248
** Send content over the wire.
249
*/
250
void transport_send(UrlData const *pUrlData, const Blob *toSend){
251
char *z = blob_buffer(toSend);
252
int n = blob_size(toSend);
253
transport.nSent += n;
254
if( pUrlData->isSsh ){
255
fwrite(z, 1, n, sshOut);
256
fflush(sshOut);
257
}else if( pUrlData->isHttps ){
258
#ifdef FOSSIL_ENABLE_SSL
259
int sent;
260
while( n>0 ){
261
sent = ssl_send(0, z, n);
262
/* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
263
if( sent<=0 ) break;
264
n -= sent;
265
}
266
#endif
267
}else if( pUrlData->isFile ){
268
fwrite(z, 1, n, transport.pFile);
269
}else{
270
int sent;
271
while( n>0 ){
272
sent = socket_send(0, z, n);
273
/* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
274
if( sent<=0 ) break;
275
n -= sent;
276
}
277
}
278
}
279
280
/*
281
** This routine is called when the outbound message is complete and
282
** it is time to begin receiving a reply.
283
*/
284
void transport_flip(UrlData *pUrlData){
285
if( pUrlData->isFile ){
286
char *zCmd;
287
fclose(transport.pFile);
288
zCmd = mprintf("%$ http --in %$ --out %$ --ipaddr 127.0.0.1"
289
" %$ --localauth",
290
g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
291
);
292
if( g.fHttpTrace ) fossil_print("RUN %s\n", zCmd);
293
fossil_system(zCmd);
294
free(zCmd);
295
transport.pFile = fossil_fopen(transport.zInFile, "rb");
296
}
297
}
298
299
/*
300
** Log all input to a file. The transport layer will take responsibility
301
** for closing the log file when it is done.
302
*/
303
void transport_log(FILE *pLog){
304
if( transport.pLog ){
305
fclose(transport.pLog);
306
transport.pLog = 0;
307
}
308
transport.pLog = pLog;
309
}
310
311
/*
312
** This routine is called when the inbound message has been received
313
** and it is time to start sending again.
314
*/
315
void transport_rewind(UrlData *pUrlData){
316
if( pUrlData->isFile ){
317
transport_close(pUrlData);
318
}
319
}
320
321
/*
322
** Read N bytes of content directly from the wire and write into
323
** the buffer.
324
*/
325
static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
326
int got;
327
if( pUrlData->isSsh ){
328
int x;
329
int wanted = N;
330
got = 0;
331
while( wanted>0 ){
332
x = read(sshIn, &zBuf[got], wanted);
333
if( x<=0 ) break;
334
got += x;
335
wanted -= x;
336
}
337
}else if( pUrlData->isHttps ){
338
#ifdef FOSSIL_ENABLE_SSL
339
got = ssl_receive(0, zBuf, N);
340
#else
341
got = 0;
342
#endif
343
}else if( pUrlData->isFile ){
344
got = fread(zBuf, 1, N, transport.pFile);
345
}else{
346
got = socket_receive(0, zBuf, N, 0);
347
}
348
/* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
349
if( transport.pLog ){
350
fwrite(zBuf, 1, got, transport.pLog);
351
fflush(transport.pLog);
352
}
353
return got;
354
}
355
356
/*
357
** Read N bytes of content from the wire and store in the supplied buffer.
358
** Return the number of bytes actually received.
359
*/
360
int transport_receive(UrlData *pUrlData, char *zBuf, int N){
361
int onHand; /* Bytes current held in the transport buffer */
362
int nByte = 0; /* Bytes of content received */
363
364
onHand = transport.nUsed - transport.iCursor;
365
if( g.fSshTrace){
366
printf("Reading %d bytes with %d on hand... ", N, onHand);
367
fflush(stdout);
368
}
369
if( onHand>0 ){
370
int toMove = onHand;
371
if( toMove>N ) toMove = N;
372
/* printf("bytes on hand: %d of %d\n", toMove, N); fflush(stdout); */
373
memcpy(zBuf, &transport.pBuf[transport.iCursor], toMove);
374
transport.iCursor += toMove;
375
if( transport.iCursor>=transport.nUsed ){
376
transport.nUsed = 0;
377
transport.iCursor = 0;
378
}
379
N -= toMove;
380
zBuf += toMove;
381
nByte += toMove;
382
}
383
if( N>0 ){
384
int got = transport_fetch(pUrlData, zBuf, N);
385
if( got>0 ){
386
nByte += got;
387
transport.nRcvd += got;
388
}
389
}
390
if( g.fSshTrace ) printf("Got %d bytes\n", nByte);
391
return nByte;
392
}
393
394
/*
395
** Load up to N new bytes of content into the transport.pBuf buffer.
396
** The buffer itself might be moved. And the transport.iCursor value
397
** might be reset to 0.
398
*/
399
static void transport_load_buffer(UrlData *pUrlData, int N){
400
int i, j;
401
if( transport.nAlloc==0 ){
402
transport.nAlloc = N;
403
transport.pBuf = fossil_malloc( N );
404
transport.iCursor = 0;
405
transport.nUsed = 0;
406
}
407
if( transport.iCursor>0 ){
408
for(i=0, j=transport.iCursor; j<transport.nUsed; i++, j++){
409
transport.pBuf[i] = transport.pBuf[j];
410
}
411
transport.nUsed -= transport.iCursor;
412
transport.iCursor = 0;
413
}
414
if( transport.nUsed + N > transport.nAlloc ){
415
char *pNew;
416
transport.nAlloc = transport.nUsed + N;
417
pNew = fossil_realloc(transport.pBuf, transport.nAlloc);
418
transport.pBuf = pNew;
419
}
420
if( N>0 ){
421
i = transport_fetch(pUrlData, &transport.pBuf[transport.nUsed], N);
422
if( i>0 ){
423
transport.nRcvd += i;
424
transport.nUsed += i;
425
}
426
}
427
}
428
429
/*
430
** Fetch a single line of input where a line is all text up to the next
431
** \n character or until the end of input. Remove all trailing whitespace
432
** from the received line and zero-terminate the result. Return a pointer
433
** to the line.
434
**
435
** Each call to this routine potentially overwrites the returned buffer.
436
*/
437
char *transport_receive_line(UrlData *pUrlData){
438
int i;
439
int iStart;
440
441
i = iStart = transport.iCursor;
442
while(1){
443
if( i >= transport.nUsed ){
444
transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
445
i -= iStart;
446
iStart = 0;
447
if( i >= transport.nUsed ){
448
transport.pBuf[i] = 0;
449
transport.iCursor = i;
450
break;
451
}
452
}
453
if( transport.pBuf[i]=='\n' ){
454
transport.iCursor = i+1;
455
while( i>=iStart && fossil_isspace(transport.pBuf[i]) ){
456
transport.pBuf[i] = 0;
457
i--;
458
}
459
break;
460
}
461
i++;
462
}
463
if( g.fSshTrace ) printf("Got line: [%s]\n", &transport.pBuf[iStart]);
464
return &transport.pBuf[iStart];
465
}
466
467
/*
468
** Global transport shutdown
469
*/
470
void transport_global_shutdown(UrlData *pUrlData){
471
if( pUrlData->isSsh ){
472
transport_ssh_close();
473
}
474
if( pUrlData->isHttps ){
475
#ifdef FOSSIL_ENABLE_SSL
476
ssl_global_shutdown();
477
#endif
478
}else{
479
socket_global_shutdown();
480
}
481
}
482
483
/*
484
** Close SSH transport.
485
*/
486
void transport_ssh_close(void){
487
if( sshPid ){
488
/*printf("Closing SSH tunnel: ");*/
489
fflush(stdout);
490
pclose2(sshIn, sshOut, sshPid);
491
sshPid = 0;
492
}
493
}
494

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button