ngrok-1.6+dfsg.orig/0000755000000000000000000000000012232501726013054 5ustar rootrootngrok-1.6+dfsg.orig/.travis.yml0000644000000000000000000000006212232501726015163 0ustar rootrootscript: make release-all install: true go: - 1.1 ngrok-1.6+dfsg.orig/CONTRIBUTORS0000644000000000000000000000030212232501726014727 0ustar rootrootContributors to ngrok, both large and small: Alan Shreve (inconshreveable) Kyle Conroy (kyleconroy) Caleb Spare (cespare) Stephen Huenneke (skastel) Nick Presta (nickpresta) ngrok-1.6+dfsg.orig/assets/0000755000000000000000000000000012232501726014356 5ustar rootrootngrok-1.6+dfsg.orig/assets/server/0000755000000000000000000000000012232501726015664 5ustar rootrootngrok-1.6+dfsg.orig/assets/server/tls/0000755000000000000000000000000012232501726016466 5ustar rootrootngrok-1.6+dfsg.orig/assets/server/tls/snakeoil.crt0000644000000000000000000000342112232501726021005 0ustar rootroot-----BEGIN CERTIFICATE----- MIIFDDCCAvQCAQEwDQYJKoZIhvcNAQEFBQAwTDELMAkGA1UEBhMCVVMxEzARBgNV BAgTCkNhbGlmb3JuaWExEjAQBgNVBAoTCW5ncm9rLmNvbTEUMBIGA1UEAxQLKi5u Z3Jvay5jb20wHhcNMTMwNjAzMDQyMzAwWhcNMjMwNjAxMDQyMzAwWjBMMQswCQYD VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEChMJbmdyb2suY29t MRQwEgYDVQQDFAsqLm5ncm9rLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBANouXC6wJacPIFYEiKIGGhliShxsy181LJ064ZTD3ARMTi9ONOsRkHXT oM0CV1ZNDniPp3JILiGL52FPp0hdJegQ+pzz/gDjX0h9MCWexM4sqLpY0TfEqk9l HWKPJg+ODK8wa1/wuQOK++m/XY3wcbZgLj23EHcGybl/xGJkaeaqGNvrJo/4RgSL /jTpp0dDWKUmKBpTH4PuD6bkl8qKQAP1eul6ZS+Gr/llFYJWqwgd3SD3Wh/61Y4n 6sDusem2NQltnlFwe5VS1LfopVtO3e2N5fveEUdbg2YDs7evNYVK+1i0kjo0dD3D tbmhe13OQ/baufxcHc1xjtGsJ57ws8nePL4eo+vFuD5Qjc9ESfzunSem0hC5AJgF rsWzIYsr7UsiuKG4gQdD17L+DogVCqRcdYXugrPEJONi8YG3eRRP/Q9klX4PWy7O Zj7KX/xBrvS5GeDNqrDVsPL0W24FJ//Sus7BSF0fc5DSYqtD4Zgf1GRi0+xSa3m9 AgJwWOHm6iC0mHwddozPLd+CP/M7PYzPRDiWY/ehoCFiqRAglYO0mhKyJvoTOjq6 TBYYp/8LcbqfqUkI6DSnZ2fNdXKDyirMSOT4K1ug1MHaq8obucLqx+5Hie5d6CKR gnN15sJwGKIy27fg6LAzi1GWuCzZ1kHqjuQh3oVfuBKnFfsdLUnFAgMBAAEwDQYJ KoZIhvcNAQEFBQADggIBADael2+SR8LC5xNCBcu4eqmil30Whb7qqr16EHU/MSQb nmFMxBqTa2B8RDZIIkb7LukH8rsAdU8Bzkc2yRdjoAfEMMcJA/fMpwuaXI5cuaKV idZFpNUyR+K5UG/CntcCvwzZp4//g+LVK9qPDZ2BJmA/PMR6OphRwRwG+ruSLUCi ywgFFhNlPMpPZ49vsFm/Q0A4JmLpZXaARt53zNbNiHT1FgTP/9L1HIpkbfoFQT6R pB/VYe8O+GmrwaL3l+L8aO07JRM+u0OKNPxOgxWgE7UngiWulXpnIHAXYxTVMQ8V 1IYutv9bYJ/+TAy9Kxzc4Q33K+5qvS58GCAjOF10NnHkiTQPbhUWISoj21eZJ1hb Z4BbC5z/Y8zNbMao0ACF0QmlnXlt0YrkjkcZQuqerzpC6YMw32vsemREc55X9UQM mVbnDah4xyPM+yNIg/uKHVTYJ6+4TttWEp1JGRaP2EOYr0yGe4XFFrKIPH2DlApM nNfqR+Mfx9WZS3n6FuWPGWUDBye2fdxOjW9Jwc/+JDR3BsS65LlzPrs6C8TcNnnD EdWJ4klYzWkuSdUiV1EXbsB1sSIKmUud2f4vJuOqlBsgS8/mTxjk123uXaN9zaN6 A9cMWQI2MbobK0HErkk0QyNTtVTKumEEko/c2ktsn8lrdJcKqCeQ9EKwp6r0Lmtp -----END CERTIFICATE----- ngrok-1.6+dfsg.orig/assets/server/tls/snakeoil.key0000644000000000000000000000625312232501726021013 0ustar rootroot-----BEGIN RSA PRIVATE KEY----- MIIJKAIBAAKCAgEA2i5cLrAlpw8gVgSIogYaGWJKHGzLXzUsnTrhlMPcBExOL040 6xGQddOgzQJXVk0OeI+nckguIYvnYU+nSF0l6BD6nPP+AONfSH0wJZ7EziyouljR N8SqT2UdYo8mD44MrzBrX/C5A4r76b9djfBxtmAuPbcQdwbJuX/EYmRp5qoY2+sm j/hGBIv+NOmnR0NYpSYoGlMfg+4PpuSXyopAA/V66XplL4av+WUVglarCB3dIPda H/rVjifqwO6x6bY1CW2eUXB7lVLUt+ilW07d7Y3l+94RR1uDZgOzt681hUr7WLSS OjR0PcO1uaF7Xc5D9tq5/FwdzXGO0awnnvCzyd48vh6j68W4PlCNz0RJ/O6dJ6bS ELkAmAWuxbMhiyvtSyK4obiBB0PXsv4OiBUKpFx1he6Cs8Qk42Lxgbd5FE/9D2SV fg9bLs5mPspf/EGu9LkZ4M2qsNWw8vRbbgUn/9K6zsFIXR9zkNJiq0PhmB/UZGLT 7FJreb0CAnBY4ebqILSYfB12jM8t34I/8zs9jM9EOJZj96GgIWKpECCVg7SaErIm +hM6OrpMFhin/wtxup+pSQjoNKdnZ811coPKKsxI5PgrW6DUwdqryhu5wurH7keJ 7l3oIpGCc3XmwnAYojLbt+DosDOLUZa4LNnWQeqO5CHehV+4EqcV+x0tScUCAwEA AQKCAgBb6Ru8L0gtUBn3IoHMf3WPK/C8eLhTqzrYIW3WFYwh42MsWm3AeO26NSSQ OGRCXsOx1hJb+jw0tZMLU1rNCTBmyoBIjiB6j04cY2Bc+L0/fWC236ODMr3sJFR0 qIkIFHcTdfpFuEq4S1xD4/GtUZUVlv7j0LKG8b0Y/9HjARn7qbw/KJheHeChGbhE 4gkt5Bj7uU87h7jHAwpk6/dlw0ekY00b/guSMdL/5K1i8s+p46q7sHeu8SP1dqtW Cze3lKJTDnKbLB9jkDk8IC1IgbjL0fMIX0w4Gz0HRJf40T5ioGuxup+/FUnCmyd6 w6QMqE/JNesTfFqxqRzZBwTJ1+xkXkSFmFhZ9McfxmEtozau0LH6TtkGmHvfmn6/ I8ImlIRyHGOUVHsEAR8c7aD9dag0LbXmjyolfin8Dow130/G3hTXYnDETx66k1RT Bh6kDEYvuxvFqkXYKRAu5u11vQA9udYqN4i+htQ2OOYOEKNX8Kxy562rXgd85MyT eTL4Dmh/6J7VtWG4nN2G8pnj5Lg99Q8fzEFQmFLFcK0kJwHu04pJaqrHL+uHHd5T KOfPAr0pUbeu2sVd9chgDhADUE1/8YPhSCsHudM4No4tDCZVEqKJ1pGLCpUw3H1v h0aA/B582p2CGFpGoInKfFKvUbYt2neDZZQygmYCUHywqNxQAQKCAQEA/H0MCJYV VRRLufZkz3fpC+wT+coqbguwrT2bpVBuNdvyDKYIkbcSDtIVNsO/1p1/CGormDll k1jNFhR+lqBB8QQfb1ZetPohxzPNncPiJypb9Uivu7GG1MNUHJUecFt8uITcY93s UgRBVsCpLgUP7SF6m1Mpu2BaXQ/sEM8dYCSmt4Fax7LW00cIpLleZmCXoc/jsTUM ixYTM5r5UWXBhl196Lfu+WjTmoxGgFeTwF8x1wuo7mQvPfripvUX/bjGR4o5JQOw 1I0XK13yrhFkEAIV61nHBGiyhmJShPaSI2uDudx0ZkfuUIM1UFvqeqWsjATaLmOW X7lE0qNg75lsRQKCAQEA3Tcqgviss9a4Owr0BagleObUnTMFzk8bgpemNdzi47eG 7C572Wx8Gs6XgNbO28lpE0PD7KU/Rvt36wP9mzGiP0M3Zr4h81AfLvftoMMs6ytk wHJOdtPUCZqvnblvKOI409nGnoUD+3nfH/qqHk4ZL3fn4vHbxwModZfOXRrH2hJr EVqAnnexi9S0JbJJxdcSlBMI5vQMKGLCj52C2mfcF+XRMuF88bwfJ7Xcb3yhyv8i pXHHZZ9QKE7eG76UO+EGKz8gn3wWKuh5HXp3HoyKidgzXtPIbu8fd4UzXl/BTMFc jWAG0Ycsm5dTobQ0Yo0Xeju5Hs5QCtItY7OAFsH/gQKCAQBBCTC9UXNjO9wZpYbo DdoAkSnAELwHJom2xgS+e044H1Rkv6u7ZO2I1cJTHe7fKChdkYNzLW2lm50QD+1f fR4fJ9G1CwlQEpH6zrQq7Bbnwbh4IOXrMdoqGbojtqFljZs9qDNgofxKUABIiU3K pdEpYpNDSROZyULdb8l9tuu5JRewcuhgQgel2kk2rOzM8Bp+up7KuYBmnyQJCeUo e05y/sf81sv+gGrpBzLtwiEzzxF2c/FqnnGwxFv3Z3BrkVm5ebgoeZ/l0AXkzMlC 3wXoPbFJsxFZaGJ7zP22dBDGgN4oVMnCwsp3AKUN8u8d8mjUlDdi9ZH5TC6XFzBT 5zAFAoIBAFCjlXmc0MfV096iBYYyX0aNTp/nQ4yLRcn7IfmshYDhG+vonfkKFMto 1819gHaaGxWMtFUFf+WOMY6YK9Bw7WYGSKHJWXLqmBN1CUh7HVq0vMtyX6vtV/QQ UUg7movau0BuuHp8npEDQhTUOUNG0ON+4CbYZ3dKbWtAZVeHNacG48S1qwEZPL1u UiUTstTNq9YSgkI+YFgweCAGGPcouRB1FCdqDzPHkcvV/X8efZQUITsSGM+wnXW0 Gj8e38ZcJvWI04mPoD0P9WaLh/S44p+REljU9tGJlXzqL2mNmlcyfVyDzrh+gAJP zYq6uAXczNwf/UF/j6oCJ82aV2z0VwECggEBAJga2Kb1ovNLMQ8Vum1tE8BOku9D hTVpz+sFugZuY5Dzl9sPGT3UXGjZZ8pGEGpLPQK2YP5yc+ehCng3ePtXV0sPUR5W 17jibM2JYFkR9RJCCA8c++2OwxZTVJcSDUkHG7cpl5QLbzOJAWG78EziiNqZEyC0 RsYKPad6e0enYtPvYlnCYr77Y4KN6mU5Ot3OzqGeP4++dUMznL21vb2wmO6xHLR0 Mwa6w7fIXYNAOuCk0GMeYzSHZT7ZYytUUhiiTVbHZuazJR6JyR7yf3dnjkPfRVHe qLsKIVNbyWayop61fNZWfN4FgLMmUDzCNNDz9sFX09Jm1QaEo+Dun7Dw8Ss= -----END RSA PRIVATE KEY----- ngrok-1.6+dfsg.orig/assets/client/0000755000000000000000000000000012232501726015634 5ustar rootrootngrok-1.6+dfsg.orig/assets/client/page.html0000644000000000000000000001613012232501726017437 0ustar rootroot ngrok

No requests to display yet


To get started, make a request to one of your tunnel URLs:

All Requests

{{ txn.Req.MethodPath }}
{{ txn.Resp.Status }} {{ txn.Duration }}
{{TimeFormat(Txn.Start)}}
Duration {{Txn.Duration}}
IP {{Txn.ConnCtx.ClientAddr.split(":")[0]}}

{{ Req.MethodPath }}

{{ Req.RawText }}
{{ Req.RawBytes }}

{{ Resp.Status }}

