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 ** drh@hwaci.com
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 */
transport_errmsg(UrlData * pUrlData)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 */
transport_stats(i64 * pnSent,i64 * pnRcvd,int resetFlag)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 */
is_safe_fossil_command(const char * zFossil)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<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 */
transport_ssh_command(Blob * p)106 void transport_ssh_command(Blob *p){
107 char *zSsh; /* The base SSH command */
108 zSsh = db_get("ssh-command", zDefaultSshCmd);
109 blob_init(p, zSsh, -1);
110 }
111
112 /*
113 ** SSH initialization of the transport layer
114 */
transport_ssh_open(UrlData * pUrlData)115 int transport_ssh_open(UrlData *pUrlData){
116 /* For SSH we need to create and run SSH fossil http
117 ** to talk to the remote machine.
118 */
119 Blob zCmd; /* The SSH command */
120 char *zHost; /* The host name to contact */
121
122 socket_ssh_resolve_addr(pUrlData);
123 transport_ssh_command(&zCmd);
124 if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
125 blob_appendf(&zCmd, " -p %d", pUrlData->port);
126 }
127 blob_appendf(&zCmd, " -T --"); /* End of switches */
128 if( pUrlData->user && pUrlData->user[0] ){
129 zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
130 blob_append_escaped_arg(&zCmd, zHost, 0);
131 fossil_free(zHost);
132 }else{
133 blob_append_escaped_arg(&zCmd, pUrlData->name, 0);
134 }
135 if( !is_safe_fossil_command(pUrlData->fossil) ){
136 fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
137 "the server.", pUrlData->fossil);
138 }
139 blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1);
140 blob_append(&zCmd, " test-http", 10);
141 if( pUrlData->path && pUrlData->path[0] ){
142 blob_append_escaped_arg(&zCmd, pUrlData->path, 1);
143 }else{
144 fossil_fatal("ssh:// URI does not specify a path to the repository");
145 }
146 if( g.fSshTrace ){
147 fossil_print("%s\n", blob_str(&zCmd)); /* Show the whole SSH command */
148 }
149 popen2(blob_str(&zCmd), &sshIn, &sshOut, &sshPid, 0);
150 if( sshPid==0 ){
151 socket_set_errmsg("cannot start ssh tunnel using [%b]", &zCmd);
152 }
153 blob_reset(&zCmd);
154 return sshPid==0;
155 }
156
157 /*
158 ** Open a connection to the server. The server is defined by the following
159 ** variables:
160 **
161 ** pUrlData->name Name of the server. Ex: fossil-scm.org
162 ** pUrlData->port TCP/IP port. Ex: 80
163 ** pUrlData->isHttps Use TLS for the connection
164 **
165 ** Return the number of errors.
166 */
transport_open(UrlData * pUrlData)167 int transport_open(UrlData *pUrlData){
168 int rc = 0;
169 if( transport.isOpen==0 ){
170 if( pUrlData->isSsh ){
171 rc = transport_ssh_open(pUrlData);
172 if( rc==0 ) transport.isOpen = 1;
173 }else if( pUrlData->isHttps ){
174 #ifdef FOSSIL_ENABLE_SSL
175 rc = ssl_open(pUrlData);
176 if( rc==0 ) transport.isOpen = 1;
177 #else
178 socket_set_errmsg("HTTPS: Fossil has been compiled without SSL support");
179 rc = 1;
180 #endif
181 }else if( pUrlData->isFile ){
182 sqlite3_uint64 iRandId;
183 sqlite3_randomness(sizeof(iRandId), &iRandId);
184 transport.zOutFile = mprintf("%s-%llu-out.http",
185 g.zRepositoryName, iRandId);
186 transport.zInFile = mprintf("%s-%llu-in.http",
187 g.zRepositoryName, iRandId);
188 transport.pFile = fossil_fopen(transport.zOutFile, "wb");
189 if( transport.pFile==0 ){
190 fossil_fatal("cannot output temporary file: %s", transport.zOutFile);
191 }
192 transport.isOpen = 1;
193 }else{
194 rc = socket_open(pUrlData);
195 if( rc==0 ) transport.isOpen = 1;
196 }
197 }
198 return rc;
199 }
200
201 /*
202 ** Close the current connection
203 */
transport_close(UrlData * pUrlData)204 void transport_close(UrlData *pUrlData){
205 if( transport.isOpen ){
206 free(transport.pBuf);
207 transport.pBuf = 0;
208 transport.nAlloc = 0;
209 transport.nUsed = 0;
210 transport.iCursor = 0;
211 if( transport.pLog ){
212 fclose(transport.pLog);
213 transport.pLog = 0;
214 }
215 if( pUrlData->isSsh ){
216 transport_ssh_close();
217 }else if( pUrlData->isHttps ){
218 #ifdef FOSSIL_ENABLE_SSL
219 ssl_close();
220 #endif
221 }else if( pUrlData->isFile ){
222 if( transport.pFile ){
223 fclose(transport.pFile);
224 transport.pFile = 0;
225 }
226 file_delete(transport.zInFile);
227 file_delete(transport.zOutFile);
228 free(transport.zInFile);
229 free(transport.zOutFile);
230 }else{
231 socket_close();
232 }
233 transport.isOpen = 0;
234 }
235 }
236
237 /*
238 ** Send content over the wire.
239 */
transport_send(UrlData * pUrlData,Blob * toSend)240 void transport_send(UrlData *pUrlData, Blob *toSend){
241 char *z = blob_buffer(toSend);
242 int n = blob_size(toSend);
243 transport.nSent += n;
244 if( pUrlData->isSsh ){
245 fwrite(z, 1, n, sshOut);
246 fflush(sshOut);
247 }else if( pUrlData->isHttps ){
248 #ifdef FOSSIL_ENABLE_SSL
249 int sent;
250 while( n>0 ){
251 sent = ssl_send(0, z, n);
252 /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
253 if( sent<=0 ) break;
254 n -= sent;
255 }
256 #endif
257 }else if( pUrlData->isFile ){
258 fwrite(z, 1, n, transport.pFile);
259 }else{
260 int sent;
261 while( n>0 ){
262 sent = socket_send(0, z, n);
263 /* printf("Sent %d of %d bytes\n", sent, n); fflush(stdout); */
264 if( sent<=0 ) break;
265 n -= sent;
266 }
267 }
268 }
269
270 /*
271 ** This routine is called when the outbound message is complete and
272 ** it is time to begin receiving a reply.
273 */
transport_flip(UrlData * pUrlData)274 void transport_flip(UrlData *pUrlData){
275 if( pUrlData->isFile ){
276 char *zCmd;
277 fclose(transport.pFile);
278 zCmd = mprintf("%$ http --in %$ --out %$ --ipaddr 127.0.0.1"
279 " %$ --localauth",
280 g.nameOfExe, transport.zOutFile, transport.zInFile, pUrlData->name
281 );
282 fossil_system(zCmd);
283 free(zCmd);
284 transport.pFile = fossil_fopen(transport.zInFile, "rb");
285 }
286 }
287
288 /*
289 ** Log all input to a file. The transport layer will take responsibility
290 ** for closing the log file when it is done.
291 */
transport_log(FILE * pLog)292 void transport_log(FILE *pLog){
293 if( transport.pLog ){
294 fclose(transport.pLog);
295 transport.pLog = 0;
296 }
297 transport.pLog = pLog;
298 }
299
300 /*
301 ** This routine is called when the inbound message has been received
302 ** and it is time to start sending again.
303 */
transport_rewind(UrlData * pUrlData)304 void transport_rewind(UrlData *pUrlData){
305 if( pUrlData->isFile ){
306 transport_close(pUrlData);
307 }
308 }
309
310 /*
311 ** Read N bytes of content directly from the wire and write into
312 ** the buffer.
313 */
transport_fetch(UrlData * pUrlData,char * zBuf,int N)314 static int transport_fetch(UrlData *pUrlData, char *zBuf, int N){
315 int got;
316 if( sshIn ){
317 int x;
318 int wanted = N;
319 got = 0;
320 while( wanted>0 ){
321 x = read(sshIn, &zBuf[got], wanted);
322 if( x<=0 ) break;
323 got += x;
324 wanted -= x;
325 }
326 }else if( pUrlData->isHttps ){
327 #ifdef FOSSIL_ENABLE_SSL
328 got = ssl_receive(0, zBuf, N);
329 #else
330 got = 0;
331 #endif
332 }else if( pUrlData->isFile ){
333 got = fread(zBuf, 1, N, transport.pFile);
334 }else{
335 got = socket_receive(0, zBuf, N, 0);
336 }
337 /* printf("received %d of %d bytes\n", got, N); fflush(stdout); */
338 if( transport.pLog ){
339 fwrite(zBuf, 1, got, transport.pLog);
340 fflush(transport.pLog);
341 }
342 return got;
343 }
344
345 /*
346 ** Read N bytes of content from the wire and store in the supplied buffer.
347 ** Return the number of bytes actually received.
348 */
transport_receive(UrlData * pUrlData,char * zBuf,int N)349 int transport_receive(UrlData *pUrlData, char *zBuf, int N){
350 int onHand; /* Bytes current held in the transport buffer */
351 int nByte = 0; /* Bytes of content received */
352
353 onHand = transport.nUsed - transport.iCursor;
354 if( g.fSshTrace){
355 printf("Reading %d bytes with %d on hand... ", N, onHand);
356 fflush(stdout);
357 }
358 if( onHand>0 ){
359 int toMove = onHand;
360 if( toMove>N ) toMove = N;
361 /* printf("bytes on hand: %d of %d\n", toMove, N); fflush(stdout); */
362 memcpy(zBuf, &transport.pBuf[transport.iCursor], toMove);
363 transport.iCursor += toMove;
364 if( transport.iCursor>=transport.nUsed ){
365 transport.nUsed = 0;
366 transport.iCursor = 0;
367 }
368 N -= toMove;
369 zBuf += toMove;
370 nByte += toMove;
371 }
372 if( N>0 ){
373 int got = transport_fetch(pUrlData, zBuf, N);
374 if( got>0 ){
375 nByte += got;
376 transport.nRcvd += got;
377 }
378 }
379 if( g.fSshTrace ) printf("Got %d bytes\n", nByte);
380 return nByte;
381 }
382
383 /*
384 ** Load up to N new bytes of content into the transport.pBuf buffer.
385 ** The buffer itself might be moved. And the transport.iCursor value
386 ** might be reset to 0.
387 */
transport_load_buffer(UrlData * pUrlData,int N)388 static void transport_load_buffer(UrlData *pUrlData, int N){
389 int i, j;
390 if( transport.nAlloc==0 ){
391 transport.nAlloc = N;
392 transport.pBuf = fossil_malloc( N );
393 transport.iCursor = 0;
394 transport.nUsed = 0;
395 }
396 if( transport.iCursor>0 ){
397 for(i=0, j=transport.iCursor; j<transport.nUsed; i++, j++){
398 transport.pBuf[i] = transport.pBuf[j];
399 }
400 transport.nUsed -= transport.iCursor;
401 transport.iCursor = 0;
402 }
403 if( transport.nUsed + N > transport.nAlloc ){
404 char *pNew;
405 transport.nAlloc = transport.nUsed + N;
406 pNew = fossil_realloc(transport.pBuf, transport.nAlloc);
407 transport.pBuf = pNew;
408 }
409 if( N>0 ){
410 i = transport_fetch(pUrlData, &transport.pBuf[transport.nUsed], N);
411 if( i>0 ){
412 transport.nRcvd += i;
413 transport.nUsed += i;
414 }
415 }
416 }
417
418 /*
419 ** Fetch a single line of input where a line is all text up to the next
420 ** \n character or until the end of input. Remove all trailing whitespace
421 ** from the received line and zero-terminate the result. Return a pointer
422 ** to the line.
423 **
424 ** Each call to this routine potentially overwrites the returned buffer.
425 */
transport_receive_line(UrlData * pUrlData)426 char *transport_receive_line(UrlData *pUrlData){
427 int i;
428 int iStart;
429
430 i = iStart = transport.iCursor;
431 while(1){
432 if( i >= transport.nUsed ){
433 transport_load_buffer(pUrlData, pUrlData->isSsh ? 2 : 1000);
434 i -= iStart;
435 iStart = 0;
436 if( i >= transport.nUsed ){
437 transport.pBuf[i] = 0;
438 transport.iCursor = i;
439 break;
440 }
441 }
442 if( transport.pBuf[i]=='\n' ){
443 transport.iCursor = i+1;
444 while( i>=iStart && fossil_isspace(transport.pBuf[i]) ){
445 transport.pBuf[i] = 0;
446 i--;
447 }
448 break;
449 }
450 i++;
451 }
452 if( g.fSshTrace ) printf("Got line: [%s]\n", &transport.pBuf[iStart]);
453 return &transport.pBuf[iStart];
454 }
455
456 /*
457 ** Global transport shutdown
458 */
transport_global_shutdown(UrlData * pUrlData)459 void transport_global_shutdown(UrlData *pUrlData){
460 if( pUrlData->isSsh ){
461 transport_ssh_close();
462 }
463 if( pUrlData->isHttps ){
464 #ifdef FOSSIL_ENABLE_SSL
465 ssl_global_shutdown();
466 #endif
467 }else{
468 socket_global_shutdown();
469 }
470 }
471
472 /*
473 ** Close SSH transport.
474 */
transport_ssh_close(void)475 void transport_ssh_close(void){
476 if( sshPid ){
477 /*printf("Closing SSH tunnel: ");*/
478 fflush(stdout);
479 pclose2(sshIn, sshOut, sshPid);
480 sshPid = 0;
481 }
482 }
483