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