{{ Resp.RawText }}
{{ Resp.RawBytes }}
ngrok-1.6+dfsg.orig/assets/client/tls/0000755000000000000000000000000012232501726016436 5ustar rootrootngrok-1.6+dfsg.orig/assets/client/tls/snakeoilca.crt0000644000000000000000000000343612232501726021267 0ustar rootroot-----BEGIN CERTIFICATE----- MIIFFDCCAvwCCQCkbN0RG/o15DANBgkqhkiG9w0BAQUFADBMMQswCQYDVQQGEwJV UzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEChMJbmdyb2suY29tMRQwEgYD VQQDFAsqLm5ncm9rLmNvbTAeFw0xMzA2MDMwMzUxNTZaFw0yMzA2MDEwMzUxNTZa MEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRIwEAYDVQQKEwlu Z3Jvay5jb20xFDASBgNVBAMUCyoubmdyb2suY29tMIICIjANBgkqhkiG9w0BAQEF AAOCAg8AMIICCgKCAgEA6QryXeKl8AWWa9uG2UbSOpooH74zLkXs3FZfk9gKvqki zXXQCRmtU6Dyn0+OuS3sE/rRmZAsSjkQG/YDtdE/SgL4dV6S62qiQngPokjR0USh PC4Hwb8TjM9W5Cd+owVzMQ0vl0AYhQk8Yc/0vX+zDOwmRWGjNKPq422usF9CJFc/ 8QY+ODJDHun8VVAkq3XfcPXgytHIqxvSJnYgDouFCA+GTsKp/65S5cigSlIrQZbH 775cTWhCjvYnq6gzyrk3RiGdb1IGuIJftMJxuJyJVbfTFtqgMGTmjHZxiLvM7dz7 j/bmrz4PvnhbQSZZLhsvP1o8mxnoNMpo/To5tHp/Ts6b5FQNL7FHpmOVLAoQ3FdX VryTjoSjiE3JLDGZINQ9MFEPgPzR8mrzqFo/6e7uB4AYlKoQo01Kzx+YmVwRKEtr VCTRZRcl66+gMkcX0ryoVnggjIWWu4d8uAh3jf++Kd0Djb/l7DCPpEgSJwZYTjCL Z6SxiBwQ4o9dEQadMGgk3tlDFCBsrHoq7NyzvXHP0BF2HKb8KREBEKCIuQj9RChW g07zmOpjngWs0CXaYly+TDP+5DZCMGD9kmXkQY9q/zqqvMt+T+/TBK9lwUsoi2Uc v93wS+TNu06aRopqPo9YZr38ka3xKPiO964pk2BoFN57g767G8k9TbhkBxitvFUC AwEAATANBgkqhkiG9w0BAQUFAAOCAgEARf/hVYntzUwFUgQrWD0l/UaCBgrlvxVC yUa8Isj3vezAhFSyZntEL+ELFv8vvQbtBGHH/tCn76WuqjwOjVL23yxkaJsrnR9+ TRNFnVeB8157+IF6HKzLCL/HIAiQ0kw/2OSLD0lZnAkg24A0/9SHcpI5GA0WlThE 4GqgcUiN9m+mL8jWG3gj+SXC7IcVS3vAvS1J7Kz0NzTh1dYkQNWJlauO2Qn95T99 plkPPh87yZO9a9bxpX9PUJkTJzOwUkZISRZEbTA0CfspUpq/phzuTViy7o2fr+To xVVa2aKT912vlQadWw5oqEK7xyxTqsYwV7CUljtCnhpS7wOZhwzI3qUk9HKH0Rt9 /HQsANuSikZosNvdM3/hv3c5DRUOwKiKdbgZCyqQf8XSeJRM1iQvcHo8U8kEJEEt dmftn+0gH3RPsV028+7XD6wraqfc7dzNzLnq2rDLSAfG3T7pp0n19JyUieWsTR45 pGcwNpXDk+weqQKknwbNoha3o51Xq/1nhRtqrUPIEgOnBWBCV8vkhXMh9Vbe+oAm LmweN1Mr6MjrHWddVn+JGcB5p+AMWr8zE+bhpPCAnupFR98z3fbOleMCWq6Q+hMP MUThqJROHamHRIJ3Iz8whIrj7EKkDBKLEfE1ExS3B9VQ+YdHy6sjexdmCIQgElq4 4SMuY/JkZlo= -----END CERTIFICATE----- ngrok-1.6+dfsg.orig/assets/client/tls/ngrokroot.crt0000644000000000000000000000276112232501726021202 0ustar rootroot-----BEGIN CERTIFICATE----- MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= -----END CERTIFICATE----- ngrok-1.6+dfsg.orig/assets/client/static/0000755000000000000000000000000012232501726017123 5ustar rootrootngrok-1.6+dfsg.orig/assets/client/static/js/0000755000000000000000000000000012270324610017534 5ustar rootrootngrok-1.6+dfsg.orig/assets/client/static/js/ngrok.js0000644000000000000000000002645612232501726021232 0ustar rootrootvar ngrok = angular.module("ngrok", ["ngSanitize"]); var hexRepr = function(bytes) { var buf = []; var ascii = []; for (var i=0; i 0x7e) { ascii.push('.'); } else { ascii.push(String.fromCharCode(b)); } buf.push(b.toString(16)); buf.push(" "); ascii.push(" "); } if (ascii.length > 0) { var charsLeft = 8 - (ascii.length / 2); for (i=0; i 0; body.hasError = !!body.Error; var syntaxClass = { "text/xml": "xml", "application/xml": "xml", "text/html": "xml", "text/css": "css", "application/json": "json", "text/javascript": "javascript", "application/javascript": "javascript", }[body.ContentType]; // decode body if (binary) { body.Text = ""; } else { body.Text = Base64.decode(body.Text).text; } // prettify var transform = { "xml": "xml", "json": "json" }[syntaxClass]; if (!body.hasError && !!transform) { try { // vkbeautify does poorly at formatting html if (body.ContentType != "text/html") { body.Text = vkbeautify[transform](body.Text); } } catch (e) { } } if (!!syntaxClass) { body.Text = hljs.highlight(syntaxClass, body.Text).value; } else { // highlight.js doesn't have a 'plaintext' syntax, so we'll just copy its escaping function. body.Text = body.Text.replace(/&/gm, '&').replace(//gm, '>'); } }; var processReq = function(req) { if (!req.RawBytes) { var decoded = Base64.decode(req.Raw); req.RawBytes = hexRepr(decoded.bytes); if (!req.Binary) { req.RawText = decoded.text; } } processBody(req.Body, req.Binary); }; var processResp = function(resp) { resp.statusClass = { '2': "text-info", '3': "muted", '4': "text-warning", '5': "text-error" }[resp.Status[0]]; if (!resp.RawBytes) { var decoded = Base64.decode(resp.Raw); resp.RawBytes = hexRepr(decoded.bytes); if (!resp.Binary) { resp.RawText = decoded.text; } } processBody(resp.Body, resp.Binary); }; var processTxn = function(txn) { processReq(txn.Req); processResp(txn.Resp); }; var preprocessTxn = function(txn) { var toFixed = function(value, precision) { var power = Math.pow(10, precision || 0); return String(Math.round(value * power) / power); } // parse nanosecond count var ns = txn.Duration; var ms = ns / (1000 * 1000); txn.Duration = ms; if (ms > 1000) { txn.Duration = toFixed(ms / 1000, 2) + "s"; } else { txn.Duration = toFixed(ms, 2) + "ms"; } }; var active; var txns = window.data.Txns; txns.forEach(function(t) { preprocessTxn(t); }); var activate = function(txn) { if (!txn.processed) { processTxn(txn); txn.processed = true; } active = txn; } if (txns.length > 0) { activate(txns[0]); } return { add: function(txnData) { txns.unshift(JSON.parse(txnData)); preprocessTxn(txns[0]); if (!active) { activate(txns[0]); } }, all: function() { return txns; }, active: function(txn) { if (!txn) { return active; } else { activate(txn); } }, isActive: function(txn) { return !!active && txn.Id == active.Id; } }; }); ngrok.directive({ "keyval": function() { return { scope: { title: "@", tuples: "=", }, replace: true, restrict: "E", template: "" + '
' + '
{{title}}
' + '' + '' + '' + '' + '' + '
{{ key }}{{ value }}
' + '
', link: function($scope) { $scope.hasKeys = function() { for (key in $scope.tuples) { return true; } return false; }; } }; }, "tabs": function() { return { scope: { "tabs": "@", "btn": "@", "onbtnclick": "&" }, replace: true, template: '' + '', link: function postLink(scope, element, attrs) { scope.tabNames = attrs.tabs.split(","); scope.activeTab = scope.tabNames[0]; scope.setTab = function(t) { scope.activeTab = t; }; scope.$parent.isTab = scope.isTab = function(t) { return t == scope.activeTab; }; }, }; }, "body": function() { return { scope: { "body": "=", "binary": "=" }, template: '' + '
' + '{{ body.Length }} bytes ' + '{{ body.RawContentType }}' + '
' + '' + '
' + '
' + '
' + '' + '
' + '' + '
' + '
' + '{{ body.Error }}' + '
', link: function($scope, $elem) { $scope.$watch(function() { return $scope.body; }, function() { if ($scope.body && $scope.body.ErrorOffset > -1) { var offset = $scope.body.ErrorOffset; function textNodes(node) { var textNodes = []; function getTextNodes(node) { if (node.nodeType == 3) { textNodes.push(node); } else { for (var i = 0, len = node.childNodes.length; i < len; ++i) { getTextNodes(node.childNodes[i]); } } } getTextNodes(node); return textNodes; } var tNodes = textNodes($elem.find("code").get(0)); for (var i=0; i -0400 return new Date(s); }, datetime: function(elem) { var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); return $t.parse(iso8601); }, isTime: function(elem) { // jQuery's `is()` doesn't play well with HTML5 in IE return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); } }); // functions that can be called via $(el).timeago('action') // init is default when no action is given // functions are called with context of a single element var functions = { init: function(){ var refresh_el = $.proxy(refresh, this); refresh_el(); var $s = $t.settings; if ($s.refreshMillis > 0) { setInterval(refresh_el, $s.refreshMillis); } }, update: function(time){ $(this).data('timeago', { datetime: $t.parse(time) }); refresh.apply(this); }, updateFromDOM: function(){ $(this).data('timeago', { datetime: $t.parse( $t.isTime(this) ? $(this).attr("datetime") : $(this).attr("title") ) }); refresh.apply(this); } }; $.fn.timeago = function(action, options) { var fn = action ? functions[action] : functions.init; if(!fn){ throw new Error("Unknown function name '"+ action +"' for timeago"); } // each over objects here and call the requested function this.each(function(){ fn.call(this, options); }); return this; }; function refresh() { var data = prepareData(this); var $s = $t.settings; if (!isNaN(data.datetime)) { if ( $s.cutoff == 0 || distance(data.datetime) < $s.cutoff) { $(this).text(inWords(data.datetime)); } } return this; } function prepareData(element) { element = $(element); if (!element.data("timeago")) { element.data("timeago", { datetime: $t.datetime(element) }); var text = $.trim(element.text()); if ($t.settings.localeTitle) { element.attr("title", element.data('timeago').datetime.toLocaleString()); } else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { element.attr("title", text); } } return element.data("timeago"); } function inWords(date) { return $t.inWords(distance(date)); } function distance(date) { return (new Date().getTime() - date.getTime()); } // fix for IE6 suckage document.createElement("abbr"); document.createElement("time"); })); ngrok-1.6+dfsg.orig/assets/client/static/js/vkbeautify.js0000644000000000000000000002434512232501726022256 0ustar rootroot/** * vkBeautify - javascript plugin to pretty-print or minify text in XML, JSON, CSS and SQL formats. * * Version - 0.99.00.beta * Copyright (c) 2012 Vadim Kiryukhin * vkiryukhin @ gmail.com * http://www.eslinstructor.net/vkbeautify/ * * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * * Pretty print * * vkbeautify.xml(text [,indent_pattern]); * vkbeautify.json(text [,indent_pattern]); * vkbeautify.css(text [,indent_pattern]); * vkbeautify.sql(text [,indent_pattern]); * * @text - String; text to beatufy; * @indent_pattern - Integer | String; * Integer: number of white spaces; * String: character string to visualize indentation ( can also be a set of white spaces ) * Minify * * vkbeautify.xmlmin(text [,preserve_comments]); * vkbeautify.jsonmin(text); * vkbeautify.cssmin(text [,preserve_comments]); * vkbeautify.sqlmin(text); * * @text - String; text to minify; * @preserve_comments - Bool; [optional]; * Set this flag to true to prevent removing comments from @text ( minxml and mincss functions only. ) * * Examples: * vkbeautify.xml(text); // pretty print XML * vkbeautify.json(text, 4 ); // pretty print JSON * vkbeautify.css(text, '. . . .'); // pretty print CSS * vkbeautify.sql(text, '----'); // pretty print SQL * * vkbeautify.xmlmin(text, true);// minify XML, preserve comments * vkbeautify.jsonmin(text);// minify JSON * vkbeautify.cssmin(text);// minify CSS, remove comments ( default ) * vkbeautify.sqlmin(text);// minify SQL * */ (function() { function createShiftArr(step) { var space = ' '; if ( isNaN(parseInt(step)) ) { // argument is string space = step; } else { // argument is integer switch(step) { case 1: space = ' '; break; case 2: space = ' '; break; case 3: space = ' '; break; case 4: space = ' '; break; case 5: space = ' '; break; case 6: space = ' '; break; case 7: space = ' '; break; case 8: space = ' '; break; case 9: space = ' '; break; case 10: space = ' '; break; case 11: space = ' '; break; case 12: space = ' '; break; } } var shift = ['\n']; // array of shifts for(ix=0;ix<100;ix++){ shift.push(shift[ix]+space); } return shift; } function vkbeautify(){ this.step = ' '; // 4 spaces this.shift = createShiftArr(this.step); }; vkbeautify.prototype.xml = function(text,step) { var ar = text.replace(/>\s{0,}<") .replace(/ or -1) { str += shift[deep]+ar[ix]; inComment = true; // end comment or // if(ar[ix].search(/-->/) > -1 || ar[ix].search(/\]>/) > -1 || ar[ix].search(/!DOCTYPE/) > -1 ) { inComment = false; } } else // end comment or // if(ar[ix].search(/-->/) > -1 || ar[ix].search(/\]>/) > -1) { str += ar[ix]; inComment = false; } else // // if( /^<\w/.exec(ar[ix-1]) && /^<\/\w/.exec(ar[ix]) && /^<[\w:\-\.\,]+/.exec(ar[ix-1]) == /^<\/[\w:\-\.\,]+/.exec(ar[ix])[0].replace('/','')) { str += ar[ix]; if(!inComment) deep--; } else // // if(ar[ix].search(/<\w/) > -1 && ar[ix].search(/<\//) == -1 && ar[ix].search(/\/>/) == -1 ) { str = !inComment ? str += shift[deep++]+ar[ix] : str += ar[ix]; } else // ... // if(ar[ix].search(/<\w/) > -1 && ar[ix].search(/<\//) > -1) { str = !inComment ? str += shift[deep]+ar[ix] : str += ar[ix]; } else // // if(ar[ix].search(/<\//) > -1) { str = !inComment ? str += shift[--deep]+ar[ix] : str += ar[ix]; } else // // if(ar[ix].search(/\/>/) > -1 ) { str = !inComment ? str += shift[deep]+ar[ix] : str += ar[ix]; } else // // if(ar[ix].search(/<\?/) > -1) { str += shift[deep]+ar[ix]; } else // xmlns // if( ar[ix].search(/xmlns\:/) > -1 || ar[ix].search(/xmlns\=/) > -1) { str += shift[deep]+ar[ix]; } else { str += ar[ix]; } } return (str[0] == '\n') ? str.slice(1) : str; } vkbeautify.prototype.json = function(text,step) { var step = step ? step : this.step; if (typeof JSON === 'undefined' ) return text; if ( typeof text === "string" ) return JSON.stringify(JSON.parse(text), null, step); if ( typeof text === "object" ) return JSON.stringify(text, null, step); return text; // text is not string nor object } vkbeautify.prototype.css = function(text, step) { var ar = text.replace(/\s{1,}/g,' ') .replace(/\{/g,"{~::~") .replace(/\}/g,"~::~}~::~") .replace(/\;/g,";~::~") .replace(/\/\*/g,"~::~/*") .replace(/\*\//g,"*/~::~") .replace(/~::~\s{0,}~::~/g,"~::~") .split('~::~'), len = ar.length, deep = 0, str = '', ix = 0, shift = step ? createShiftArr(step) : this.shift; for(ix=0;ix/g,"") .replace(/[ \r\n\t]{1,}xmlns/g, ' xmlns'); return str.replace(/>\s{0,}<"); } vkbeautify.prototype.jsonmin = function(text) { if (typeof JSON === 'undefined' ) return text; return JSON.stringify(JSON.parse(text), null, 0); } vkbeautify.prototype.cssmin = function(text, preserveComments) { var str = preserveComments ? text : text.replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g,"") ; return str.replace(/\s{1,}/g,' ') .replace(/\{\s{1,}/g,"{") .replace(/\}\s{1,}/g,"}") .replace(/\;\s{1,}/g,";") .replace(/\/\*\s{1,}/g,"/*") .replace(/\*\/\s{1,}/g,"*/"); } vkbeautify.prototype.sqlmin = function(text) { return text.replace(/\s{1,}/g," ").replace(/\s{1,}\(/,"(").replace(/\s{1,}\)/,")"); } window.vkbeautify = new vkbeautify(); })(); ngrok-1.6+dfsg.orig/assets/client/static/js/base64.js0000644000000000000000000001407612232501726021171 0ustar rootroot/* Copyright (c) 2008 Fred Palmer fred.palmer_at_gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ function StringBuffer() { this.buffer = []; } StringBuffer.prototype.append = function append(string) { this.buffer.push(string); return this; }; StringBuffer.prototype.toString = function toString() { return this.buffer.join(""); }; window.Base64 = { codex : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", encode : function (input) { var output = new StringBuffer(); var enumerator = new Utf8EncodeEnumerator(input); while (enumerator.moveNext()) { var chr1 = enumerator.current; enumerator.moveNext(); var chr2 = enumerator.current; enumerator.moveNext(); var chr3 = enumerator.current; var enc1 = chr1 >> 2; var enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); var enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); var enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output.append(this.codex.charAt(enc1) + this.codex.charAt(enc2) + this.codex.charAt(enc3) + this.codex.charAt(enc4)); } return output.toString(); }, decode : function (input) { var output = new StringBuffer(); var outputBytes = []; var enumerator = new Base64DecodeEnumerator(input); while (enumerator.moveNext()) { var charCode = enumerator.current; outputBytes.push(charCode); if (charCode < 128) output.append(String.fromCharCode(charCode)); else if ((charCode > 191) && (charCode < 224)) { enumerator.moveNext(); var charCode2 = enumerator.current; outputBytes.push(charCode2); output.append(String.fromCharCode(((charCode & 31) << 6) | (charCode2 & 63))); } else { enumerator.moveNext(); var charCode2 = enumerator.current; outputBytes.push(charCode2); enumerator.moveNext(); var charCode3 = enumerator.current; outputBytes.push(charCode3); output.append(String.fromCharCode(((charCode & 15) << 12) | ((charCode2 & 63) << 6) | (charCode3 & 63))); } } return { "bytes": outputBytes, "text": output.toString() }; } }; function Utf8EncodeEnumerator(input) { this._input = input; this._index = -1; this._buffer = []; } Utf8EncodeEnumerator.prototype = { current: Number.NaN, moveNext: function() { if (this._buffer.length > 0) { this.current = this._buffer.shift(); return true; } else if (this._index >= (this._input.length - 1)) { this.current = Number.NaN; return false; } else { var charCode = this._input.charCodeAt(++this._index); // "\r\n" -> "\n" // if ((charCode == 13) && (this._input.charCodeAt(this._index + 1) == 10)) { charCode = 10; this._index += 2; } if (charCode < 128) { this.current = charCode; } else if ((charCode > 127) && (charCode < 2048)) { this.current = (charCode >> 6) | 192; this._buffer.push((charCode & 63) | 128); } else { this.current = (charCode >> 12) | 224; this._buffer.push(((charCode >> 6) & 63) | 128); this._buffer.push((charCode & 63) | 128); } return true; } } } function Base64DecodeEnumerator(input) { this._input = input; this._index = -1; this._buffer = []; } Base64DecodeEnumerator.prototype = { current: 64, moveNext: function() { if (this._buffer.length > 0) { this.current = this._buffer.shift(); return true; } else if (this._index >= (this._input.length - 1)) { this.current = 64; return false; } else { var enc1 = Base64.codex.indexOf(this._input.charAt(++this._index)); var enc2 = Base64.codex.indexOf(this._input.charAt(++this._index)); var enc3 = Base64.codex.indexOf(this._input.charAt(++this._index)); var enc4 = Base64.codex.indexOf(this._input.charAt(++this._index)); var chr1 = (enc1 << 2) | (enc2 >> 4); var chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); var chr3 = ((enc3 & 3) << 6) | enc4; this.current = chr1; if (enc3 != 64) this._buffer.push(chr2); if (enc4 != 64) this._buffer.push(chr3); return true; } } }; ngrok-1.6+dfsg.orig/assets/client/static/img/0000755000000000000000000000000012270324610017674 5ustar rootrootngrok-1.6+dfsg.orig/assets/client/static/css/0000755000000000000000000000000012270324610017710 5ustar rootrootngrok-1.6+dfsg.orig/docs/0000755000000000000000000000000012232501726014004 5ustar rootrootngrok-1.6+dfsg.orig/docs/SELFHOSTING.md0000644000000000000000000000502412232501726016114 0ustar rootroot# How to run your own ngrokd server Running your own ngrok server is really easy! The instructions below will guide you along your way! ## 1. Get an SSL certificate ngrok provides secure tunnels via TLS, so you'll need an SSL certificate. Assuming you want to create tunnels on *.example.com, buy a wildcard SSL certificate for *.example.com. ## 2. Modify your DNS You need to use the DNS management tools given to you by your provider to create an A record which points *.example.com to the IP address of the server where you will run ngrokd. ## 3. Compile it You can compile an ngrokd server with the following command: make release-server Make sure you compile it with the GOOS/GOARCH environment variables set to the platform of your target server. Then copy the binary over to your server. ## 4. Run the server You'll run the server with the following command. ./ngrokd -tlsKey="/path/to/tls.key" -tlsCert="/path/to/tls.crt" -domain="example.com" ### Specifying your TLS certificate and key ngrok only makes TLS-encrypted connections. When you run ngrokd, you'll need to instruct it where to find your TLS certificate and private key. Specify the paths with the following switches: -tlsKey="/path/to/tls.key" -tlsCert="/path/to/tls.crt" ### Setting the server's domain When you run your own ngrokd server, you need to tell ngrokd the domain it's running on so that it knows what URLs to issue to clients. -domain="example.com" ## 5. Configure the client In order to connect with a client, you'll need to set two options in ngrok's configuration file. The ngrok configuration file is a simple YAML file that is read from ~/.ngrok by default. You may specify a custom configuration file path with the -config switch. Your config file must contain the following two options. server_addr: example.com:4443 trust_host_root_certs: true Subsitute the address of your ngrokd server for "example.com:4443". The "trust_host_root_certs" parameter instructs ngrok to trust the root certificates on your computer when establishing TLS connections to the server. By default, ngrok only trusts the root certificate for ngrok.com. ## 6. Connect with a client Then, just run ngrok as usual to connect securely to your own ngrokd server! ngrok 80 ## FAQ #### Do I really need a wildcard certificate? If you don't need to run https tunnels, then you don't need a wildcard cert. #### I don't want to pay for an SSL certificate, can I use a self-signed one? Yes, it's possible to use a self-signed certificate, but you'll need to recompile ngrok with your signing CA. ngrok-1.6+dfsg.orig/docs/CHANGELOG.md0000644000000000000000000000621412232501726015620 0ustar rootroot# Changelog ## 0.23 - 09/06/2013 - BUGFIX: Fixed a bug which caused some important HTTP headers to be omitted from request introspection and replay ## 0.22 - 09/04/2013 - FEATURE: ngrok now tunnels websocket requests ## 0.21 - 08/17/2013 - IMPROVEMENT: The ngrok web ui can now be disabled with -webport=-1 ## 0.20 - 08/17/2013 - BUGFIX: Fixed a bug where ngrok would not stop its autoupdate loop even after it should stop ## 0.19 - 08/17/2013 - BUGFIX: Fixed a bug where ngrok's would loop infinitely trying to checking for updates after the second update check - BUGFIX: Fixed a race condition in ngrokd's metrics logging immediately after start up ## 0.18 - 08/15/2013 - BUGFIX: Fixed a bug where ngrok would compare the Host header for virtual hosting using case-sensitive comparisons - BUGFIX: Fixed a bug where ngrok would not include the port number in the virtual host when not serving on port 80 - BUGFIX: Fixed a bug where ngrok would crash when trying to replay a request - IMPROVEMENT: ngrok can now indicate manual updates again - IMPROVEMENT: ngrok can now supports update channels - IMPROVEMENT: ngrok can now detect some updates that will fail before downloading ## 0.17 - 07/30/2013 - BUGFIX: Fixed an issue where ngrok's registry cache would return a URL from a different protocol ## 0.16 - 07/30/2013 - BUGFIX: Fixed an issue where ngrok would crash when parsing bad XML that wasn't a syntax error - BUGFIX: Fixed an issue where ngrok would crash when parsing bad JSON that wasn't a syntax error - BUGFIX: Fixed an issue where the web ui would sometimes not update the request body when changing requests - BUGFIX: Fixed an issue where ngrokd's registry cache would not load from file - BUGFIX: Fixed an issue where ngrokd's registry cache would not save to file - BUGFIX: Fixed an issue where ngrok would refuse requests with an Authorization header if no HTTP auth was specified. - BUGFIX: Fixed a bug where ngrok would fail to cross-compile in you hadn't compiled natively first - IMPROVEMENT: ngrok's registry cache now handles and attempts to restore TCP URLs - IMPROVEMENT: Added simple Travis CI integration to make sure ngrok compiles ## 0.15 - 07/27/2013 - FEATURE: ngrok can now update itself automatically ## 0.14 - 07/03/2013 - BUGFIX: Fix an issue where ngrok could never save/load the authtoken file on linux - BUGFIX: Fix an issue where ngrok wouldn't emit log messages while loading authtokens ## 0.13 - 07/02/2013 - FEATURE: -hostname switch on client allows you to run tunnels over custom domains (requires you CNAME your DNS) - IMPROVEMENT: ngrok client UI now shows the client IP address for a request - IMPROVEMENT: ngrok client UI now shows how long ago a request was made (uservoice request 4127487) - IMPROVEMENT: ngrokd now uses and LRU cache for tunnel affinity data - IMPROVEMENT: ngrokd can now save and restore its tunnel affinity cache to a file to preserve across restarts ## 0.12 - 06/30/2013 - IMPROVEMENT: Improved developer documentation - IMPROVEMENT: Simplified build process with custom version of go-bindata that compiles assets into binary releases - BUGFIX: Github issue #4: Raw/Binary requests bodies are no longer truncated at 8192 bytes. ngrok-1.6+dfsg.orig/docs/DEVELOPMENT.md0000644000000000000000000001272712232501726016121 0ustar rootroot# Developer's guide to ngrok ## Components The ngrok project is composed of two components, the ngrok client (ngrok) and the ngrok server (ngrokd). The ngrok client is the more complicated piece because it has UIs for displaying saved requests and responses. ## Compiling git clone git@github.com:inconshreveable/ngrok.git cd ngrok && make bin/ngrok [LOCAL PORT] There are Makefile targets for compiling just the client or server. make client make server **NB: You must compile with Go 1.1+!** ### Compiling release versions Both the client and the server contain static asset files. These include TLS/SSL certificates and the html/css/js for the client's web interface. The release versions embed all of this data into the binaries themselves, whereas the debug versions read these files from the filesystem. *You should always develop on debug versions so that you don't have to recompile when testing changes in the static assets.* There are Makefile targets for compiling the client and server for releases: make release-client make release-server make release-all ## Developing locally The strategy I use for developing on ngrok is to do the following: Add the following lines to /etc/hosts: 127.0.0.1 ngrok.me 127.0.0.1 test.ngrok.me Run ngrokd with the following options: ./bin/ngrokd -domain ngrok.me Create an ngrok configuration file, "debug.yml" with the following contents: server_addr: ngrok.me:4443 tunnels: test: proto: http: 8080 Then run ngrok with either of these commands: ./bin/ngrok -config=debug.yml -log=ngrok.log start test ./bin/ngrok -config=debug.yml -log=ngrok.log -subdomain=test 8080 This will get you setup with an ngrok client talking to an ngrok server all locally under your control. Happy hacking! ## Network protocol and tunneling At a high level, ngrok's tunneling works as follows: ### Connection Setup and Authentication 1. The client initiates a long-lived TCP connection to the server over which they will pass JSON instruction messages. This connection is called the *Control Connection*. 1. After the connection is established, the client sends an *Auth* message with authentication and version information. 1. The server validates the client's *Auth* message and sends an *AuthResp* message indicating either success or failure. ### Tunnel creation 1. The client may then ask the server to create tunnels for it by sending *ReqTunnel* messages. 1. When the server receives a *ReqTunnel* message, it will send 1 or more *NewTunnel* messages that indicate successful tunnel creation or indicate failure. ### Tunneling connections 1. When the server receives a new public connection, it locates the approriate tunnel by examining the HTTP host header (or the port number for TCP tunnels). This connection from the public internet is called a *Public Connection*. 1. The server sends a *ReqProxy* message to the client over the control connection. 1. The client initiates a new TCP connection to the server called a *Proxy Connection*. 1. The client sends a *RegProxy* message over the proxy connection so the server can associate it to a control connection (and thus the tunnels it's responsible for). 1. The server sends a *StartProxy* message over the proxy connection with metadata information about the connection (the client IP and name of the tunnel). 1. The server begins copying the traffic byte-for-byte from the public connection to the proxy connection and vice-versa. 1. The client opens a connection to the local address configured for that tunnel. This is called the *Private Connection*. 1. The client begins copying the traffic byte-for-byte from the proxied connection to the private connection and vice-versa. ### Detecting dead tunnels 1. In order to determine whether a tunnel is still alive, the client periodically sends Ping messages over the control connection to the server, which replies with Pong messages. 1. When a tunnel is detected to be dead, the server will clean up all of that tunnel's state and the client will attempt to reconnect and establish a new tunnel. ### Wire format Messages are sent over the wire as netstrings of the form: The message length is sent as a 64-bit little endian integer. ### Code The definitions and shared protocol routines lives under _src/ngrok/msg_ #### src/ngrok/msg/msg.go All of the different message types (Auth, AuthResp, ReqTunnel, RegProxy, StartProxy, etc) are defined here and their fields documented. This is a good place to go to understand exactly what messages are sent between the client and server. ## ngrokd - the server ### Code Code for the server lives under src/ngrok/server ### Entry point The ngrokd entry point is in _src/ngrok/server/main.go_. There is a stub at _src/ngrok/main/ngrokd/ngrokd.go_ for the purposes of creating a properly named binary and being in its own "main" package to comply with go's build system. ## ngrok - the client ### Code Code for the client lives under src/ngrok/client ### Entry point The ngrok entry point is in _src/ngrok/client/main.go_. There is a stub at _src/ngrok/main/ngrok/ngrok.go_ for the purposes of creating a properly named binary and being in its own "main" package to comply with go's build system. ## Static assets The html and javascript code for the ngrok web interface as well as other static assets like TLS/SSL certificates live under the top-level _assets_ directory. ## Beyond More documentation can be found in the comments of the code itself. ngrok-1.6+dfsg.orig/src/0000755000000000000000000000000012232501726013643 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/0000755000000000000000000000000012232501726014763 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/server/0000755000000000000000000000000012232501726016271 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/server/cli.go0000644000000000000000000000210412232501726017364 0ustar rootrootpackage server import ( "flag" ) type Options struct { httpAddr string httpsAddr string tunnelAddr string domain string tlsCrt string tlsKey string logto string } func parseArgs() *Options { httpAddr := flag.String("httpAddr", ":80", "Public address for HTTP connections, empty string to disable") httpsAddr := flag.String("httpsAddr", ":443", "Public address listening for HTTPS connections, emptry string to disable") tunnelAddr := flag.String("tunnelAddr", ":4443", "Public address listening for ngrok client") domain := flag.String("domain", "ngrok.com", "Domain where the tunnels are hosted") tlsCrt := flag.String("tlsCrt", "", "Path to a TLS certificate file") tlsKey := flag.String("tlsKey", "", "Path to a TLS key file") logto := flag.String("log", "stdout", "Write log messages to this file. 'stdout' and 'none' have special meanings") flag.Parse() return &Options{ httpAddr: *httpAddr, httpsAddr: *httpsAddr, tunnelAddr: *tunnelAddr, domain: *domain, tlsCrt: *tlsCrt, tlsKey: *tlsKey, logto: *logto, } } ngrok-1.6+dfsg.orig/src/ngrok/server/metrics.go0000644000000000000000000001750512232501726020276 0ustar rootrootpackage server import ( "bytes" "encoding/json" "fmt" gometrics "github.com/inconshreveable/go-metrics" "io/ioutil" "net/http" "ngrok/conn" "ngrok/log" "os" "time" ) var metrics Metrics func init() { keenApiKey := os.Getenv("KEEN_API_KEY") if keenApiKey != "" { metrics = NewKeenIoMetrics(60 * time.Second) } else { metrics = NewLocalMetrics(30 * time.Second) } } type Metrics interface { log.Logger OpenConnection(*Tunnel, conn.Conn) CloseConnection(*Tunnel, conn.Conn, time.Time, int64, int64) OpenTunnel(*Tunnel) CloseTunnel(*Tunnel) } type LocalMetrics struct { log.Logger reportInterval time.Duration windowsCounter gometrics.Counter linuxCounter gometrics.Counter osxCounter gometrics.Counter otherCounter gometrics.Counter tunnelMeter gometrics.Meter tcpTunnelMeter gometrics.Meter httpTunnelMeter gometrics.Meter connMeter gometrics.Meter lostHeartbeatMeter gometrics.Meter connTimer gometrics.Timer bytesInCount gometrics.Counter bytesOutCount gometrics.Counter /* tunnelGauge gometrics.Gauge tcpTunnelGauge gometrics.Gauge connGauge gometrics.Gauge */ } func NewLocalMetrics(reportInterval time.Duration) *LocalMetrics { metrics := LocalMetrics{ Logger: log.NewPrefixLogger("metrics"), reportInterval: reportInterval, windowsCounter: gometrics.NewCounter(), linuxCounter: gometrics.NewCounter(), osxCounter: gometrics.NewCounter(), otherCounter: gometrics.NewCounter(), tunnelMeter: gometrics.NewMeter(), tcpTunnelMeter: gometrics.NewMeter(), httpTunnelMeter: gometrics.NewMeter(), connMeter: gometrics.NewMeter(), lostHeartbeatMeter: gometrics.NewMeter(), connTimer: gometrics.NewTimer(), bytesInCount: gometrics.NewCounter(), bytesOutCount: gometrics.NewCounter(), /* metrics.tunnelGauge = gometrics.NewGauge(), metrics.tcpTunnelGauge = gometrics.NewGauge(), metrics.connGauge = gometrics.NewGauge(), */ } go metrics.Report() return &metrics } func (m *LocalMetrics) OpenTunnel(t *Tunnel) { m.tunnelMeter.Mark(1) switch t.ctl.auth.OS { case "windows": m.windowsCounter.Inc(1) case "linux": m.linuxCounter.Inc(1) case "darwin": m.osxCounter.Inc(1) default: m.otherCounter.Inc(1) } switch t.req.Protocol { case "tcp": m.tcpTunnelMeter.Mark(1) case "http": m.httpTunnelMeter.Mark(1) } } func (m *LocalMetrics) CloseTunnel(t *Tunnel) { } func (m *LocalMetrics) OpenConnection(t *Tunnel, c conn.Conn) { m.connMeter.Mark(1) } func (m *LocalMetrics) CloseConnection(t *Tunnel, c conn.Conn, start time.Time, bytesIn, bytesOut int64) { m.bytesInCount.Inc(bytesIn) m.bytesOutCount.Inc(bytesOut) } func (m *LocalMetrics) Report() { m.Info("Reporting every %d seconds", int(m.reportInterval.Seconds())) for { time.Sleep(m.reportInterval) buffer, err := json.Marshal(map[string]interface{}{ "windows": m.windowsCounter.Count(), "linux": m.linuxCounter.Count(), "osx": m.osxCounter.Count(), "other": m.otherCounter.Count(), "httpTunnelMeter.count": m.httpTunnelMeter.Count(), "tcpTunnelMeter.count": m.tcpTunnelMeter.Count(), "tunnelMeter.count": m.tunnelMeter.Count(), "tunnelMeter.m1": m.tunnelMeter.Rate1(), "connMeter.count": m.connMeter.Count(), "connMeter.m1": m.connMeter.Rate1(), "bytesIn.count": m.bytesInCount.Count(), "bytesOut.count": m.bytesOutCount.Count(), }) if err != nil { m.Error("Failed to serialize metrics: %v", err) continue } m.Info("Reporting: %s", buffer) } } type KeenIoMetric struct { Collection string Event interface{} } type KeenIoMetrics struct { log.Logger ApiKey string ProjectToken string HttpClient http.Client Metrics chan *KeenIoMetric } func NewKeenIoMetrics(batchInterval time.Duration) *KeenIoMetrics { k := &KeenIoMetrics{ Logger: log.NewPrefixLogger("metrics"), ApiKey: os.Getenv("KEEN_API_KEY"), ProjectToken: os.Getenv("KEEN_PROJECT_TOKEN"), Metrics: make(chan *KeenIoMetric, 1000), } go func() { defer func() { if r := recover(); r != nil { k.Error("KeenIoMetrics failed: %v", r) } }() batch := make(map[string][]interface{}) batchTimer := time.Tick(batchInterval) for { select { case m := <-k.Metrics: list, ok := batch[m.Collection] if !ok { list = make([]interface{}, 0) } batch[m.Collection] = append(list, m.Event) case <-batchTimer: // no metrics to report if len(batch) == 0 { continue } payload, err := json.Marshal(batch) if err != nil { k.Error("Failed to serialize metrics payload: %v, %v", batch, err) } else { for key, val := range batch { k.Debug("Reporting %d metrics for %s", len(val), key) } k.AuthedRequest("POST", "/events", bytes.NewReader(payload)) } batch = make(map[string][]interface{}) } } }() return k } func (k *KeenIoMetrics) AuthedRequest(method, path string, body *bytes.Reader) (resp *http.Response, err error) { path = fmt.Sprintf("https://api.keen.io/3.0/projects/%s%s", k.ProjectToken, path) req, err := http.NewRequest(method, path, body) if err != nil { return } req.Header.Add("Authorization", k.ApiKey) if body != nil { req.Header.Add("Content-Type", "application/json") req.ContentLength = int64(body.Len()) } requestStartAt := time.Now() resp, err = k.HttpClient.Do(req) if err != nil { k.Error("Failed to send metric event to keen.io %v", err) } else { k.Info("keen.io processed request in %f sec", time.Since(requestStartAt).Seconds()) defer resp.Body.Close() if resp.StatusCode != 200 { bytes, _ := ioutil.ReadAll(resp.Body) k.Error("Got %v response from keen.io: %s", resp.StatusCode, bytes) } } return } func (k *KeenIoMetrics) OpenConnection(t *Tunnel, c conn.Conn) { } func (k *KeenIoMetrics) CloseConnection(t *Tunnel, c conn.Conn, start time.Time, in, out int64) { event := struct { Keen KeenStruct `json:"keen"` OS string ClientId string Protocol string Url string User string Version string Reason string HttpAuth bool Subdomain bool TunnelDuration float64 ConnectionDuration float64 BytesIn int64 BytesOut int64 }{ Keen: KeenStruct{ Timestamp: start.UTC().Format("2006-01-02T15:04:05.000Z"), }, OS: t.ctl.auth.OS, ClientId: t.ctl.id, Protocol: t.req.Protocol, Url: t.url, User: t.ctl.auth.User, Version: t.ctl.auth.MmVersion, HttpAuth: t.req.HttpAuth != "", Subdomain: t.req.Subdomain != "", TunnelDuration: time.Since(t.start).Seconds(), ConnectionDuration: time.Since(start).Seconds(), BytesIn: in, BytesOut: out, } k.Metrics <- &KeenIoMetric{Collection: "CloseConnection", Event: event} } func (k *KeenIoMetrics) OpenTunnel(t *Tunnel) { } type KeenStruct struct { Timestamp string `json:"timestamp"` } func (k *KeenIoMetrics) CloseTunnel(t *Tunnel) { event := struct { Keen KeenStruct `json:"keen"` OS string ClientId string Protocol string Url string User string Version string Reason string Duration float64 HttpAuth bool Subdomain bool }{ Keen: KeenStruct{ Timestamp: t.start.UTC().Format("2006-01-02T15:04:05.000Z"), }, OS: t.ctl.auth.OS, ClientId: t.ctl.id, Protocol: t.req.Protocol, Url: t.url, User: t.ctl.auth.User, Version: t.ctl.auth.MmVersion, //Reason: reason, Duration: time.Since(t.start).Seconds(), HttpAuth: t.req.HttpAuth != "", Subdomain: t.req.Subdomain != "", } k.Metrics <- &KeenIoMetric{Collection: "CloseTunnel", Event: event} } ngrok-1.6+dfsg.orig/src/ngrok/server/assets/0000755000000000000000000000000012232501726017573 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/server/assets/assets_debug.go0000644000000000000000000000021312232501726022566 0ustar rootroot// +build !release package assets import ( "io/ioutil" ) func ReadAsset(name string) ([]byte, error) { return ioutil.ReadFile(name) } ngrok-1.6+dfsg.orig/src/ngrok/server/main.go0000644000000000000000000000642212232501726017550 0ustar rootrootpackage server import ( "crypto/tls" "math/rand" "ngrok/conn" log "ngrok/log" "ngrok/msg" "ngrok/util" "os" "runtime/debug" "time" ) const ( registryCacheSize uint64 = 1024 * 1024 // 1 MB connReadTimeout time.Duration = 10 * time.Second ) // GLOBALS var ( tunnelRegistry *TunnelRegistry controlRegistry *ControlRegistry // XXX: kill these global variables - they're only used in tunnel.go for constructing forwarding URLs opts *Options listeners map[string]*conn.Listener ) func NewProxy(pxyConn conn.Conn, regPxy *msg.RegProxy) { // fail gracefully if the proxy connection fails to register defer func() { if r := recover(); r != nil { pxyConn.Warn("Failed with error: %v", r) pxyConn.Close() } }() // set logging prefix pxyConn.SetType("pxy") // look up the control connection for this proxy pxyConn.Info("Registering new proxy for %s", regPxy.ClientId) ctl := controlRegistry.Get(regPxy.ClientId) if ctl == nil { panic("No client found for identifier: " + regPxy.ClientId) } ctl.RegisterProxy(pxyConn) } // Listen for incoming control and proxy connections // We listen for incoming control and proxy connections on the same port // for ease of deployment. The hope is that by running on port 443, using // TLS and running all connections over the same port, we can bust through // restrictive firewalls. func tunnelListener(addr string, tlsConfig *tls.Config) { // listen for incoming connections listener, err := conn.Listen(addr, "tun", tlsConfig) if err != nil { panic(err) } log.Info("Listening for control and proxy connections on %s", listener.Addr.String()) for c := range listener.Conns { go func(tunnelConn conn.Conn) { // don't crash on panics defer func() { if r := recover(); r != nil { tunnelConn.Info("tunnelListener failed with error %v: %s", r, debug.Stack()) } }() tunnelConn.SetReadDeadline(time.Now().Add(connReadTimeout)) var rawMsg msg.Message if rawMsg, err = msg.ReadMsg(tunnelConn); err != nil { tunnelConn.Warn("Failed to read message: %v", err) tunnelConn.Close() return } // don't timeout after the initial read, tunnel heartbeating will kill // dead connections tunnelConn.SetReadDeadline(time.Time{}) switch m := rawMsg.(type) { case *msg.Auth: NewControl(tunnelConn, m) case *msg.RegProxy: NewProxy(tunnelConn, m) default: tunnelConn.Close() } }(c) } } func Main() { // parse options opts = parseArgs() // init logging log.LogTo(opts.logto) // seed random number generator seed, err := util.RandomSeed() if err != nil { panic(err) } rand.Seed(seed) // init tunnel/control registry registryCacheFile := os.Getenv("REGISTRY_CACHE_FILE") tunnelRegistry = NewTunnelRegistry(registryCacheSize, registryCacheFile) controlRegistry = NewControlRegistry() // start listeners listeners = make(map[string]*conn.Listener) // load tls configuration tlsConfig, err := LoadTLSConfig(opts.tlsCrt, opts.tlsKey) if err != nil { panic(err) } // listen for http if opts.httpAddr != "" { listeners["http"] = startHttpListener(opts.httpAddr, nil) } // listen for https if opts.httpsAddr != "" { listeners["https"] = startHttpListener(opts.httpsAddr, tlsConfig) } // ngrok clients tunnelListener(opts.tunnelAddr, tlsConfig) } ngrok-1.6+dfsg.orig/src/ngrok/server/registry.go0000644000000000000000000001223412232501726020472 0ustar rootrootpackage server import ( "encoding/gob" "fmt" "net" "ngrok/cache" "ngrok/log" "sync" "time" ) const ( cacheSaveInterval time.Duration = 10 * time.Minute ) type cacheUrl string func (url cacheUrl) Size() int { return len(url) } // TunnelRegistry maps a tunnel URL to Tunnel structures type TunnelRegistry struct { tunnels map[string]*Tunnel affinity *cache.LRUCache log.Logger sync.RWMutex } func NewTunnelRegistry(cacheSize uint64, cacheFile string) *TunnelRegistry { registry := &TunnelRegistry{ tunnels: make(map[string]*Tunnel), affinity: cache.NewLRUCache(cacheSize), Logger: log.NewPrefixLogger("registry", "tun"), } // LRUCache uses Gob encoding. Unfortunately, Gob is fickle and will fail // to encode or decode any non-primitive types that haven't been "registered" // with it. Since we store cacheUrl objects, we need to register them here first // for the encoding/decoding to work var urlobj cacheUrl gob.Register(urlobj) // try to load and then periodically save the affinity cache to file, if specified if cacheFile != "" { err := registry.affinity.LoadItemsFromFile(cacheFile) if err != nil { registry.Error("Failed to load affinity cache %s: %v", cacheFile, err) } registry.SaveCacheThread(cacheFile, cacheSaveInterval) } else { registry.Info("No affinity cache specified") } return registry } // Spawns a goroutine the periodically saves the cache to a file. func (r *TunnelRegistry) SaveCacheThread(path string, interval time.Duration) { go func() { r.Info("Saving affinity cache to %s every %s", path, interval.String()) for { time.Sleep(interval) r.Debug("Saving affinity cache") err := r.affinity.SaveItemsToFile(path) if err != nil { r.Error("Failed to save affinity cache: %v", err) } else { r.Info("Saved affinity cache") } } }() } // Register a tunnel with a specific url, returns an error // if a tunnel is already registered at that url func (r *TunnelRegistry) Register(url string, t *Tunnel) error { r.Lock() defer r.Unlock() if r.tunnels[url] != nil { return fmt.Errorf("The tunnel %s is already registered.", url) } r.tunnels[url] = t return nil } func (r *TunnelRegistry) cacheKeys(t *Tunnel) (ip string, id string) { clientIp := t.ctl.conn.RemoteAddr().(*net.TCPAddr).IP.String() clientId := t.ctl.id ipKey := fmt.Sprintf("client-ip-%s:%s", t.req.Protocol, clientIp) idKey := fmt.Sprintf("client-id-%s:%s", t.req.Protocol, clientId) return ipKey, idKey } func (r *TunnelRegistry) GetCachedRegistration(t *Tunnel) (url string) { ipCacheKey, idCacheKey := r.cacheKeys(t) // check cache for ID first, because we prefer that over IP which might // not be specific to a user because of NATs if v, ok := r.affinity.Get(idCacheKey); ok { url = string(v.(cacheUrl)) t.Debug("Found registry affinity %s for %s", url, idCacheKey) } else if v, ok := r.affinity.Get(ipCacheKey); ok { url = string(v.(cacheUrl)) t.Debug("Found registry affinity %s for %s", url, ipCacheKey) } return } func (r *TunnelRegistry) RegisterAndCache(url string, t *Tunnel) (err error) { if err = r.Register(url, t); err == nil { // we successfully assigned a url, cache it ipCacheKey, idCacheKey := r.cacheKeys(t) r.affinity.Set(ipCacheKey, cacheUrl(url)) r.affinity.Set(idCacheKey, cacheUrl(url)) } return } // Register a tunnel with the following process: // Consult the affinity cache to try to assign a previously used tunnel url if possible // Generate new urls repeatedly with the urlFn and register until one is available. func (r *TunnelRegistry) RegisterRepeat(urlFn func() string, t *Tunnel) (string, error) { url := r.GetCachedRegistration(t) if url == "" { url = urlFn() } maxAttempts := 5 for i := 0; i < maxAttempts; i++ { if err := r.RegisterAndCache(url, t); err != nil { // pick a new url and try again url = urlFn() } else { // we successfully assigned a url, we're done return url, nil } } return "", fmt.Errorf("Failed to assign a URL after %d attempts!", maxAttempts) } func (r *TunnelRegistry) Del(url string) { r.Lock() defer r.Unlock() delete(r.tunnels, url) } func (r *TunnelRegistry) Get(url string) *Tunnel { r.RLock() defer r.RUnlock() return r.tunnels[url] } // ControlRegistry maps a client ID to Control structures type ControlRegistry struct { controls map[string]*Control log.Logger sync.RWMutex } func NewControlRegistry() *ControlRegistry { return &ControlRegistry{ controls: make(map[string]*Control), Logger: log.NewPrefixLogger("registry", "ctl"), } } func (r *ControlRegistry) Get(clientId string) *Control { r.RLock() defer r.RUnlock() return r.controls[clientId] } func (r *ControlRegistry) Add(clientId string, ctl *Control) (oldCtl *Control) { r.Lock() defer r.Unlock() oldCtl = r.controls[clientId] if oldCtl != nil { oldCtl.Replaced(ctl) } r.controls[clientId] = ctl r.Info("Registered control with id %s", clientId) return } func (r *ControlRegistry) Del(clientId string) error { r.Lock() defer r.Unlock() if r.controls[clientId] == nil { return fmt.Errorf("No control found for client id: %s", clientId) } else { r.Info("Removed control registry id %s", clientId) delete(r.controls, clientId) return nil } } ngrok-1.6+dfsg.orig/src/ngrok/server/control.go0000644000000000000000000001761712232501726020314 0ustar rootrootpackage server import ( "fmt" "io" "ngrok/conn" "ngrok/msg" "ngrok/util" "ngrok/version" "runtime/debug" "strings" "time" ) const ( pingTimeoutInterval = 30 * time.Second connReapInterval = 10 * time.Second controlWriteTimeout = 10 * time.Second proxyStaleDuration = 60 * time.Second proxyMaxPoolSize = 10 ) type Control struct { // auth message auth *msg.Auth // actual connection conn conn.Conn // put a message in this channel to send it over // conn to the client out chan (msg.Message) // read from this channel to get the next message sent // to us over conn by the client in chan (msg.Message) // the last time we received a ping from the client - for heartbeats lastPing time.Time // all of the tunnels this control connection handles tunnels []*Tunnel // proxy connections proxies chan conn.Conn // identifier id string // synchronizer for controlled shutdown of writer() writerShutdown *util.Shutdown // synchronizer for controlled shutdown of reader() readerShutdown *util.Shutdown // synchronizer for controlled shutdown of manager() managerShutdown *util.Shutdown // synchronizer for controller shutdown of entire Control shutdown *util.Shutdown } func NewControl(ctlConn conn.Conn, authMsg *msg.Auth) { var err error // create the object c := &Control{ auth: authMsg, conn: ctlConn, out: make(chan msg.Message), in: make(chan msg.Message), proxies: make(chan conn.Conn, 10), lastPing: time.Now(), writerShutdown: util.NewShutdown(), readerShutdown: util.NewShutdown(), managerShutdown: util.NewShutdown(), shutdown: util.NewShutdown(), } failAuth := func(e error) { _ = msg.WriteMsg(ctlConn, &msg.AuthResp{Error: e.Error()}) ctlConn.Close() } // register the clientid c.id = authMsg.ClientId if c.id == "" { // it's a new session, assign an ID if c.id, err = util.SecureRandId(16); err != nil { failAuth(err) return } } // set logging prefix ctlConn.SetType("ctl") ctlConn.AddLogPrefix(c.id) if authMsg.Version != version.Proto { failAuth(fmt.Errorf("Incompatible versions. Server %s, client %s. Download a new version at http://ngrok.com", version.MajorMinor(), authMsg.Version)) return } // register the control if replaced := controlRegistry.Add(c.id, c); replaced != nil { replaced.shutdown.WaitComplete() } // start the writer first so that the following messages get sent go c.writer() // Respond to authentication c.out <- &msg.AuthResp{ Version: version.Proto, MmVersion: version.MajorMinor(), ClientId: c.id, } // As a performance optimization, ask for a proxy connection up front c.out <- &msg.ReqProxy{} // manage the connection go c.manager() go c.reader() go c.stopper() } // Register a new tunnel on this control connection func (c *Control) registerTunnel(rawTunnelReq *msg.ReqTunnel) { for _, proto := range strings.Split(rawTunnelReq.Protocol, "+") { tunnelReq := *rawTunnelReq tunnelReq.Protocol = proto c.conn.Debug("Registering new tunnel") t, err := NewTunnel(&tunnelReq, c) if err != nil { c.out <- &msg.NewTunnel{Error: err.Error()} if len(c.tunnels) == 0 { c.shutdown.Begin() } // we're done return } // add it to the list of tunnels c.tunnels = append(c.tunnels, t) // acknowledge success c.out <- &msg.NewTunnel{ Url: t.url, Protocol: proto, ReqId: rawTunnelReq.ReqId, } rawTunnelReq.Hostname = strings.Replace(t.url, proto+"://", "", 1) } } func (c *Control) manager() { // don't crash on panics defer func() { if err := recover(); err != nil { c.conn.Info("Control::manager failed with error %v: %s", err, debug.Stack()) } }() // kill everything if the control manager stops defer c.shutdown.Begin() // notify that manager() has shutdown defer c.managerShutdown.Complete() // reaping timer for detecting heartbeat failure reap := time.NewTicker(connReapInterval) defer reap.Stop() for { select { case <-reap.C: if time.Since(c.lastPing) > pingTimeoutInterval { c.conn.Info("Lost heartbeat") c.shutdown.Begin() } case mRaw, ok := <-c.in: // c.in closes to indicate shutdown if !ok { return } switch m := mRaw.(type) { case *msg.ReqTunnel: c.registerTunnel(m) case *msg.Ping: c.lastPing = time.Now() c.out <- &msg.Pong{} } } } } func (c *Control) writer() { defer func() { if err := recover(); err != nil { c.conn.Info("Control::writer failed with error %v: %s", err, debug.Stack()) } }() // kill everything if the writer() stops defer c.shutdown.Begin() // notify that we've flushed all messages defer c.writerShutdown.Complete() // write messages to the control channel for m := range c.out { c.conn.SetWriteDeadline(time.Now().Add(controlWriteTimeout)) if err := msg.WriteMsg(c.conn, m); err != nil { panic(err) } } } func (c *Control) reader() { defer func() { if err := recover(); err != nil { c.conn.Warn("Control::reader failed with error %v: %s", err, debug.Stack()) } }() // kill everything if the reader stops defer c.shutdown.Begin() // notify that we're done defer c.readerShutdown.Complete() // read messages from the control channel for { if msg, err := msg.ReadMsg(c.conn); err != nil { if err == io.EOF { c.conn.Info("EOF") return } else { panic(err) } } else { // this can also panic during shutdown c.in <- msg } } } func (c *Control) stopper() { defer func() { if r := recover(); r != nil { c.conn.Error("Failed to shut down control: %v", r) } }() // wait until we're instructed to shutdown c.shutdown.WaitBegin() // remove ourself from the control registry controlRegistry.Del(c.id) // shutdown manager() so that we have no more work to do close(c.in) c.managerShutdown.WaitComplete() // shutdown writer() close(c.out) c.writerShutdown.WaitComplete() // close connection fully c.conn.Close() // shutdown all of the tunnels for _, t := range c.tunnels { t.Shutdown() } // shutdown all of the proxy connections close(c.proxies) for p := range c.proxies { p.Close() } c.shutdown.Complete() c.conn.Info("Shutdown complete") } func (c *Control) RegisterProxy(conn conn.Conn) { conn.AddLogPrefix(c.id) conn.SetDeadline(time.Now().Add(proxyStaleDuration)) select { case c.proxies <- conn: conn.Info("Registered") default: conn.Info("Proxies buffer is full, discarding.") conn.Close() } } // Remove a proxy connection from the pool and return it // If not proxy connections are in the pool, request one // and wait until it is available // Returns an error if we couldn't get a proxy because it took too long // or the tunnel is closing func (c *Control) GetProxy() (proxyConn conn.Conn, err error) { var ok bool // get a proxy connection from the pool select { case proxyConn, ok = <-c.proxies: if !ok { err = fmt.Errorf("No proxy connections available, control is closing") return } default: // no proxy available in the pool, ask for one over the control channel c.conn.Debug("No proxy in pool, requesting proxy from control . . .") if err = util.PanicToError(func() { c.out <- &msg.ReqProxy{} }); err != nil { return } select { case proxyConn, ok = <-c.proxies: if !ok { err = fmt.Errorf("No proxy connections available, control is closing") return } case <-time.After(pingTimeoutInterval): err = fmt.Errorf("Timeout trying to get proxy connection") return } } return } // Called when this control is replaced by another control // this can happen if the network drops out and the client reconnects // before the old tunnel has lost its heartbeat func (c *Control) Replaced(replacement *Control) { c.conn.Info("Replaced by control: %s", replacement.conn.Id()) // set the control id to empty string so that when stopper() // calls registry.Del it won't delete the replacement c.id = "" // tell the old one to shutdown c.shutdown.Begin() } ngrok-1.6+dfsg.orig/src/ngrok/server/http.go0000644000000000000000000000504712232501726017605 0ustar rootrootpackage server import ( "crypto/tls" "fmt" "net" "ngrok/conn" "ngrok/log" "strings" "time" ) const ( NotAuthorized = `HTTP/1.0 401 Not Authorized WWW-Authenticate: Basic realm="ngrok" Content-Length: 23 Authorization required ` NotFound = `HTTP/1.0 404 Not Found Content-Length: %d Tunnel %s not found ` BadRequest = `HTTP/1.0 400 Bad Request Content-Length: 12 Bad Request ` ) // Listens for new http(s) connections from the public internet func startHttpListener(addr string, tlsCfg *tls.Config) (listener *conn.Listener) { // bind/listen for incoming connections var err error if listener, err = conn.Listen(addr, "pub", tlsCfg); err != nil { panic(err) } proto := "http" if tlsCfg != nil { proto = "https" } log.Info("Listening for public %s connections on %v", proto, listener.Addr.String()) go func() { for conn := range listener.Conns { go httpHandler(conn, proto) } }() return } // Handles a new http connection from the public internet func httpHandler(tcpConn net.Conn, proto string) { // wrap up the connection for logging conn := conn.NewHttp(tcpConn, "pub") defer conn.Close() defer func() { // recover from failures if r := recover(); r != nil { conn.Warn("httpHandler failed with error %v", r) } }() // Make sure we detect dead connections while we decide how to multiplex conn.SetDeadline(time.Now().Add(connReadTimeout)) // read out the http request req, err := conn.ReadRequest() if err != nil { conn.Warn("Failed to read valid %s request: %v", proto, err) conn.Write([]byte(BadRequest)) return } // read out the Host header from the request host := strings.ToLower(req.Host) conn.Debug("Found hostname %s in request", host) // multiplex to find the right backend host tunnel := tunnelRegistry.Get(fmt.Sprintf("%s://%s", proto, host)) if tunnel == nil { conn.Info("No tunnel found for hostname %s", host) conn.Write([]byte(fmt.Sprintf(NotFound, len(host)+18, host))) return } // If the client specified http auth and it doesn't match this request's auth // then fail the request with 401 Not Authorized and request the client reissue the // request with basic authdeny the request if tunnel.req.HttpAuth != "" && req.Header.Get("Authorization") != tunnel.req.HttpAuth { conn.Info("Authentication failed: %s", req.Header.Get("Authorization")) conn.Write([]byte(NotAuthorized)) return } // dead connections will now be handled by tunnel heartbeating and the client conn.SetDeadline(time.Time{}) // let the tunnel handle the connection now tunnel.HandlePublicConnection(conn) } ngrok-1.6+dfsg.orig/src/ngrok/server/tunnel.go0000644000000000000000000001623312232501726020132 0ustar rootrootpackage server import ( "encoding/base64" "fmt" "math/rand" "net" "ngrok/conn" "ngrok/log" "ngrok/msg" "ngrok/util" "os" "strconv" "strings" "sync/atomic" "time" ) var defaultPortMap = map[string]int{ "http": 80, "https": 443, "smtp": 25, } /** * Tunnel: A control connection, metadata and proxy connections which * route public traffic to a firewalled endpoint. */ type Tunnel struct { // request that opened the tunnel req *msg.ReqTunnel // time when the tunnel was opened start time.Time // public url url string // tcp listener listener *net.TCPListener // control connection ctl *Control // logger log.Logger // closing closing int32 } // Common functionality for registering virtually hosted protocols func registerVhost(t *Tunnel, protocol string, servingPort int) (err error) { vhost := os.Getenv("VHOST") if vhost == "" { vhost = fmt.Sprintf("%s:%d", opts.domain, servingPort) } // Canonicalize virtual host by removing default port (e.g. :80 on HTTP) defaultPort, ok := defaultPortMap[protocol] if !ok { return fmt.Errorf("Couldn't find default port for protocol %s", protocol) } defaultPortSuffix := fmt.Sprintf(":%d", defaultPort) if strings.HasSuffix(vhost, defaultPortSuffix) { vhost = vhost[0 : len(vhost)-len(defaultPortSuffix)] } // Canonicalize by always using lower-case vhost = strings.ToLower(vhost) // Register for specific hostname hostname := strings.ToLower(strings.TrimSpace(t.req.Hostname)) if hostname != "" { t.url = fmt.Sprintf("%s://%s", protocol, hostname) return tunnelRegistry.Register(t.url, t) } // Register for specific subdomain subdomain := strings.ToLower(strings.TrimSpace(t.req.Subdomain)) if subdomain != "" { t.url = fmt.Sprintf("%s://%s.%s", protocol, subdomain, vhost) return tunnelRegistry.Register(t.url, t) } // Register for random URL t.url, err = tunnelRegistry.RegisterRepeat(func() string { return fmt.Sprintf("%s://%x.%s", protocol, rand.Int31(), vhost) }, t) return } // Create a new tunnel from a registration message received // on a control channel func NewTunnel(m *msg.ReqTunnel, ctl *Control) (t *Tunnel, err error) { t = &Tunnel{ req: m, start: time.Now(), ctl: ctl, Logger: log.NewPrefixLogger(), } proto := t.req.Protocol switch proto { case "tcp": bindTcp := func(port int) error { if t.listener, err = net.ListenTCP("tcp", &net.TCPAddr{IP: net.ParseIP("0.0.0.0"), Port: port}); err != nil { err = t.ctl.conn.Error("Error binding TCP listener: %v", err) return err } // create the url addr := t.listener.Addr().(*net.TCPAddr) t.url = fmt.Sprintf("tcp://%s:%d", opts.domain, addr.Port) // register it if err = tunnelRegistry.RegisterAndCache(t.url, t); err != nil { // This should never be possible because the OS will // only assign available ports to us. t.listener.Close() err = fmt.Errorf("TCP listener bound, but failed to register %s", t.url) return err } go t.listenTcp(t.listener) return nil } // use the custom remote port you asked for if t.req.RemotePort != 0 { bindTcp(int(t.req.RemotePort)) return } // try to return to you the same port you had before cachedUrl := tunnelRegistry.GetCachedRegistration(t) if cachedUrl != "" { var port int parts := strings.Split(cachedUrl, ":") portPart := parts[len(parts)-1] port, err = strconv.Atoi(portPart) if err != nil { t.ctl.conn.Error("Failed to parse cached url port as integer: %s", portPart) } else { // we have a valid, cached port, let's try to bind with it if bindTcp(port) != nil { t.ctl.conn.Warn("Failed to get custom port %d: %v, trying a random one", port, err) } else { // success, we're done return } } } // Bind for TCP connections bindTcp(0) return case "http", "https": l, ok := listeners[proto] if !ok { err = fmt.Errorf("Not listeneing for %s connections", proto) return } if err = registerVhost(t, proto, l.Addr.(*net.TCPAddr).Port); err != nil { return } default: err = fmt.Errorf("Protocol %s is not supported", proto) return } // pre-encode the http basic auth for fast comparisons later if m.HttpAuth != "" { m.HttpAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(m.HttpAuth)) } t.AddLogPrefix(t.Id()) t.Info("Registered new tunnel on: %s", t.ctl.conn.Id()) metrics.OpenTunnel(t) return } func (t *Tunnel) Shutdown() { t.Info("Shutting down") // mark that we're shutting down atomic.StoreInt32(&t.closing, 1) // if we have a public listener (this is a raw TCP tunnel), shut it down if t.listener != nil { t.listener.Close() } // remove ourselves from the tunnel registry tunnelRegistry.Del(t.url) // let the control connection know we're shutting down // currently, only the control connection shuts down tunnels, // so it doesn't need to know about it // t.ctl.stoptunnel <- t metrics.CloseTunnel(t) } func (t *Tunnel) Id() string { return t.url } // Listens for new public tcp connections from the internet. func (t *Tunnel) listenTcp(listener *net.TCPListener) { for { defer func() { if r := recover(); r != nil { log.Warn("listenTcp failed with error %v", r) } }() // accept public connections tcpConn, err := listener.AcceptTCP() if err != nil { // not an error, we're shutting down this tunnel if atomic.LoadInt32(&t.closing) == 1 { return } t.Error("Failed to accept new TCP connection: %v", err) continue } conn := conn.Wrap(tcpConn, "pub") conn.AddLogPrefix(t.Id()) conn.Info("New connection from %v", conn.RemoteAddr()) go t.HandlePublicConnection(conn) } } func (t *Tunnel) HandlePublicConnection(publicConn conn.Conn) { defer publicConn.Close() defer func() { if r := recover(); r != nil { publicConn.Warn("HandlePublicConnection failed with error %v", r) } }() startTime := time.Now() metrics.OpenConnection(t, publicConn) var proxyConn conn.Conn var err error for i := 0; i < (2 * proxyMaxPoolSize); i++ { // get a proxy connection if proxyConn, err = t.ctl.GetProxy(); err != nil { t.Warn("Failed to get proxy connection: %v", err) return } defer proxyConn.Close() t.Info("Got proxy connection %s", proxyConn.Id()) proxyConn.AddLogPrefix(t.Id()) // tell the client we're going to start using this proxy connection startPxyMsg := &msg.StartProxy{ Url: t.url, ClientAddr: publicConn.RemoteAddr().String(), } if err = msg.WriteMsg(proxyConn, startPxyMsg); err != nil { proxyConn.Warn("Failed to write StartProxyMessage: %v, attempt %d", err, i) proxyConn.Close() } else { // success break } } if err != nil { // give up publicConn.Error("Too many failures starting proxy connection") return } // To reduce latency handling tunnel connections, we employ the following curde heuristic: // Whenever we take a proxy connection from the pool, replace it with a new one util.PanicToError(func() { t.ctl.out <- &msg.ReqProxy{} }) // no timeouts while connections are joined proxyConn.SetDeadline(time.Time{}) // join the public and proxy connections bytesIn, bytesOut := conn.Join(publicConn, proxyConn) metrics.CloseConnection(t, publicConn, startTime, bytesIn, bytesOut) } ngrok-1.6+dfsg.orig/src/ngrok/server/tls.go0000644000000000000000000000140612232501726017423 0ustar rootrootpackage server import ( "crypto/tls" "io/ioutil" "ngrok/server/assets" ) func LoadTLSConfig(crtPath string, keyPath string) (tlsConfig *tls.Config, err error) { fileOrAsset := func(path string, default_path string) ([]byte, error) { loadFn := ioutil.ReadFile if path == "" { loadFn = assets.ReadAsset path = default_path } return loadFn(path) } var ( crt []byte key []byte cert tls.Certificate ) if crt, err = fileOrAsset(crtPath, "assets/server/tls/snakeoil.crt"); err != nil { return } if key, err = fileOrAsset(keyPath, "assets/server/tls/snakeoil.key"); err != nil { return } if cert, err = tls.X509KeyPair(crt, key); err != nil { return } tlsConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, } return } ngrok-1.6+dfsg.orig/src/ngrok/conn/0000755000000000000000000000000012232501726015720 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/conn/conn.go0000644000000000000000000001216312232501726017207 0ustar rootrootpackage conn import ( "bufio" "bytes" "crypto/tls" "encoding/base64" "fmt" "io" "math/rand" "net" "net/http" "net/url" "ngrok/log" "sync" ) type Conn interface { net.Conn log.Logger Id() string SetType(string) CloseRead() error } type loggedConn struct { tcp *net.TCPConn net.Conn log.Logger id int32 typ string } type Listener struct { net.Addr Conns chan Conn } func wrapConn(conn net.Conn, typ string) *loggedConn { switch c := conn.(type) { case *loggedConn: return c case *net.TCPConn: wrapped := &loggedConn{c, conn, log.NewPrefixLogger(), rand.Int31(), typ} wrapped.AddLogPrefix(wrapped.Id()) return wrapped } return nil } func Listen(addr, typ string, tlsCfg *tls.Config) (l *Listener, err error) { // listen for incoming connections listener, err := net.Listen("tcp", addr) if err != nil { return } l = &Listener{ Addr: listener.Addr(), Conns: make(chan Conn), } go func() { for { rawConn, err := listener.Accept() if err != nil { log.Error("Failed to accept new TCP connection of type %s: %v", typ, err) continue } c := wrapConn(rawConn, typ) if tlsCfg != nil { c.Conn = tls.Server(c.Conn, tlsCfg) } c.Info("New connection from %v", c.RemoteAddr()) l.Conns <- c } }() return } func Wrap(conn net.Conn, typ string) *loggedConn { return wrapConn(conn, typ) } func Dial(addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err error) { var rawConn net.Conn if rawConn, err = net.Dial("tcp", addr); err != nil { return } conn = wrapConn(rawConn, typ) conn.Debug("New connection to: %v", rawConn.RemoteAddr()) if tlsCfg != nil { conn.StartTLS(tlsCfg) } return } func DialHttpProxy(proxyUrl, addr, typ string, tlsCfg *tls.Config) (conn *loggedConn, err error) { // parse the proxy address var parsedUrl *url.URL if parsedUrl, err = url.Parse(proxyUrl); err != nil { return } var proxyAuth string if parsedUrl.User != nil { proxyAuth = "Basic " + base64.StdEncoding.EncodeToString([]byte(parsedUrl.User.String())) } var proxyTlsConfig *tls.Config switch parsedUrl.Scheme { case "http": proxyTlsConfig = nil case "https": proxyTlsConfig = new(tls.Config) default: err = fmt.Errorf("Proxy URL scheme must be http or https, got: %s", parsedUrl.Scheme) return } // dial the proxy if conn, err = Dial(parsedUrl.Host, typ, proxyTlsConfig); err != nil { return } // send an HTTP proxy CONNECT message req, err := http.NewRequest("CONNECT", "https://"+addr, nil) if err != nil { return } if proxyAuth != "" { req.Header.Set("Proxy-Authorization", proxyAuth) } req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; ngrok)") req.Write(conn) // read the proxy's response resp, err := http.ReadResponse(bufio.NewReader(conn), req) if err != nil { return } resp.Body.Close() if resp.StatusCode != 200 { err = fmt.Errorf("Non-200 response from proxy server: %s", resp.Status) return } // upgrade to TLS conn.StartTLS(tlsCfg) return } func (c *loggedConn) StartTLS(tlsCfg *tls.Config) { c.Conn = tls.Client(c.Conn, tlsCfg) } func (c *loggedConn) Close() (err error) { if err := c.Conn.Close(); err == nil { c.Debug("Closing") } return } func (c *loggedConn) Id() string { return fmt.Sprintf("%s:%x", c.typ, c.id) } func (c *loggedConn) SetType(typ string) { oldId := c.Id() c.typ = typ c.ClearLogPrefixes() c.AddLogPrefix(c.Id()) c.Info("Renamed connection %s", oldId) } func (c *loggedConn) CloseRead() error { // XXX: use CloseRead() in Conn.Join() and in Control.shutdown() for cleaner // connection termination. Unfortunately, when I've tried that, I've observed // failures where the connection was closed *before* flushing its write buffer, // set with SetLinger() set properly (which it is by default). return c.tcp.CloseRead() } func Join(c Conn, c2 Conn) (int64, int64) { var wait sync.WaitGroup pipe := func(to Conn, from Conn, bytesCopied *int64) { defer to.Close() defer from.Close() defer wait.Done() var err error *bytesCopied, err = io.Copy(to, from) if err != nil { from.Warn("Copied %d bytes to %s before failing with error %v", *bytesCopied, to.Id(), err) } else { from.Debug("Copied %d bytes to %s", *bytesCopied, to.Id()) } } wait.Add(2) var fromBytes, toBytes int64 go pipe(c, c2, &fromBytes) go pipe(c2, c, &toBytes) c.Info("Joined with connection %s", c2.Id()) wait.Wait() return fromBytes, toBytes } type httpConn struct { *loggedConn reqBuf *bytes.Buffer } func NewHttp(conn net.Conn, typ string) *httpConn { return &httpConn{ wrapConn(conn, typ), bytes.NewBuffer(make([]byte, 0, 1024)), } } func (c *httpConn) ReadRequest() (*http.Request, error) { r := io.TeeReader(c.loggedConn, c.reqBuf) return http.ReadRequest(bufio.NewReader(r)) } func (c *loggedConn) ReadFrom(r io.Reader) (n int64, err error) { // special case when we're reading from an http request where // we had to parse the request and consume bytes from the socket // and store them in a temporary request buffer if httpConn, ok := r.(*httpConn); ok { if n, err = httpConn.reqBuf.WriteTo(c); err != nil { return } } nCopied, err := io.Copy(c.Conn, r) n += nCopied return } ngrok-1.6+dfsg.orig/src/ngrok/conn/tee.go0000644000000000000000000000341312232501726017025 0ustar rootrootpackage conn import ( "bufio" "io" ) // conn.Tee is a wraps a conn.Conn // causing all writes/reads to be tee'd just // like the unix command such that all data that // is read and written to the connection through its // interfaces will also be copied into two dedicated pipes // used for consuming a copy of the data stream // // this is useful for introspecting the traffic flowing // over a connection without having to tamper with the actual // code that reads and writes over the connection // // NB: the data is Tee'd into a shared-memory io.Pipe which // has a limited (and small) buffer. If you are not consuming from // the ReadBuffer() and WriteBuffer(), you are going to block // your application's real traffic from flowing over the connection type Tee struct { rd io.Reader wr io.Writer readPipe struct { rd *io.PipeReader wr *io.PipeWriter } writePipe struct { rd *io.PipeReader wr *io.PipeWriter } Conn } func NewTee(conn Conn) *Tee { c := &Tee{ rd: nil, wr: nil, Conn: conn, } c.readPipe.rd, c.readPipe.wr = io.Pipe() c.writePipe.rd, c.writePipe.wr = io.Pipe() c.rd = io.TeeReader(c.Conn, c.readPipe.wr) c.wr = io.MultiWriter(c.Conn, c.writePipe.wr) return c } func (c *Tee) ReadBuffer() *bufio.Reader { return bufio.NewReader(c.readPipe.rd) } func (c *Tee) WriteBuffer() *bufio.Reader { return bufio.NewReader(c.writePipe.rd) } func (c *Tee) Read(b []byte) (n int, err error) { n, err = c.rd.Read(b) if err != nil { c.readPipe.wr.Close() } return } func (c *Tee) ReadFrom(r io.Reader) (n int64, err error) { n, err = io.Copy(c.wr, r) if err != nil { c.writePipe.wr.Close() } return } func (c *Tee) Write(b []byte) (n int, err error) { n, err = c.wr.Write(b) if err != nil { c.writePipe.wr.Close() } return } ngrok-1.6+dfsg.orig/src/ngrok/cache/0000755000000000000000000000000012232501726016026 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/cache/lru.go0000644000000000000000000001256712232501726017172 0ustar rootroot// Copyright 2012, Google Inc. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // The implementation borrows heavily from SmallLRUCache (originally by Nathan // Schrenk). The object maintains a doubly-linked list of elements in the // When an element is accessed it is promoted to the head of the list, and when // space is needed the element at the tail of the list (the least recently used // element) is evicted. package cache import ( "container/list" "encoding/gob" "fmt" "io" "os" "sync" "time" ) type LRUCache struct { mu sync.Mutex // list & table of *entry objects list *list.List table map[string]*list.Element // Our current size, in bytes. Obviously a gross simplification and low-grade // approximation. size uint64 // How many bytes we are limiting the cache to. capacity uint64 } // Values that go into LRUCache need to satisfy this interface. type Value interface { Size() int } type Item struct { Key string Value Value } type entry struct { key string value Value size int time_accessed time.Time } func NewLRUCache(capacity uint64) *LRUCache { return &LRUCache{ list: list.New(), table: make(map[string]*list.Element), capacity: capacity, } } func (lru *LRUCache) Get(key string) (v Value, ok bool) { lru.mu.Lock() defer lru.mu.Unlock() element := lru.table[key] if element == nil { return nil, false } lru.moveToFront(element) return element.Value.(*entry).value, true } func (lru *LRUCache) Set(key string, value Value) { lru.mu.Lock() defer lru.mu.Unlock() if element := lru.table[key]; element != nil { lru.updateInplace(element, value) } else { lru.addNew(key, value) } } func (lru *LRUCache) SetIfAbsent(key string, value Value) { lru.mu.Lock() defer lru.mu.Unlock() if element := lru.table[key]; element != nil { lru.moveToFront(element) } else { lru.addNew(key, value) } } func (lru *LRUCache) Delete(key string) bool { lru.mu.Lock() defer lru.mu.Unlock() element := lru.table[key] if element == nil { return false } lru.list.Remove(element) delete(lru.table, key) lru.size -= uint64(element.Value.(*entry).size) return true } func (lru *LRUCache) Clear() { lru.mu.Lock() defer lru.mu.Unlock() lru.list.Init() lru.table = make(map[string]*list.Element) lru.size = 0 } func (lru *LRUCache) SetCapacity(capacity uint64) { lru.mu.Lock() defer lru.mu.Unlock() lru.capacity = capacity lru.checkCapacity() } func (lru *LRUCache) Stats() (length, size, capacity uint64, oldest time.Time) { lru.mu.Lock() defer lru.mu.Unlock() if lastElem := lru.list.Back(); lastElem != nil { oldest = lastElem.Value.(*entry).time_accessed } return uint64(lru.list.Len()), lru.size, lru.capacity, oldest } func (lru *LRUCache) StatsJSON() string { if lru == nil { return "{}" } l, s, c, o := lru.Stats() return fmt.Sprintf("{\"Length\": %v, \"Size\": %v, \"Capacity\": %v, \"OldestAccess\": \"%v\"}", l, s, c, o) } func (lru *LRUCache) Keys() []string { lru.mu.Lock() defer lru.mu.Unlock() keys := make([]string, 0, lru.list.Len()) for e := lru.list.Front(); e != nil; e = e.Next() { keys = append(keys, e.Value.(*entry).key) } return keys } func (lru *LRUCache) Items() []Item { lru.mu.Lock() defer lru.mu.Unlock() items := make([]Item, 0, lru.list.Len()) for e := lru.list.Front(); e != nil; e = e.Next() { v := e.Value.(*entry) items = append(items, Item{Key: v.key, Value: v.value}) } return items } func (lru *LRUCache) SaveItems(w io.Writer) error { items := lru.Items() encoder := gob.NewEncoder(w) return encoder.Encode(items) } func (lru *LRUCache) SaveItemsToFile(path string) error { if wr, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644); err != nil { return err } else { defer wr.Close() return lru.SaveItems(wr) } } func (lru *LRUCache) LoadItems(r io.Reader) error { items := make([]Item, 0) decoder := gob.NewDecoder(r) if err := decoder.Decode(&items); err != nil { return err } lru.mu.Lock() lru.mu.Unlock() for _, item := range items { // XXX: copied from Set() if element := lru.table[item.Key]; element != nil { lru.updateInplace(element, item.Value) } else { lru.addNew(item.Key, item.Value) } } return nil } func (lru *LRUCache) LoadItemsFromFile(path string) error { if rd, err := os.Open(path); err != nil { return err } else { defer rd.Close() return lru.LoadItems(rd) } } func (lru *LRUCache) updateInplace(element *list.Element, value Value) { valueSize := value.Size() sizeDiff := valueSize - element.Value.(*entry).size element.Value.(*entry).value = value element.Value.(*entry).size = valueSize lru.size += uint64(sizeDiff) lru.moveToFront(element) lru.checkCapacity() } func (lru *LRUCache) moveToFront(element *list.Element) { lru.list.MoveToFront(element) element.Value.(*entry).time_accessed = time.Now() } func (lru *LRUCache) addNew(key string, value Value) { newEntry := &entry{key, value, value.Size(), time.Now()} element := lru.list.PushFront(newEntry) lru.table[key] = element lru.size += uint64(newEntry.size) lru.checkCapacity() } func (lru *LRUCache) checkCapacity() { // Partially duplicated from Delete for lru.size > lru.capacity { delElem := lru.list.Back() delValue := delElem.Value.(*entry) lru.list.Remove(delElem) delete(lru.table, delValue.key) lru.size -= uint64(delValue.size) } } ngrok-1.6+dfsg.orig/src/ngrok/client/0000755000000000000000000000000012232501726016241 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/client/cli.go0000644000000000000000000000514712232501726017346 0ustar rootrootpackage client import ( "flag" "fmt" "ngrok/version" "os" ) const usage1 string = `Usage: %s [OPTIONS] Options: ` const usage2 string = ` Examples: ngrok 80 ngrok -subdomain=example 8080 ngrok -proto=tcp 22 ngrok -hostname="example.com" -httpauth="user:password" 10.0.0.1 Advanced usage: ngrok [OPTIONS] [command args] [...] Commands: ngrok start [tunnel] [...] Start tunnels by name from config file ngrok help Print help ngrok version Print ngrok version Examples: ngrok start www api blog pubsub ngrok -log=stdout -config=ngrok.yml start ssh ngrok version ` type Options struct { config string logto string authtoken string httpauth string hostname string protocol string subdomain string command string args []string } func parseArgs() (opts *Options, err error) { flag.Usage = func() { fmt.Fprintf(os.Stderr, usage1, os.Args[0]) flag.PrintDefaults() fmt.Fprintf(os.Stderr, usage2) } config := flag.String( "config", "", "Path to ngrok configuration file. (default: $HOME/.ngrok)") logto := flag.String( "log", "none", "Write log messages to this file. 'stdout' and 'none' have special meanings") authtoken := flag.String( "authtoken", "", "Authentication token for identifying an ngrok.com account") httpauth := flag.String( "httpauth", "", "username:password HTTP basic auth creds protecting the public tunnel endpoint") subdomain := flag.String( "subdomain", "", "Request a custom subdomain from the ngrok server. (HTTP only)") hostname := flag.String( "hostname", "", "Request a custom hostname from the ngrok server. (HTTP only) (requires CNAME of your DNS)") protocol := flag.String( "proto", "http+https", "The protocol of the traffic over the tunnel {'http', 'https', 'tcp'} (default: 'http+https')") flag.Parse() opts = &Options{ config: *config, logto: *logto, httpauth: *httpauth, subdomain: *subdomain, protocol: *protocol, authtoken: *authtoken, hostname: *hostname, command: flag.Arg(0), } switch opts.command { case "start": opts.args = flag.Args()[1:] case "version": fmt.Println(version.MajorMinor()) os.Exit(0) case "help": flag.Usage() os.Exit(0) case "": err = fmt.Errorf("You must specify a local port to tunnel or an ngrok command.") return default: if len(flag.Args()) > 1 { err = fmt.Errorf("You may only specify one port to tunnel to on the command line, got %d: %v", len(flag.Args()), flag.Args()) return } opts.command = "default" opts.args = flag.Args() } return } ngrok-1.6+dfsg.orig/src/ngrok/client/metrics.go0000644000000000000000000000165612232501726020246 0ustar rootrootpackage client import ( metrics "github.com/inconshreveable/go-metrics" ) const ( sampleSize int = 1028 sampleAlpha float64 = 0.015 ) type ClientMetrics struct { // metrics connGauge metrics.Gauge connMeter metrics.Meter connTimer metrics.Timer proxySetupTimer metrics.Timer bytesIn metrics.Histogram bytesOut metrics.Histogram bytesInCount metrics.Counter bytesOutCount metrics.Counter } func NewClientMetrics() *ClientMetrics { return &ClientMetrics{ connGauge: metrics.NewGauge(), connMeter: metrics.NewMeter(), connTimer: metrics.NewTimer(), proxySetupTimer: metrics.NewTimer(), bytesIn: metrics.NewHistogram(metrics.NewExpDecaySample(sampleSize, sampleAlpha)), bytesOut: metrics.NewHistogram(metrics.NewExpDecaySample(sampleSize, sampleAlpha)), bytesInCount: metrics.NewCounter(), bytesOutCount: metrics.NewCounter(), } } ngrok-1.6+dfsg.orig/src/ngrok/client/model.go0000644000000000000000000002521612232501726017676 0ustar rootrootpackage client import ( "crypto/tls" "fmt" metrics "github.com/inconshreveable/go-metrics" "io/ioutil" "math" "ngrok/client/mvc" "ngrok/conn" "ngrok/log" "ngrok/msg" "ngrok/proto" "ngrok/util" "ngrok/version" "runtime" "strings" "sync/atomic" "time" ) const ( defaultServerAddr = "ngrokd.ngrok.com:443" pingInterval = 20 * time.Second maxPongLatency = 15 * time.Second updateCheckInterval = 6 * time.Hour BadGateway = `

Tunnel %s unavailable

Unable to initiate connection to %s. A web server must be running on port %s to complete the tunnel.

` ) type ClientModel struct { log.Logger id string tunnels map[string]mvc.Tunnel serverVersion string metrics *ClientMetrics updateStatus mvc.UpdateStatus connStatus mvc.ConnStatus protoMap map[string]proto.Protocol protocols []proto.Protocol ctl mvc.Controller serverAddr string proxyUrl string authToken string tlsConfig *tls.Config tunnelConfig map[string]*TunnelConfiguration configPath string } func newClientModel(config *Configuration, ctl mvc.Controller) *ClientModel { protoMap := make(map[string]proto.Protocol) protoMap["http"] = proto.NewHttp() protoMap["https"] = protoMap["http"] protoMap["tcp"] = proto.NewTcp() protocols := []proto.Protocol{protoMap["http"], protoMap["tcp"]} // configure TLS var tlsConfig *tls.Config if config.TrustHostRootCerts { tlsConfig = &tls.Config{} } else { var err error if tlsConfig, err = LoadTLSConfig(rootCrtPaths); err != nil { panic(err) } } return &ClientModel{ Logger: log.NewPrefixLogger("client"), // server address serverAddr: config.ServerAddr, // proxy address proxyUrl: config.HttpProxy, // auth token authToken: config.AuthToken, // connection status connStatus: mvc.ConnConnecting, // update status updateStatus: mvc.UpdateNone, // metrics metrics: NewClientMetrics(), // protocols protoMap: protoMap, // protocol list protocols: protocols, // open tunnels tunnels: make(map[string]mvc.Tunnel), // controller ctl: ctl, // tls configuration tlsConfig: tlsConfig, // tunnel configuration tunnelConfig: config.Tunnels, // config path configPath: config.Path, } } // mvc.State interface func (c ClientModel) GetProtocols() []proto.Protocol { return c.protocols } func (c ClientModel) GetClientVersion() string { return version.MajorMinor() } func (c ClientModel) GetServerVersion() string { return c.serverVersion } func (c ClientModel) GetTunnels() []mvc.Tunnel { tunnels := make([]mvc.Tunnel, 0) for _, t := range c.tunnels { tunnels = append(tunnels, t) } return tunnels } func (c ClientModel) GetConnStatus() mvc.ConnStatus { return c.connStatus } func (c ClientModel) GetUpdateStatus() mvc.UpdateStatus { return c.updateStatus } func (c ClientModel) GetConnectionMetrics() (metrics.Meter, metrics.Timer) { return c.metrics.connMeter, c.metrics.connTimer } func (c ClientModel) GetBytesInMetrics() (metrics.Counter, metrics.Histogram) { return c.metrics.bytesInCount, c.metrics.bytesIn } func (c ClientModel) GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) { return c.metrics.bytesOutCount, c.metrics.bytesOut } func (c ClientModel) SetUpdateStatus(updateStatus mvc.UpdateStatus) { c.updateStatus = updateStatus c.update() } // mvc.Model interface func (c *ClientModel) PlayRequest(tunnel mvc.Tunnel, payload []byte) { var localConn conn.Conn localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) if err != nil { c.Warn("Failed to open private leg to %s: %v", tunnel.LocalAddr, err) return } defer localConn.Close() localConn = tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: "127.0.0.1"}) localConn.Write(payload) ioutil.ReadAll(localConn) } func (c *ClientModel) Shutdown() { } func (c *ClientModel) update() { c.ctl.Update(c) } func (c *ClientModel) Run() { // how long we should wait before we reconnect maxWait := 30 * time.Second wait := 1 * time.Second for { // run the control channel c.control() // control oonly returns when a failure has occurred, so we're going to try to reconnect if c.connStatus == mvc.ConnOnline { wait = 1 * time.Second } log.Info("Waiting %d seconds before reconnecting", int(wait.Seconds())) time.Sleep(wait) // exponentially increase wait time wait = 2 * wait wait = time.Duration(math.Min(float64(wait), float64(maxWait))) c.connStatus = mvc.ConnReconnecting c.update() } } // Establishes and manages a tunnel control connection with the server func (c *ClientModel) control() { defer func() { if r := recover(); r != nil { log.Error("control recovering from failure %v", r) } }() // establish control channel var ( ctlConn conn.Conn err error ) if c.proxyUrl == "" { // simple non-proxied case, just connect to the server ctlConn, err = conn.Dial(c.serverAddr, "ctl", c.tlsConfig) } else { ctlConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "ctl", c.tlsConfig) } if err != nil { panic(err) } defer ctlConn.Close() // authenticate with the server auth := &msg.Auth{ ClientId: c.id, OS: runtime.GOOS, Arch: runtime.GOARCH, Version: version.Proto, MmVersion: version.MajorMinor(), User: c.authToken, } if err = msg.WriteMsg(ctlConn, auth); err != nil { panic(err) } // wait for the server to authenticate us var authResp msg.AuthResp if err = msg.ReadMsgInto(ctlConn, &authResp); err != nil { panic(err) } if authResp.Error != "" { emsg := fmt.Sprintf("Failed to authenticate to server: %s", authResp.Error) c.ctl.Shutdown(emsg) return } c.id = authResp.ClientId c.serverVersion = authResp.MmVersion c.Info("Authenticated with server, client id: %v", c.id) c.update() if err = SaveAuthToken(c.configPath, c.authToken); err != nil { c.Error("Failed to save auth token: %v", err) } // request tunnels reqIdToTunnelConfig := make(map[string]*TunnelConfiguration) for _, config := range c.tunnelConfig { // create the protocol list to ask for var protocols []string for proto, _ := range config.Protocols { protocols = append(protocols, proto) } reqTunnel := &msg.ReqTunnel{ ReqId: util.RandId(8), Protocol: strings.Join(protocols, "+"), Hostname: config.Hostname, Subdomain: config.Subdomain, HttpAuth: config.HttpAuth, RemotePort: config.RemotePort, } // send the tunnel request if err = msg.WriteMsg(ctlConn, reqTunnel); err != nil { panic(err) } // save request id association so we know which local address // to proxy to later reqIdToTunnelConfig[reqTunnel.ReqId] = config } // start the heartbeat lastPong := time.Now().UnixNano() c.ctl.Go(func() { c.heartbeat(&lastPong, ctlConn) }) // main control loop for { var rawMsg msg.Message if rawMsg, err = msg.ReadMsg(ctlConn); err != nil { panic(err) } switch m := rawMsg.(type) { case *msg.ReqProxy: c.ctl.Go(c.proxy) case *msg.Pong: atomic.StoreInt64(&lastPong, time.Now().UnixNano()) case *msg.NewTunnel: if m.Error != "" { emsg := fmt.Sprintf("Server failed to allocate tunnel: %s", m.Error) c.Error(emsg) c.ctl.Shutdown(emsg) continue } tunnel := mvc.Tunnel{ PublicUrl: m.Url, LocalAddr: reqIdToTunnelConfig[m.ReqId].Protocols[m.Protocol], Protocol: c.protoMap[m.Protocol], } c.tunnels[tunnel.PublicUrl] = tunnel c.connStatus = mvc.ConnOnline c.Info("Tunnel established at %v", tunnel.PublicUrl) c.update() default: ctlConn.Warn("Ignoring unknown control message %v ", m) } } } // Establishes and manages a tunnel proxy connection with the server func (c *ClientModel) proxy() { var ( remoteConn conn.Conn err error ) if c.proxyUrl == "" { remoteConn, err = conn.Dial(c.serverAddr, "pxy", c.tlsConfig) } else { remoteConn, err = conn.DialHttpProxy(c.proxyUrl, c.serverAddr, "pxy", c.tlsConfig) } if err != nil { log.Error("Failed to establish proxy connection: %v", err) return } defer remoteConn.Close() err = msg.WriteMsg(remoteConn, &msg.RegProxy{ClientId: c.id}) if err != nil { remoteConn.Error("Failed to write RegProxy: %v", err) return } // wait for the server to ack our register var startPxy msg.StartProxy if err = msg.ReadMsgInto(remoteConn, &startPxy); err != nil { remoteConn.Error("Server failed to write StartProxy: %v", err) return } tunnel, ok := c.tunnels[startPxy.Url] if !ok { remoteConn.Error("Couldn't find tunnel for proxy: %s", startPxy.Url) return } // start up the private connection start := time.Now() localConn, err := conn.Dial(tunnel.LocalAddr, "prv", nil) if err != nil { remoteConn.Warn("Failed to open private leg %s: %v", tunnel.LocalAddr, err) if tunnel.Protocol.GetName() == "http" { // try to be helpful when you're in HTTP mode and a human might see the output badGatewayBody := fmt.Sprintf(BadGateway, tunnel.PublicUrl, tunnel.LocalAddr, tunnel.LocalAddr) remoteConn.Write([]byte(fmt.Sprintf(`HTTP/1.0 502 Bad Gateway Content-Type: text/html Content-Length: %d %s`, len(badGatewayBody), badGatewayBody))) } return } defer localConn.Close() m := c.metrics m.proxySetupTimer.Update(time.Since(start)) m.connMeter.Mark(1) c.update() m.connTimer.Time(func() { localConn := tunnel.Protocol.WrapConn(localConn, mvc.ConnectionContext{Tunnel: tunnel, ClientAddr: startPxy.ClientAddr}) bytesIn, bytesOut := conn.Join(localConn, remoteConn) m.bytesIn.Update(bytesIn) m.bytesOut.Update(bytesOut) m.bytesInCount.Inc(bytesIn) m.bytesOutCount.Inc(bytesOut) }) c.update() } // Hearbeating to ensure our connection ngrokd is still live func (c *ClientModel) heartbeat(lastPongAddr *int64, conn conn.Conn) { lastPing := time.Unix(atomic.LoadInt64(lastPongAddr)-1, 0) ping := time.NewTicker(pingInterval) pongCheck := time.NewTicker(time.Second) defer func() { conn.Close() ping.Stop() pongCheck.Stop() }() for { select { case <-pongCheck.C: lastPong := time.Unix(0, atomic.LoadInt64(lastPongAddr)) needPong := lastPong.Sub(lastPing) < 0 pongLatency := time.Since(lastPing) if needPong && pongLatency > maxPongLatency { c.Info("Last ping: %v, Last pong: %v", lastPing, lastPong) c.Info("Connection stale, haven't gotten PongMsg in %d seconds", int(pongLatency.Seconds())) return } case <-ping.C: err := msg.WriteMsg(conn, &msg.Ping{}) if err != nil { conn.Debug("Got error %v when writing PingMsg", err) return } lastPing = time.Now() } } } ngrok-1.6+dfsg.orig/src/ngrok/client/config.go0000644000000000000000000001554012232501726020042 0ustar rootrootpackage client import ( "fmt" "io/ioutil" "launchpad.net/goyaml" "net" "net/url" "ngrok/log" "os" "os/user" "path" "regexp" "strconv" "strings" ) type Configuration struct { HttpProxy string `yaml:"http_proxy,omitempty"` ServerAddr string `yaml:"server_addr,omitempty"` InspectAddr string `yaml:"inspect_addr,omitempty"` TrustHostRootCerts bool `yaml:"trust_host_root_certs,omitempty"` AuthToken string `yaml:"auth_token,omitempty"` Tunnels map[string]*TunnelConfiguration `yaml:"tunnels,omitempty"` LogTo string `yaml:"-"` Path string `yaml:"-"` } type TunnelConfiguration struct { Subdomain string `yaml:"subdomain,omitempty"` Hostname string `yaml:"hostname,omitempty"` Protocols map[string]string `yaml:"proto,omitempty"` HttpAuth string `yaml:"auth,omitempty"` RemotePort uint16 `yaml:"remote_port,omitempty"` } func LoadConfiguration(opts *Options) (config *Configuration, err error) { configPath := opts.config if configPath == "" { configPath = defaultPath() } log.Info("Reading configuration file %s", configPath) configBuf, err := ioutil.ReadFile(configPath) if err != nil { // failure to read a configuration file is only a fatal error if // the user specified one explicitly if opts.config != "" { err = fmt.Errorf("Failed to read configuration file %s: %v", configPath, err) return } } // deserialize/parse the config config = new(Configuration) if err = goyaml.Unmarshal(configBuf, &config); err != nil { err = fmt.Errorf("Error parsing configuration file %s: %v", configPath, err) return } // try to parse the old .ngrok format for backwards compatibility matched := false content := strings.TrimSpace(string(configBuf)) if matched, err = regexp.MatchString("^[0-9a-zA-Z_\\-!]+$", content); err != nil { return } else if matched { config = &Configuration{AuthToken: content} } // set configuration defaults if config.ServerAddr == "" { config.ServerAddr = defaultServerAddr } if config.InspectAddr == "" { config.InspectAddr = "127.0.0.1:4040" } if config.HttpProxy == "" { config.HttpProxy = os.Getenv("http_proxy") } // validate and normalize configuration if config.InspectAddr != "disabled" { if config.InspectAddr, err = normalizeAddress(config.InspectAddr, "inspect_addr"); err != nil { return } } if config.ServerAddr, err = normalizeAddress(config.ServerAddr, "server_addr"); err != nil { return } if config.HttpProxy != "" { var proxyUrl *url.URL if proxyUrl, err = url.Parse(config.HttpProxy); err != nil { return } else { if proxyUrl.Scheme != "http" && proxyUrl.Scheme != "https" { err = fmt.Errorf("Proxy url scheme must be 'http' or 'https', got %v", proxyUrl.Scheme) return } } } for name, t := range config.Tunnels { if t == nil || t.Protocols == nil || len(t.Protocols) == 0 { err = fmt.Errorf("Tunnel %s does not specify any protocols to tunnel.", name) return } for k, addr := range t.Protocols { tunnelName := fmt.Sprintf("for tunnel %s[%s]", name, k) if t.Protocols[k], err = normalizeAddress(addr, tunnelName); err != nil { return } if err = validateProtocol(k, tunnelName); err != nil { return } } // use the name of the tunnel as the subdomain if none is specified if t.Hostname == "" && t.Subdomain == "" { // XXX: a crude heuristic, really we should be checking if the last part // is a TLD if len(strings.Split(name, ".")) > 1 { t.Hostname = name } else { t.Subdomain = name } } } // override configuration with command-line options config.LogTo = opts.logto config.Path = configPath if opts.authtoken != "" { config.AuthToken = opts.authtoken } switch opts.command { // start a single tunnel, the default, simple ngrok behavior case "default": config.Tunnels = make(map[string]*TunnelConfiguration) config.Tunnels["default"] = &TunnelConfiguration{ Subdomain: opts.subdomain, Hostname: opts.hostname, HttpAuth: opts.httpauth, Protocols: make(map[string]string), } for _, proto := range strings.Split(opts.protocol, "+") { if err = validateProtocol(proto, "default"); err != nil { return } if config.Tunnels["default"].Protocols[proto], err = normalizeAddress(opts.args[0], ""); err != nil { return } } // start tunnels case "start": if len(opts.args) == 0 { err = fmt.Errorf("You must specify at least one tunnel to start") return } requestedTunnels := make(map[string]bool) for _, arg := range opts.args { requestedTunnels[arg] = true if _, ok := config.Tunnels[arg]; !ok { err = fmt.Errorf("Requested to start tunnel %s which is not defined in the config file.", arg) return } } for name, _ := range config.Tunnels { if !requestedTunnels[name] { delete(config.Tunnels, name) } } default: err = fmt.Errorf("Unknown command: %s", opts.command) return } return } func defaultPath() string { user, err := user.Current() // user.Current() does not work on linux when cross compilling because // it requires CGO; use os.Getenv("HOME") hack until we compile natively homeDir := os.Getenv("HOME") if err != nil { log.Warn("Failed to get user's home directory: %s. Using $HOME: %s", err.Error(), homeDir) } else { homeDir = user.HomeDir } return path.Join(homeDir, ".ngrok") } func normalizeAddress(addr string, propName string) (string, error) { // normalize port to address if _, err := strconv.Atoi(addr); err == nil { addr = ":" + addr } tcpAddr, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return "", fmt.Errorf("Invalid address %s '%s': %s", propName, addr, err.Error()) } if tcpAddr.IP == nil { tcpAddr.IP = net.ParseIP("127.0.0.1") } return tcpAddr.String(), nil } func validateProtocol(proto, propName string) (err error) { switch proto { case "http", "https", "http+https", "tcp": default: err = fmt.Errorf("Invalid protocol for %s: %s", propName, proto) } return } func SaveAuthToken(configPath, authtoken string) (err error) { // empty configuration by default for the case that we can't read it c := new(Configuration) // read the configuration oldConfigBytes, err := ioutil.ReadFile(configPath) if err == nil { // unmarshal if we successfully read the configuration file if err = goyaml.Unmarshal(oldConfigBytes, c); err != nil { return } } // no need to save, the authtoken is already the correct value if c.AuthToken == authtoken { return } // update auth token c.AuthToken = authtoken // rewrite configuration newConfigBytes, err := goyaml.Marshal(c) if err != nil { return } err = ioutil.WriteFile(configPath, newConfigBytes, 0600) return } ngrok-1.6+dfsg.orig/src/ngrok/client/assets/0000755000000000000000000000000012232501726017543 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/client/assets/assets_debug.go0000644000000000000000000000021312232501726022536 0ustar rootroot// +build !release package assets import ( "io/ioutil" ) func ReadAsset(name string) ([]byte, error) { return ioutil.ReadFile(name) } ngrok-1.6+dfsg.orig/src/ngrok/client/main.go0000644000000000000000000000107312232501726017515 0ustar rootrootpackage client import ( "fmt" "math/rand" "ngrok/log" "ngrok/util" "os" ) func Main() { // parse options opts, err := parseArgs() if err != nil { fmt.Println(err) os.Exit(1) } // set up logging log.LogTo(opts.logto) // read configuration file config, err := LoadConfiguration(opts) if err != nil { fmt.Println(err) os.Exit(1) } // seed random number generator seed, err := util.RandomSeed() if err != nil { fmt.Printf("Couldn't securely seed the random number generator!") os.Exit(1) } rand.Seed(seed) NewController().Run(config) } ngrok-1.6+dfsg.orig/src/ngrok/client/mvc/0000755000000000000000000000000012232501726017026 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/client/mvc/model.go0000644000000000000000000000014712232501726020457 0ustar rootrootpackage mvc type Model interface { Run() Shutdown() PlayRequest(tunnel Tunnel, payload []byte) } ngrok-1.6+dfsg.orig/src/ngrok/client/mvc/state.go0000644000000000000000000000150512232501726020476 0ustar rootrootpackage mvc import ( metrics "github.com/inconshreveable/go-metrics" "ngrok/proto" ) type UpdateStatus int const ( UpdateNone = -1 * iota UpdateInstalling UpdateReady UpdateAvailable ) type ConnStatus int const ( ConnConnecting = iota ConnReconnecting ConnOnline ) type Tunnel struct { PublicUrl string Protocol proto.Protocol LocalAddr string } type ConnectionContext struct { Tunnel Tunnel ClientAddr string } type State interface { GetClientVersion() string GetServerVersion() string GetTunnels() []Tunnel GetProtocols() []proto.Protocol GetUpdateStatus() UpdateStatus GetConnStatus() ConnStatus GetConnectionMetrics() (metrics.Meter, metrics.Timer) GetBytesInMetrics() (metrics.Counter, metrics.Histogram) GetBytesOutMetrics() (metrics.Counter, metrics.Histogram) SetUpdateStatus(UpdateStatus) } ngrok-1.6+dfsg.orig/src/ngrok/client/mvc/view.go0000644000000000000000000000006112232501726020324 0ustar rootrootpackage mvc type View interface { Shutdown() } ngrok-1.6+dfsg.orig/src/ngrok/client/mvc/controller.go0000644000000000000000000000106312232501726021540 0ustar rootrootpackage mvc import ( "ngrok/util" ) type Controller interface { // how the model communicates that it has changed state Update(State) // instructs the controller to shut the app down Shutdown(message string) // PlayRequest instructs the model to play requests PlayRequest(tunnel Tunnel, payload []byte) // A channel of updates Updates() *util.Broadcast // returns the current state State() State // safe wrapper for running go-routines Go(fn func()) // the address where the web inspection interface is running GetWebInspectAddr() string } ngrok-1.6+dfsg.orig/src/ngrok/client/update_debug.go0000644000000000000000000000024512232501726021221 0ustar rootroot// +build !release,!autoupdate package client import ( "ngrok/client/mvc" ) // no auto-updating in debug mode func autoUpdate(state mvc.State, token string) { } ngrok-1.6+dfsg.orig/src/ngrok/client/update_release.go0000644000000000000000000000562312232501726021560 0ustar rootroot// +build release autoupdate package client import ( update "github.com/inconshreveable/go-update" "net/http" "net/url" "ngrok/client/mvc" "ngrok/log" "ngrok/version" "runtime" "time" ) const ( updateEndpoint = "https://dl.ngrok.com/update" checkEndpoint = "https://dl.ngrok.com/update/check" ) func progressWatcher(s mvc.State, progress chan int, complete chan int) { for { select { case pct, ok := <-progress: if !ok { close(complete) return } else if pct == 100 { s.SetUpdateStatus(mvc.UpdateInstalling) close(complete) return } else { if pct%25 == 0 { log.Info("Downloading update %d%% complete", pct) } s.SetUpdateStatus(mvc.UpdateStatus(pct)) } } } } func autoUpdate(s mvc.State, token string) { tryAgain := true params := make(url.Values) params.Add("version", version.MajorMinor()) params.Add("os", runtime.GOOS) params.Add("arch", runtime.GOARCH) params.Add("user", token) updateUrl := updateEndpoint + "?" + params.Encode() checkUrl := checkEndpoint + "?" + params.Encode() update := func() { log.Info("Checking for update") available, err := update.NewDownload(checkUrl).Check() if err != nil { log.Error("Error while checking for update: %v", err) return } if !available { log.Info("No update available") return } // stop trying after a single download attempt // XXX: improve this so the we can: // 1. safely update multiple times // 2. only retry after a network connection failure tryAgain = false download := update.NewDownload(updateUrl) downloadComplete := make(chan int) go progressWatcher(s, download.Progress, downloadComplete) log.Info("Trying to update . . .") err, errRecover := download.GetAndUpdate() <-downloadComplete if err != nil { // log error to console log.Error("Error while updating ngrok: %v", err) if errRecover != nil { log.Error("Error while recovering from failed ngrok update, your binary may be missing: %v", errRecover.Error()) params.Add("errorRecover", errRecover.Error()) } // log error to ngrok.com's servers for debugging purposes params.Add("error", err.Error()) resp, reportErr := http.PostForm("https://dl.ngrok.com/update/error", params) if err != nil { log.Error("Error while reporting update error: %v, %v", err, reportErr) } resp.Body.Close() // tell the user to update manually s.SetUpdateStatus(mvc.UpdateAvailable) } else { if !download.Available { // this is the way the server tells us to update manually log.Info("Server wants us to update manually") s.SetUpdateStatus(mvc.UpdateAvailable) } else { // tell the user the update is ready log.Info("Update ready!") s.SetUpdateStatus(mvc.UpdateReady) } } return } // try to update immediately and then at a set interval update() for _ = range time.Tick(updateCheckInterval) { if !tryAgain { break } update() } } ngrok-1.6+dfsg.orig/src/ngrok/client/views/0000755000000000000000000000000012232501726017376 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/client/views/term/0000755000000000000000000000000012232501726020345 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/client/views/term/area.go0000644000000000000000000000160612232501726021607 0ustar rootroot// shared internal functions for handling output to the terminal package term import ( "fmt" termbox "github.com/nsf/termbox-go" ) const ( fgColor = termbox.ColorWhite bgColor = termbox.ColorDefault ) type area struct { // top-left corner x, y int // size of the area w, h int // default colors fgColor, bgColor termbox.Attribute } func NewArea(x, y, w, h int) *area { return &area{x, y, w, h, fgColor, bgColor} } func (a *area) Clear() { for i := 0; i < a.w; i++ { for j := 0; j < a.h; j++ { termbox.SetCell(a.x+i, a.y+j, ' ', a.fgColor, a.bgColor) } } } func (a *area) APrintf(fg termbox.Attribute, x, y int, arg0 string, args ...interface{}) { s := fmt.Sprintf(arg0, args...) for i, ch := range s { termbox.SetCell(a.x+x+i, a.y+y, ch, fg, bgColor) } } func (a *area) Printf(x, y int, arg0 string, args ...interface{}) { a.APrintf(a.fgColor, x, y, arg0, args...) } ngrok-1.6+dfsg.orig/src/ngrok/client/views/term/http.go0000644000000000000000000000511312232501726021653 0ustar rootrootpackage term import ( termbox "github.com/nsf/termbox-go" "ngrok/client/mvc" "ngrok/log" "ngrok/proto" "ngrok/util" "unicode/utf8" ) const ( size = 10 pathMaxLength = 25 ) type HttpView struct { log.Logger *area httpProto *proto.Http HttpRequests *util.Ring shutdown chan int termView *TermView } func colorFor(status string) termbox.Attribute { switch status[0] { case '3': return termbox.ColorCyan case '4': return termbox.ColorYellow case '5': return termbox.ColorRed default: } return termbox.ColorWhite } func newTermHttpView(ctl mvc.Controller, termView *TermView, proto *proto.Http, x, y int) *HttpView { v := &HttpView{ httpProto: proto, HttpRequests: util.NewRing(size), area: NewArea(x, y, 70, size+5), shutdown: make(chan int), termView: termView, Logger: log.NewPrefixLogger("view", "term", "http"), } ctl.Go(v.Run) return v } func (v *HttpView) Run() { updates := v.httpProto.Txns.Reg() for { select { case txn := <-updates: v.Debug("Got HTTP update") if txn.(*proto.HttpTxn).Resp == nil { v.HttpRequests.Add(txn) } v.Render() } } } func (v *HttpView) Render() { v.Clear() v.Printf(0, 0, "HTTP Requests") v.Printf(0, 1, "-------------") for i, obj := range v.HttpRequests.Slice() { txn := obj.(*proto.HttpTxn) path := truncatePath(txn.Req.URL.Path) v.Printf(0, 3+i, "%s %v", txn.Req.Method, path) if txn.Resp != nil { v.APrintf(colorFor(txn.Resp.Status), 30, 3+i, "%s", txn.Resp.Status) } } v.termView.Flush() } func (v *HttpView) Shutdown() { close(v.shutdown) } func truncatePath(path string) string { // Truncate all long strings based on rune count if utf8.RuneCountInString(path) > pathMaxLength { path = string([]rune(path)[:pathMaxLength]) } // By this point, len(path) should be < pathMaxLength if we're dealing with single-byte runes. // Otherwise, we have a multi-byte string and need to calculate the size of each rune and // truncate manually. // // This is a workaround for a bug in termbox-go. Remove it when this issue is fixed: // https://github.com/nsf/termbox-go/pull/21 if len(path) > pathMaxLength { out := make([]byte, pathMaxLength, pathMaxLength) length := 0 for { r, size := utf8.DecodeRuneInString(path[length:]) if r == utf8.RuneError && size == 1 { break } // utf8.EncodeRune expects there to be enough room to store the full size of the rune if length+size <= pathMaxLength { utf8.EncodeRune(out[length:], r) length += size } else { break } } path = string(out[:length]) } return path } ngrok-1.6+dfsg.orig/src/ngrok/client/views/term/view.go0000644000000000000000000000721312232501726021651 0ustar rootroot// interactive terminal interface for local clients package term import ( termbox "github.com/nsf/termbox-go" "ngrok/client/mvc" "ngrok/log" "ngrok/proto" "ngrok/util" "time" ) type TermView struct { ctl mvc.Controller updates chan interface{} flush chan int shutdown chan int redraw *util.Broadcast subviews []mvc.View log.Logger *area } func NewTermView(ctl mvc.Controller) *TermView { // initialize terminal display termbox.Init() w, _ := termbox.Size() v := &TermView{ ctl: ctl, updates: ctl.Updates().Reg(), redraw: util.NewBroadcast(), flush: make(chan int), shutdown: make(chan int), Logger: log.NewPrefixLogger("view", "term"), area: NewArea(0, 0, w, 10), } ctl.Go(v.run) ctl.Go(v.input) return v } func connStatusRepr(status mvc.ConnStatus) (string, termbox.Attribute) { switch status { case mvc.ConnConnecting: return "connecting", termbox.ColorCyan case mvc.ConnReconnecting: return "reconnecting", termbox.ColorRed case mvc.ConnOnline: return "online", termbox.ColorGreen } return "unknown", termbox.ColorWhite } func (v *TermView) draw() { state := v.ctl.State() v.Clear() // quit instructions quitMsg := "(Ctrl+C to quit)" v.Printf(v.w-len(quitMsg), 0, quitMsg) // new version message updateStatus := state.GetUpdateStatus() var updateMsg string switch updateStatus { case mvc.UpdateNone: updateMsg = "" case mvc.UpdateInstalling: updateMsg = "ngrok is updating" case mvc.UpdateReady: updateMsg = "ngrok has updated: restart ngrok for the new version" case mvc.UpdateAvailable: updateMsg = "new version available at https://ngrok.com" default: pct := float64(updateStatus) / 100.0 const barLength = 25 full := int(barLength * pct) bar := make([]byte, barLength+2) bar[0] = '[' bar[barLength+1] = ']' for i := 0; i < 25; i++ { if i <= full { bar[i+1] = '#' } else { bar[i+1] = ' ' } } updateMsg = "Downloading update: " + string(bar) } if updateMsg != "" { v.APrintf(termbox.ColorYellow, 30, 0, updateMsg) } v.APrintf(termbox.ColorBlue|termbox.AttrBold, 0, 0, "ngrok") statusStr, statusColor := connStatusRepr(state.GetConnStatus()) v.APrintf(statusColor, 0, 2, "%-30s%s", "Tunnel Status", statusStr) v.Printf(0, 3, "%-30s%s/%s", "Version", state.GetClientVersion(), state.GetServerVersion()) var i int = 4 for _, t := range state.GetTunnels() { v.Printf(0, i, "%-30s%s -> %s", "Forwarding", t.PublicUrl, t.LocalAddr) i++ } v.Printf(0, i+0, "%-30s%s", "Web Interface", v.ctl.GetWebInspectAddr()) connMeter, connTimer := state.GetConnectionMetrics() v.Printf(0, i+1, "%-30s%d", "# Conn", connMeter.Count()) msec := float64(time.Millisecond) v.Printf(0, i+2, "%-30s%.2fms", "Avg Conn Time", connTimer.Mean()/msec) termbox.Flush() } func (v *TermView) run() { defer close(v.shutdown) defer termbox.Close() redraw := v.redraw.Reg() defer v.redraw.UnReg(redraw) v.draw() for { v.Debug("Waiting for update") select { case <-v.flush: termbox.Flush() case <-v.updates: v.draw() case <-redraw: v.draw() case <-v.shutdown: return } } } func (v *TermView) Shutdown() { v.shutdown <- 1 <-v.shutdown } func (v *TermView) Flush() { v.flush <- 1 } func (v *TermView) NewHttpView(p *proto.Http) *HttpView { return newTermHttpView(v.ctl, v, p, 0, 12) } func (v *TermView) input() { for { ev := termbox.PollEvent() switch ev.Type { case termbox.EventKey: switch ev.Key { case termbox.KeyCtrlC: v.Info("Got quit command") v.ctl.Shutdown("") } case termbox.EventResize: v.Info("Resize event, redrawing") v.redraw.In() <- 1 case termbox.EventError: panic(ev.Err) } } } ngrok-1.6+dfsg.orig/src/ngrok/client/views/web/0000755000000000000000000000000012232501726020153 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/client/views/web/http.go0000644000000000000000000001461712232501726021472 0ustar rootroot// interative web user interface package web import ( "encoding/base64" "encoding/json" "encoding/xml" "html/template" "net/http" "net/http/httputil" "net/url" "ngrok/client/assets" "ngrok/client/mvc" "ngrok/log" "ngrok/proto" "ngrok/util" "strings" "unicode/utf8" ) type SerializedTxn struct { Id string Duration int64 Start int64 ConnCtx mvc.ConnectionContext *proto.HttpTxn `json:"-"` Req SerializedRequest Resp SerializedResponse } type SerializedBody struct { RawContentType string ContentType string Text string Length int Error string ErrorOffset int Form url.Values } type SerializedRequest struct { Raw string MethodPath string Params url.Values Header http.Header Body SerializedBody Binary bool } type SerializedResponse struct { Raw string Status string Header http.Header Body SerializedBody Binary bool } type WebHttpView struct { log.Logger webview *WebView ctl mvc.Controller httpProto *proto.Http state chan SerializedUiState HttpRequests *util.Ring idToTxn map[string]*SerializedTxn } type SerializedUiState struct { Tunnels []mvc.Tunnel } type SerializedPayload struct { Txns []interface{} UiState SerializedUiState } func newWebHttpView(ctl mvc.Controller, wv *WebView, proto *proto.Http) *WebHttpView { whv := &WebHttpView{ Logger: log.NewPrefixLogger("view", "web", "http"), webview: wv, ctl: ctl, httpProto: proto, idToTxn: make(map[string]*SerializedTxn), HttpRequests: util.NewRing(20), } ctl.Go(whv.updateHttp) whv.register() return whv } type XMLDoc struct { data []byte `xml:",innerxml"` } func makeBody(h http.Header, body []byte) SerializedBody { b := SerializedBody{ Length: len(body), Text: base64.StdEncoding.EncodeToString(body), ErrorOffset: -1, } // some errors like XML errors only give a line number // and not an exact offset offsetForLine := func(line int) int { lines := strings.SplitAfterN(b.Text, "\n", line) return b.Length - len(lines[len(lines)-1]) } var err error b.RawContentType = h.Get("Content-Type") if b.RawContentType != "" { b.ContentType = strings.TrimSpace(strings.Split(b.RawContentType, ";")[0]) switch b.ContentType { case "application/xml", "text/xml": err = xml.Unmarshal(body, new(XMLDoc)) if err != nil { if syntaxError, ok := err.(*xml.SyntaxError); ok { // xml syntax errors only give us a line number, so we // count to find an offset b.ErrorOffset = offsetForLine(syntaxError.Line) } } case "application/json": err = json.Unmarshal(body, new(json.RawMessage)) if err != nil { if syntaxError, ok := err.(*json.SyntaxError); ok { b.ErrorOffset = int(syntaxError.Offset) } } case "application/x-www-form-urlencoded": b.Form, err = url.ParseQuery(string(body)) } } if err != nil { b.Error = err.Error() } return b } func (whv *WebHttpView) updateHttp() { // open channels for incoming http state changes // and broadbasts txnUpdates := whv.httpProto.Txns.Reg() for txn := range txnUpdates { // XXX: it's not safe for proto.Http and this code // to be accessing txn and txn.(req/resp) without synchronization htxn := txn.(*proto.HttpTxn) // we haven't processed this transaction yet if we haven't set the // user data if htxn.UserCtx == nil { rawReq, err := httputil.DumpRequestOut(htxn.Req.Request, true) if err != nil { whv.Error("Failed to dump request: %v", err) continue } body := makeBody(htxn.Req.Header, htxn.Req.BodyBytes) whtxn := &SerializedTxn{ Id: util.RandId(8), HttpTxn: htxn, Req: SerializedRequest{ MethodPath: htxn.Req.Method + " " + htxn.Req.URL.Path, Raw: base64.StdEncoding.EncodeToString(rawReq), Params: htxn.Req.URL.Query(), Header: htxn.Req.Header, Body: body, Binary: !utf8.Valid(rawReq), }, Start: htxn.Start.Unix(), ConnCtx: htxn.ConnUserCtx.(mvc.ConnectionContext), } htxn.UserCtx = whtxn // XXX: unsafe map access from multiple go routines whv.idToTxn[whtxn.Id] = whtxn // XXX: use return value to delete from map so we don't leak memory whv.HttpRequests.Add(whtxn) } else { rawResp, err := httputil.DumpResponse(htxn.Resp.Response, true) if err != nil { whv.Error("Failed to dump response: %v", err) continue } txn := htxn.UserCtx.(*SerializedTxn) body := makeBody(htxn.Resp.Header, htxn.Resp.BodyBytes) txn.Duration = htxn.Duration.Nanoseconds() txn.Resp = SerializedResponse{ Status: htxn.Resp.Status, Raw: base64.StdEncoding.EncodeToString(rawResp), Header: htxn.Resp.Header, Body: body, Binary: !utf8.Valid(rawResp), } payload, err := json.Marshal(txn) if err != nil { whv.Error("Failed to serialized txn payload for websocket: %v", err) } whv.webview.wsMessages.In() <- payload } } } func (whv *WebHttpView) register() { http.HandleFunc("/http/in/replay", func(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { err := util.MakePanicTrace(r) whv.Error("Replay failed: %v", err) http.Error(w, err, 500) } }() r.ParseForm() txnid := r.Form.Get("txnid") if txn, ok := whv.idToTxn[txnid]; ok { reqBytes, err := base64.StdEncoding.DecodeString(txn.Req.Raw) if err != nil { panic(err) } whv.ctl.PlayRequest(txn.ConnCtx.Tunnel, reqBytes) w.Write([]byte(http.StatusText(200))) } else { http.Error(w, http.StatusText(400), 400) } }) http.HandleFunc("/http/in", func(w http.ResponseWriter, r *http.Request) { defer func() { if r := recover(); r != nil { err := util.MakePanicTrace(r) whv.Error("HTTP web view failed: %v", err) http.Error(w, err, 500) } }() pageTmpl, err := assets.ReadAsset("assets/client/page.html") if err != nil { panic(err) } tmpl := template.Must(template.New("page.html").Delims("{%", "%}").Parse(string(pageTmpl))) payloadData := SerializedPayload{ Txns: whv.HttpRequests.Slice(), UiState: SerializedUiState{Tunnels: whv.ctl.State().GetTunnels()}, } payload, err := json.Marshal(payloadData) if err != nil { panic(err) } // write the response if err := tmpl.Execute(w, string(payload)); err != nil { panic(err) } }) } func (whv *WebHttpView) Shutdown() { } ngrok-1.6+dfsg.orig/src/ngrok/client/views/web/view.go0000644000000000000000000000341312232501726021455 0ustar rootroot// interative web user interface package web import ( "github.com/garyburd/go-websocket/websocket" "net/http" "ngrok/client/assets" "ngrok/client/mvc" "ngrok/log" "ngrok/proto" "ngrok/util" "path" ) type WebView struct { log.Logger ctl mvc.Controller // messages sent over this broadcast are sent too all websocket connections wsMessages *util.Broadcast } func NewWebView(ctl mvc.Controller, addr string) *WebView { wv := &WebView{ Logger: log.NewPrefixLogger("view", "web"), wsMessages: util.NewBroadcast(), ctl: ctl, } // for now, always redirect to the http view http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/http/in", 302) }) // handle web socket connections http.HandleFunc("/_ws", func(w http.ResponseWriter, r *http.Request) { conn, err := websocket.Upgrade(w, r.Header, nil, 1024, 1024) if err != nil { http.Error(w, "Failed websocket upgrade", 400) wv.Warn("Failed websocket upgrade: %v", err) return } msgs := wv.wsMessages.Reg() defer wv.wsMessages.UnReg(msgs) for m := range msgs { err := conn.WriteMessage(websocket.OpText, m.([]byte)) if err != nil { // connection is closed break } } }) // serve static assets http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) { buf, err := assets.ReadAsset(path.Join("assets", "client", r.URL.Path[1:])) if err != nil { wv.Warn("Error serving static file: %s", err.Error()) http.NotFound(w, r) return } w.Write(buf) }) wv.Info("Serving web interface on %s", addr) wv.ctl.Go(func() { http.ListenAndServe(addr, nil) }) return wv } func (wv *WebView) NewHttpView(proto *proto.Http) *WebHttpView { return newWebHttpView(wv.ctl, wv, proto) } func (wv *WebView) Shutdown() { } ngrok-1.6+dfsg.orig/src/ngrok/client/release.go0000644000000000000000000000014712232501726020212 0ustar rootroot// +build release package client var ( rootCrtPaths = []string{"assets/client/tls/ngrokroot.crt"} ) ngrok-1.6+dfsg.orig/src/ngrok/client/debug.go0000644000000000000000000000021412232501726017653 0ustar rootroot// +build !release package client var ( rootCrtPaths = []string{"assets/client/tls/ngrokroot.crt", "assets/client/tls/snakeoilca.crt"} ) ngrok-1.6+dfsg.orig/src/ngrok/client/controller.go0000644000000000000000000000707312232501726020762 0ustar rootrootpackage client import ( "fmt" "ngrok/client/mvc" "ngrok/client/views/term" "ngrok/client/views/web" "ngrok/log" "ngrok/proto" "ngrok/util" "sync" ) type command interface{} type cmdQuit struct { // display this message after quit message string } type cmdPlayRequest struct { // the tunnel to play this request over tunnel mvc.Tunnel // the bytes of the request to issue payload []byte } // The MVC Controller type Controller struct { // Controller logger log.Logger // the model sends updates through this broadcast channel updates *util.Broadcast // the model model mvc.Model // the views views []mvc.View // interal structure to issue commands to the controller cmds chan command // internal structure to synchronize access to State object state chan mvc.State // options config *Configuration } // public interface func NewController() *Controller { ctl := &Controller{ Logger: log.NewPrefixLogger("controller"), updates: util.NewBroadcast(), cmds: make(chan command), views: make([]mvc.View, 0), state: make(chan mvc.State), } return ctl } func (ctl *Controller) State() mvc.State { return <-ctl.state } func (ctl *Controller) Update(state mvc.State) { ctl.updates.In() <- state } func (ctl *Controller) Updates() *util.Broadcast { return ctl.updates } func (ctl *Controller) Shutdown(message string) { ctl.cmds <- cmdQuit{message: message} } func (ctl *Controller) PlayRequest(tunnel mvc.Tunnel, payload []byte) { ctl.cmds <- cmdPlayRequest{tunnel: tunnel, payload: payload} } func (ctl *Controller) Go(fn func()) { go func() { defer func() { if r := recover(); r != nil { err := util.MakePanicTrace(r) ctl.Error(err) ctl.Shutdown(err) } }() fn() }() } // private functions func (ctl *Controller) doShutdown() { ctl.Info("Shutting down") var wg sync.WaitGroup // wait for all of the views, plus the model wg.Add(len(ctl.views) + 1) for _, v := range ctl.views { vClosure := v ctl.Go(func() { vClosure.Shutdown() wg.Done() }) } ctl.Go(func() { ctl.model.Shutdown() wg.Done() }) wg.Wait() } func (ctl *Controller) addView(v mvc.View) { ctl.views = append(ctl.views, v) } func (ctl *Controller) GetWebInspectAddr() string { return ctl.config.InspectAddr } func (ctl *Controller) Run(config *Configuration) { // Save the configuration ctl.config = config // init the model model := newClientModel(config, ctl) ctl.model = model var state mvc.State = model // init web ui var webView *web.WebView if config.InspectAddr != "disabled" { webView = web.NewWebView(ctl, config.InspectAddr) ctl.addView(webView) } // init term ui var termView *term.TermView if config.LogTo != "stdout" { termView = term.NewTermView(ctl) ctl.addView(termView) } for _, protocol := range model.GetProtocols() { switch p := protocol.(type) { case *proto.Http: if termView != nil { ctl.addView(termView.NewHttpView(p)) } if webView != nil { ctl.addView(webView.NewHttpView(p)) } default: } } ctl.Go(func() { autoUpdate(state, config.AuthToken) }) ctl.Go(ctl.model.Run) updates := ctl.updates.Reg() defer ctl.updates.UnReg(updates) done := make(chan int) for { select { case obj := <-ctl.cmds: switch cmd := obj.(type) { case cmdQuit: msg := cmd.message go func() { ctl.doShutdown() fmt.Println(msg) done <- 1 }() case cmdPlayRequest: ctl.Go(func() { ctl.model.PlayRequest(cmd.tunnel, cmd.payload) }) } case obj := <-updates: state = obj.(mvc.State) case ctl.state <- state: case <-done: return } } } ngrok-1.6+dfsg.orig/src/ngrok/client/tls.go0000644000000000000000000000120512232501726017370 0ustar rootrootpackage client import ( "crypto/tls" "crypto/x509" "encoding/pem" "fmt" "ngrok/client/assets" ) func LoadTLSConfig(rootCertPaths []string) (*tls.Config, error) { pool := x509.NewCertPool() for _, certPath := range rootCertPaths { rootCrt, err := assets.ReadAsset(certPath) if err != nil { return nil, err } pemBlock, _ := pem.Decode(rootCrt) if pemBlock == nil { return nil, fmt.Errorf("Bad PEM data") } certs, err := x509.ParseCertificates(pemBlock.Bytes) if err != nil { return nil, err } pool.AddCert(certs[0]) } return &tls.Config{ RootCAs: pool, ServerName: "ngrokd.ngrok.com", }, nil } ngrok-1.6+dfsg.orig/src/ngrok/version/0000755000000000000000000000000012232501726016450 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/version/version.go0000644000000000000000000000047012232501726020465 0ustar rootrootpackage version import ( "fmt" ) const ( Proto = "2" Major = "1" Minor = "6" ) func MajorMinor() string { return fmt.Sprintf("%s.%s", Major, Minor) } func Full() string { return fmt.Sprintf("%s-%s.%s", Proto, Major, Minor) } func Compat(client string, server string) bool { return client == server } ngrok-1.6+dfsg.orig/src/ngrok/proto/0000755000000000000000000000000012232501726016126 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/proto/tcp.go0000644000000000000000000000034312232501726017243 0ustar rootrootpackage proto import ( "ngrok/conn" ) type Tcp struct{} func NewTcp() *Tcp { return new(Tcp) } func (h *Tcp) GetName() string { return "tcp" } func (h *Tcp) WrapConn(c conn.Conn, ctx interface{}) conn.Conn { return c } ngrok-1.6+dfsg.orig/src/ngrok/proto/http.go0000644000000000000000000000642412232501726017442 0ustar rootrootpackage proto import ( "bytes" metrics "github.com/inconshreveable/go-metrics" "io" "io/ioutil" "net/http" "net/http/httputil" "ngrok/conn" "ngrok/util" "sync" "time" ) type HttpRequest struct { *http.Request BodyBytes []byte } type HttpResponse struct { *http.Response BodyBytes []byte } type HttpTxn struct { Req *HttpRequest Resp *HttpResponse Start time.Time Duration time.Duration UserCtx interface{} ConnUserCtx interface{} } type Http struct { Txns *util.Broadcast reqGauge metrics.Gauge reqMeter metrics.Meter reqTimer metrics.Timer } func NewHttp() *Http { return &Http{ Txns: util.NewBroadcast(), reqGauge: metrics.NewGauge(), reqMeter: metrics.NewMeter(), reqTimer: metrics.NewTimer(), } } func extractBody(r io.Reader) ([]byte, io.ReadCloser, error) { buf := new(bytes.Buffer) _, err := buf.ReadFrom(r) return buf.Bytes(), ioutil.NopCloser(buf), err } func (h *Http) GetName() string { return "http" } func (h *Http) WrapConn(c conn.Conn, ctx interface{}) conn.Conn { tee := conn.NewTee(c) lastTxn := make(chan *HttpTxn) go h.readRequests(tee, lastTxn, ctx) go h.readResponses(tee, lastTxn) return tee } func (h *Http) readRequests(tee *conn.Tee, lastTxn chan *HttpTxn, connCtx interface{}) { defer close(lastTxn) for { req, err := http.ReadRequest(tee.WriteBuffer()) if err != nil { // no more requests to be read, we're done break } // make sure we read the body of the request so that // we don't block the writer _, err = httputil.DumpRequest(req, true) h.reqMeter.Mark(1) if err != nil { tee.Warn("Failed to extract request body: %v", err) } // golang's ReadRequest/DumpRequestOut is broken. Fix up the request so it works later req.URL.Scheme = "http" req.URL.Host = req.Host txn := &HttpTxn{Start: time.Now(), ConnUserCtx: connCtx} txn.Req = &HttpRequest{Request: req} txn.Req.BodyBytes, txn.Req.Body, err = extractBody(req.Body) lastTxn <- txn h.Txns.In() <- txn } } func (h *Http) readResponses(tee *conn.Tee, lastTxn chan *HttpTxn) { for txn := range lastTxn { resp, err := http.ReadResponse(tee.ReadBuffer(), txn.Req.Request) txn.Duration = time.Since(txn.Start) h.reqTimer.Update(txn.Duration) if err != nil { tee.Warn("Error reading response from server: %v", err) // no more responses to be read, we're done break } // make sure we read the body of the response so that // we don't block the reader _, _ = httputil.DumpResponse(resp, true) txn.Resp = &HttpResponse{Response: resp} txn.Resp.BodyBytes, txn.Resp.Body, err = extractBody(resp.Body) if err != nil { tee.Warn("Failed to extract response body: %v", err) } h.Txns.In() <- txn // XXX: remove web socket shim in favor of a real websocket protocol analyzer if txn.Req.Header.Get("Upgrade") == "websocket" { tee.Info("Upgrading to websocket") var wg sync.WaitGroup // shim for websockets // in order for websockets to work, we need to continue reading all of the // the bytes in the analyzer so that the joined connections will continue // sending bytes to each other wg.Add(2) go func() { ioutil.ReadAll(tee.WriteBuffer()) wg.Done() }() go func() { ioutil.ReadAll(tee.ReadBuffer()) wg.Done() }() wg.Wait() break } } } ngrok-1.6+dfsg.orig/src/ngrok/proto/interface.go0000644000000000000000000000020312232501726020410 0ustar rootrootpackage proto import ( "ngrok/conn" ) type Protocol interface { GetName() string WrapConn(conn.Conn, interface{}) conn.Conn } ngrok-1.6+dfsg.orig/src/ngrok/util/0000755000000000000000000000000012232501726015740 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/util/errors.go0000644000000000000000000000124412232501726017604 0ustar rootrootpackage util import ( "fmt" "runtime" ) const crashMessage = `panic: %v %s Oh noes! ngrok crashed! Please submit the stack trace and any relevant information to: github.com/inconshreveable/ngrok/issues` func MakePanicTrace(err interface{}) string { stackBuf := make([]byte, 4096) n := runtime.Stack(stackBuf, false) return fmt.Sprintf(crashMessage, err, stackBuf[:n]) } // Runs the given function and converts any panic encountered while doing so // into an error. Useful for sending to channels that will close func PanicToError(fn func()) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("Panic: %v", r) } }() fn() return } ngrok-1.6+dfsg.orig/src/ngrok/util/broadcast.go0000644000000000000000000000231412232501726020231 0ustar rootrootpackage util type Broadcast struct { listeners []chan interface{} reg chan (chan interface{}) unreg chan (chan interface{}) in chan interface{} } func NewBroadcast() *Broadcast { b := &Broadcast{ listeners: make([]chan interface{}, 0), reg: make(chan (chan interface{})), unreg: make(chan (chan interface{})), in: make(chan interface{}), } go func() { for { select { case l := <-b.unreg: // remove L from b.listeners // this operation is slow: O(n) but not used frequently // unlike iterating over listeners oldListeners := b.listeners b.listeners = make([]chan interface{}, 0, len(oldListeners)) for _, oldL := range oldListeners { if l != oldL { b.listeners = append(b.listeners, oldL) } } case l := <-b.reg: b.listeners = append(b.listeners, l) case item := <-b.in: for _, l := range b.listeners { l <- item } } } }() return b } func (b *Broadcast) In() chan interface{} { return b.in } func (b *Broadcast) Reg() chan interface{} { listener := make(chan interface{}) b.reg <- listener return listener } func (b *Broadcast) UnReg(listener chan interface{}) { b.unreg <- listener } ngrok-1.6+dfsg.orig/src/ngrok/util/id.go0000644000000000000000000000174512232501726016672 0ustar rootrootpackage util import ( "crypto/rand" "encoding/binary" "fmt" mrand "math/rand" ) func RandomSeed() (seed int64, err error) { err = binary.Read(rand.Reader, binary.LittleEndian, &seed) return } // creates a random identifier of the specified length func RandId(idlen int) string { b := make([]byte, idlen) var randVal uint32 for i := 0; i < idlen; i++ { byteIdx := i % 4 if byteIdx == 0 { randVal = mrand.Uint32() } b[i] = byte((randVal >> (8 * uint(byteIdx))) & 0xFF) } return fmt.Sprintf("%x", b) } // like RandId, but uses a crypto/rand for secure random identifiers func SecureRandId(idlen int) (id string, err error) { b := make([]byte, idlen) n, err := rand.Read(b) if n != idlen { err = fmt.Errorf("Only generated %d random bytes, %d requested", n, idlen) return } if err != nil { return } id = fmt.Sprintf("%x", b) return } func SecureRandIdOrPanic(idlen int) string { id, err := SecureRandId(idlen) if err != nil { panic(err) } return id } ngrok-1.6+dfsg.orig/src/ngrok/util/ring.go0000644000000000000000000000122112232501726017222 0ustar rootrootpackage util import ( "container/list" "sync" ) type Ring struct { sync.Mutex *list.List capacity int } func NewRing(capacity int) *Ring { return &Ring{capacity: capacity, List: list.New()} } func (r *Ring) Add(item interface{}) interface{} { r.Lock() defer r.Unlock() // add new item r.PushFront(item) // remove old item if at capacity var old interface{} if r.Len() >= r.capacity { old = r.Remove(r.Back()) } return old } func (r *Ring) Slice() []interface{} { r.Lock() defer r.Unlock() i := 0 items := make([]interface{}, r.Len()) for e := r.Front(); e != nil; e = e.Next() { items[i] = e.Value i++ } return items } ngrok-1.6+dfsg.orig/src/ngrok/util/shutdown.go0000644000000000000000000000124412232501726020143 0ustar rootrootpackage util import ( "sync" ) // A small utility class for managing controlled shutdowns type Shutdown struct { sync.Mutex inProgress bool begin chan int // closed when the shutdown begins complete chan int // closed when the shutdown completes } func NewShutdown() *Shutdown { return &Shutdown{ begin: make(chan int), complete: make(chan int), } } func (s *Shutdown) Begin() { s.Lock() defer s.Unlock() if s.inProgress == true { return } else { s.inProgress = true close(s.begin) } } func (s *Shutdown) WaitBegin() { <-s.begin } func (s *Shutdown) Complete() { close(s.complete) } func (s *Shutdown) WaitComplete() { <-s.complete } ngrok-1.6+dfsg.orig/src/ngrok/msg/0000755000000000000000000000000012232501726015551 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/msg/msg.go0000644000000000000000000000672312232501726016676 0ustar rootrootpackage msg import ( "encoding/json" "reflect" ) var TypeMap map[string]reflect.Type func init() { TypeMap = make(map[string]reflect.Type) t := func(obj interface{}) reflect.Type { return reflect.TypeOf(obj).Elem() } TypeMap["Auth"] = t((*Auth)(nil)) TypeMap["AuthResp"] = t((*AuthResp)(nil)) TypeMap["ReqTunnel"] = t((*ReqTunnel)(nil)) TypeMap["NewTunnel"] = t((*NewTunnel)(nil)) TypeMap["RegProxy"] = t((*RegProxy)(nil)) TypeMap["ReqProxy"] = t((*ReqProxy)(nil)) TypeMap["StartProxy"] = t((*StartProxy)(nil)) TypeMap["Ping"] = t((*Ping)(nil)) TypeMap["Pong"] = t((*Pong)(nil)) } type Message interface{} type Envelope struct { Type string Payload json.RawMessage } // When a client opens a new control channel to the server // it must start by sending an Auth message. type Auth struct { Version string // protocol version MmVersion string // major/minor software version (informational only) User string Password string OS string Arch string ClientId string // empty for new sessions } // A server responds to an Auth message with an // AuthResp message over the control channel. // // If Error is not the empty string // the server has indicated it will not accept // the new session and will close the connection. // // The server response includes a unique ClientId // that is used to associate and authenticate future // proxy connections via the same field in RegProxy messages. type AuthResp struct { Version string MmVersion string ClientId string Error string } // A client sends this message to the server over the control channel // to request a new tunnel be opened on the client's behalf. // ReqId is a random number set by the client that it can pull // from future NewTunnel's to correlate then to the requesting ReqTunnel. type ReqTunnel struct { ReqId string Protocol string // http only Hostname string Subdomain string HttpAuth string // tcp only RemotePort uint16 } // When the server opens a new tunnel on behalf of // a client, it sends a NewTunnel message to notify the client. // ReqId is the ReqId from the corresponding ReqTunnel message. // // A client may receive *multiple* NewTunnel messages from a single // ReqTunnel. (ex. A client opens an https tunnel and the server // chooses to open an http tunnel of the same name as well) type NewTunnel struct { ReqId string Url string Protocol string Error string } // When the server wants to initiate a new tunneled connection, it sends // this message over the control channel to the client. When a client receives // this message, it must initiate a new proxy connection to the server. type ReqProxy struct { } // After a client receives a ReqProxy message, it opens a new // connection to the server and sends a RegProxy message. type RegProxy struct { ClientId string } // This message is sent by the server to the client over a *proxy* connection before it // begins to send the bytes of the proxied request. type StartProxy struct { Url string // URL of the tunnel this connection connection is being proxied for ClientAddr string // Network address of the client initiating the connection to the tunnel } // A client or server may send this message periodically over // the control channel to request that the remote side acknowledge // its connection is still alive. The remote side must respond with a Pong. type Ping struct { } // Sent by a client or server over the control channel to indicate // it received a Ping. type Pong struct { } ngrok-1.6+dfsg.orig/src/ngrok/msg/pack.go0000644000000000000000000000157712232501726017030 0ustar rootrootpackage msg import ( "encoding/json" "errors" "fmt" "reflect" ) func unpack(buffer []byte, msgIn Message) (msg Message, err error) { var env Envelope if err = json.Unmarshal(buffer, &env); err != nil { return } if msgIn == nil { t, ok := TypeMap[env.Type] if !ok { err = errors.New(fmt.Sprintf("Unsupposted message type %s", env.Type)) return } // guess type msg = reflect.New(t).Interface().(Message) } else { msg = msgIn } err = json.Unmarshal(env.Payload, &msg) return } func UnpackInto(buffer []byte, msg Message) (err error) { _, err = unpack(buffer, msg) return } func Unpack(buffer []byte) (msg Message, err error) { return unpack(buffer, nil) } func Pack(payload interface{}) ([]byte, error) { return json.Marshal(struct { Type string Payload interface{} }{ Type: reflect.TypeOf(payload).Elem().Name(), Payload: payload, }) } ngrok-1.6+dfsg.orig/src/ngrok/msg/conn.go0000644000000000000000000000224312232501726017036 0ustar rootrootpackage msg import ( "encoding/binary" "errors" "fmt" "ngrok/conn" ) func readMsgShared(c conn.Conn) (buffer []byte, err error) { c.Debug("Waiting to read message") var sz int64 err = binary.Read(c, binary.LittleEndian, &sz) if err != nil { return } c.Debug("Reading message with length: %d", sz) buffer = make([]byte, sz) n, err := c.Read(buffer) c.Debug("Read message %s", buffer) if err != nil { return } if int64(n) != sz { err = errors.New(fmt.Sprintf("Expected to read %d bytes, but only read %d", sz, n)) return } return } func ReadMsg(c conn.Conn) (msg Message, err error) { buffer, err := readMsgShared(c) if err != nil { return } return Unpack(buffer) } func ReadMsgInto(c conn.Conn, msg Message) (err error) { buffer, err := readMsgShared(c) if err != nil { return } return UnpackInto(buffer, msg) } func WriteMsg(c conn.Conn, msg interface{}) (err error) { buffer, err := Pack(msg) if err != nil { return } c.Debug("Writing message: %s", string(buffer)) err = binary.Write(c, binary.LittleEndian, int64(len(buffer))) if err != nil { return } if _, err = c.Write(buffer); err != nil { return } return nil } ngrok-1.6+dfsg.orig/src/ngrok/main/0000755000000000000000000000000012232501726015707 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/main/ngrok/0000755000000000000000000000000012232501726017027 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/main/ngrok/ngrok.go0000644000000000000000000000011112232501726020467 0ustar rootrootpackage main import ( "ngrok/client" ) func main() { client.Main() } ngrok-1.6+dfsg.orig/src/ngrok/main/ngrokd/0000755000000000000000000000000012232501726017173 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/main/ngrokd/ngrokd.go0000644000000000000000000000011112232501726020777 0ustar rootrootpackage main import ( "ngrok/server" ) func main() { server.Main() } ngrok-1.6+dfsg.orig/src/ngrok/log/0000755000000000000000000000000012232501726015544 5ustar rootrootngrok-1.6+dfsg.orig/src/ngrok/log/logger.go0000644000000000000000000000373012232501726017355 0ustar rootrootpackage log import ( log "code.google.com/p/log4go" "fmt" ) var root log.Logger = make(log.Logger) func LogTo(target string) { var writer log.LogWriter = nil switch target { case "stdout": writer = log.NewConsoleLogWriter() case "none": // no logging default: writer = log.NewFileLogWriter(target, true) } if writer != nil { root.AddFilter("log", log.DEBUG, writer) } } type Logger interface { AddLogPrefix(string) ClearLogPrefixes() Debug(string, ...interface{}) Info(string, ...interface{}) Warn(string, ...interface{}) error Error(string, ...interface{}) error } type PrefixLogger struct { *log.Logger prefix string } func NewPrefixLogger(prefixes ...string) Logger { logger := &PrefixLogger{Logger: &root} for _, p := range prefixes { logger.AddLogPrefix(p) } return logger } func (pl *PrefixLogger) pfx(fmtstr string) interface{} { return fmt.Sprintf("%s %s", pl.prefix, fmtstr) } func (pl *PrefixLogger) Debug(arg0 string, args ...interface{}) { pl.Logger.Debug(pl.pfx(arg0), args...) } func (pl *PrefixLogger) Info(arg0 string, args ...interface{}) { pl.Logger.Info(pl.pfx(arg0), args...) } func (pl *PrefixLogger) Warn(arg0 string, args ...interface{}) error { return pl.Logger.Warn(pl.pfx(arg0), args...) } func (pl *PrefixLogger) Error(arg0 string, args ...interface{}) error { return pl.Logger.Error(pl.pfx(arg0), args...) } func (pl *PrefixLogger) AddLogPrefix(prefix string) { if len(pl.prefix) > 0 { pl.prefix += " " } pl.prefix += "[" + prefix + "]" } func (pl *PrefixLogger) ClearLogPrefixes() { pl.prefix = "" } // we should never really use these . . . always prefer logging through a prefix logger func Debug(arg0 string, args ...interface{}) { root.Debug(arg0, args...) } func Info(arg0 string, args ...interface{}) { root.Info(arg0, args...) } func Warn(arg0 string, args ...interface{}) error { return root.Warn(arg0, args...) } func Error(arg0 string, args ...interface{}) error { return root.Error(arg0, args...) } ngrok-1.6+dfsg.orig/.gitignore0000644000000000000000000000025312232501726015044 0ustar rootroot*.swp bin/ pkg/ src/code.google.com src/github.com src/bitbucket.org src/launchpad.net src/ngrok/client/assets/assets_release.go src/ngrok/server/assets/assets_release.go ngrok-1.6+dfsg.orig/LICENSE0000644000000000000000000000104712232501726014063 0ustar rootrootCopyright 2013 Alan Shreve Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ngrok-1.6+dfsg.orig/README.md0000644000000000000000000000336312232501726014340 0ustar rootroot# ngrok - Introspected tunnels to localhost ([homepage](https://ngrok.com)) ### "I want to securely expose a web server to the internet and capture all traffic for detailed inspection and replay" ![](https://ngrok.com/static/img/overview.png) ## What is ngrok? ngrok is a reverse proxy that creates a secure tunnel between from a public endpoint to a locally running web service. ngrok captures and analyzes all traffic over the tunnel for later inspection and replay. ## What can I do with ngrok? - Expose any http service behind a NAT or firewall to the internet on a subdomain of ngrok.com - Expose any tcp service behind a NAT or firewall to the internet on a random port of ngrok.com - Inspect all http requests/resposes that are transmitted over the tunnel - Replay any request that was transmitted over the tunnel ## What is ngrok useful for? - Temporarily sharing a website that is only running on your development machine - Demoing an app at a hackathon without deploying - Developing any services which consume webhooks (HTTP callbacks) by allowing you to replay those requests - Debugging and understanding any web service by inspecting the HTTP traffic - Running networked services on machines that are firewalled off from the internet ## Downloading and installing ngrok ngrok has _no_ runtime dependencies. Just download a single binary for your platform and run it. Some premium features are only available by creating an account on ngrok.com. If you need them, [create an account on ngrok.com](https://ngrok.com/signup). - [Linux](https://dl.ngrok.com/linux_386/ngrok.zip) - [Mac OSX](https://dl.ngrok.com/darwin_386/ngrok.zip) - [Windows](https://dl.ngrok.com/windows_386/ngrok.zip) ## Developing on ngrok [ngrok developer's guide](docs/DEVELOPMENT.md) ngrok-1.6+dfsg.orig/Makefile0000644000000000000000000000173212232501726014517 0ustar rootroot.PHONY: default server client deps fmt clean all release-client release-server release-all client-assets server-assets export GOPATH:=$(shell pwd) default: all deps: go get -tags '$(BUILDTAGS)' -d -v ngrok/... server: deps go install -tags '$(BUILDTAGS)' ngrok/main/ngrokd fmt: go fmt ngrok/... client: deps go install -tags '$(BUILDTAGS)' ngrok/main/ngrok client-assets: go get github.com/inconshreveable/go-bindata GOOS="" GOARCH="" go install github.com/inconshreveable/go-bindata bin/go-bindata -o src/ngrok/client/assets assets/client server-assets: go get github.com/inconshreveable/go-bindata GOOS="" GOARCH="" go install github.com/inconshreveable/go-bindata bin/go-bindata -o src/ngrok/server/assets assets/server release-client: BUILDTAGS=release release-client: client-assets client release-server: BUILDTAGS=release release-server: server-assets server release-all: release-client release-server all: fmt client server clean: go clean -i -r ngrok/...