Compare commits

...

14 Commits

Author SHA1 Message Date
Tatsh 9ba76db59c
Merge 8e0f3c630b into 648ff8eea1 2023-07-31 00:56:22 +10:00
Tatsh 8e0f3c630b
Merge branch 'vitalif:master' into launchd 2023-07-03 23:48:56 -04:00
Vitaliy Filippov 648ff8eea1 Cache layers during Docker build, take source from the current dir instead of cloning 2022-12-10 13:20:43 +03:00
Vitaliy Filippov eb82bfe28b
Merge pull request #361 from jumoog/master
reduce docker image size even more
2022-12-10 13:10:12 +03:00
Vitaliy Filippov f9e9fe510d
Merge pull request #363 from junghans/patch-1
Syncer.hh: fix build with gcc-12
2022-12-10 12:57:20 +03:00
Vitaliy Filippov ae38035ef4
Merge pull request #371 from jasper1378/master
Fix CMake CMP0004 Error
2022-12-10 12:56:59 +03:00
Christoph Junghans b788284020 Syncer.hh: fix build with gcc-12 2022-12-04 16:31:16 +01:00
Kilian von Pflugk cd4665ae1b reduce docker image size even more 2022-12-04 16:24:07 +01:00
Vitaliy Filippov d03c4a24ce Bump version to 0.5.3 2022-11-09 12:45:14 +03:00
Vitaliy Filippov 328987ec34 Fix readme 2022-11-09 12:41:47 +03:00
Vitaliy Filippov 6645206d27 Implement Google OAuth loopback IP address flow (fixes #362 #372 #365) 2022-11-09 12:37:18 +03:00
Jasper Young 5c8e87ee9a Fix CMake CMP0004 Error 2022-09-20 20:10:28 -04:00
Christoph Junghans 3cf1c058a3
Syncer.hh: fix build with gcc-12 2022-05-21 09:36:52 -06:00
Andrew Udvare fc65d815f7 Add macOS launchd files for automatic syncing 2021-05-31 03:07:59 -04:00
14 changed files with 270 additions and 55 deletions

View File

@ -1,4 +1,5 @@
.git *
Dockerfile !cmake
.dockerignore !grive
.gitignore !libgrive
!CMakeLists.txt

View File

@ -4,7 +4,7 @@ project(grive2)
include(GNUInstallDirs) include(GNUInstallDirs)
# Grive version. remember to update it for every new release! # Grive version. remember to update it for every new release!
set( GRIVE_VERSION "0.5.2-dev" CACHE STRING "Grive version" ) set( GRIVE_VERSION "0.5.3" CACHE STRING "Grive version" )
message(WARNING "Version to build: ${GRIVE_VERSION}") message(WARNING "Version to build: ${GRIVE_VERSION}")
# common compile options # common compile options
@ -23,5 +23,14 @@ if ( HAVE_SYSTEMD )
add_subdirectory( systemd ) add_subdirectory( systemd )
endif( HAVE_SYSTEMD ) endif( HAVE_SYSTEMD )
find_program(
HAVE_LAUNCHD launchd
PATHS /sbin
NO_DEFAULT_PATH
)
if ( HAVE_LAUNCHD )
add_subdirectory( launchd )
endif( HAVE_LAUNCHD )
add_subdirectory( libgrive ) add_subdirectory( libgrive )
add_subdirectory( grive ) add_subdirectory( grive )

View File

@ -1,27 +1,25 @@
FROM alpine:3.7 as build FROM alpine:3.7 as build
RUN apk add git make cmake g++ libgcrypt-dev yajl-dev yajl \ RUN apk add make cmake g++ libgcrypt-dev yajl-dev yajl \
boost-dev curl-dev expat-dev cppunit-dev binutils-dev \ boost-dev curl-dev expat-dev cppunit-dev binutils-dev \
pkgconfig \ pkgconfig
&& git clone https://github.com/vitalif/grive2.git \
&& mkdir grive2/build \ ADD . /grive2
&& cd grive2/build \
&& cmake .. \ RUN mkdir /grive2/build \
&& make -j4 \ && cd /grive2/build \
&& make install \ && cmake .. \
&& cd ../.. \ && make -j4 install
&& rm -rf grive2 \
&& mkdir /drive
FROM alpine:3.7 FROM alpine:3.7
COPY --from=build /usr/local/bin/grive /bin/grive
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.1/dumb-init_1.2.1_amd64 /bin/dumb-init RUN apk add yajl libcurl libgcrypt boost-program_options boost-regex libstdc++ boost-system \
RUN chmod 777 /bin/dumb-init /bin/grive \
&& mkdir /data \
&& apk add yajl-dev curl-dev libgcrypt \
boost-program_options boost-regex libstdc++ boost-system boost-dev binutils-dev \
&& apk add boost-filesystem --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main && apk add boost-filesystem --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main
COPY --from=build /usr/local/bin/grive /bin/grive
RUN chmod 777 /bin/grive \
&& mkdir /data
VOLUME /data VOLUME /data
WORKDIR /data WORKDIR /data
ENTRYPOINT ["dumb-init", "grive"] ENTRYPOINT grive

View File

@ -1,6 +1,6 @@
# Grive2 0.5.2-dev # Grive2 0.5.3
13 Nov 2019, Vitaliy Filippov 09 Nov 2022, Vitaliy Filippov
http://yourcmc.ru/wiki/Grive2 http://yourcmc.ru/wiki/Grive2
@ -39,10 +39,10 @@ grive -a
A URL should be printed. Go to the link. You will need to login to your Google A URL should be printed. Go to the link. You will need to login to your Google
account if you haven't done so. After granting the permission to Grive, the account if you haven't done so. After granting the permission to Grive, the
browser will show you an authenication code. Copy-and-paste that to the authorization code will be forwarded to the Grive application and you will be
standard input of Grive. redirected to a localhost web page confirming the authorization.
If everything works fine, Grive will create .grive and .grive_state files in your If everything works fine, Grive will create .grive and .grive\_state files in your
current directory. It will also start downloading files from your Google Drive to current directory. It will also start downloading files from your Google Drive to
your current directory. your current directory.
@ -203,7 +203,10 @@ Alternativly you can define your own client_id and client_secret during build
## Version History ## Version History
### Grive2 v0.5.2-dev ### Grive2 v0.5.3
- Implement Google OAuth loopback IP redirect flow
- Various small fixes
### Grive2 v0.5.1 ### Grive2 v0.5.1

View File

@ -27,6 +27,9 @@ IF(LIBGCRYPTCONFIG_EXECUTABLE)
EXEC_PROGRAM(${LIBGCRYPTCONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE LIBGCRYPT_CFLAGS) EXEC_PROGRAM(${LIBGCRYPTCONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE LIBGCRYPT_CFLAGS)
string(REPLACE "fgrep: warning: fgrep is obsolescent; using grep -F" "" LIBGCRYPT_LIBRARIES "${LIBGCRYPT_LIBRARIES}")
string(STRIP "${LIBGCRYPT_LIBRARIES}" LIBGCRYPT_LIBRARIES)
IF(${LIBGCRYPT_CFLAGS} MATCHES "\n") IF(${LIBGCRYPT_CFLAGS} MATCHES "\n")
SET(LIBGCRYPT_CFLAGS " ") SET(LIBGCRYPT_CFLAGS " ")
ENDIF(${LIBGCRYPT_CFLAGS} MATCHES "\n") ENDIF(${LIBGCRYPT_CFLAGS} MATCHES "\n")

7
debian/changelog vendored
View File

@ -1,3 +1,10 @@
grive2 (0.5.3) unstable; urgency=medium
* Implement Google OAuth loopback IP redirect flow
* Various small fixes
-- Vitaliy Filippov <vitalif@yourcmc.ru> Wed, 09 Nov 2022 12:42:28 +0300
grive2 (0.5.2+git20210315) unstable; urgency=medium grive2 (0.5.2+git20210315) unstable; urgency=medium
* Newer dev version * Newer dev version

View File

@ -111,9 +111,9 @@ int Main( int argc, char **argv )
( "help,h", "Produce help message" ) ( "help,h", "Produce help message" )
( "version,v", "Display Grive version" ) ( "version,v", "Display Grive version" )
( "auth,a", "Request authorization token" ) ( "auth,a", "Request authorization token" )
( "id,i", po::value<std::string>(), "Authentication ID") ( "id,i", po::value<std::string>(), "Authentication ID")
( "secret,e", po::value<std::string>(), "Authentication secret") ( "secret,e", po::value<std::string>(), "Authentication secret")
( "print-url", "Only print url for request") ( "print-url", "Only print url for request")
( "path,p", po::value<std::string>(), "Path to working copy root") ( "path,p", po::value<std::string>(), "Path to working copy root")
( "dir,s", po::value<std::string>(), "Single subdirectory to sync") ( "dir,s", po::value<std::string>(), "Single subdirectory to sync")
( "verbose,V", "Verbose mode. Enable more messages than normal.") ( "verbose,V", "Verbose mode. Enable more messages than normal.")
@ -185,34 +185,32 @@ int Main( int argc, char **argv )
: default_secret ; : default_secret ;
OAuth2 token( http.get(), id, secret ) ; OAuth2 token( http.get(), id, secret ) ;
if ( vm.count("print-url") ) if ( vm.count("print-url") )
{ {
std::cout << token.MakeAuthURL() << std::endl ; std::cout << token.MakeAuthURL() << std::endl ;
return 0 ; return 0 ;
} }
std::cout std::cout
<< "-----------------------\n" << "-----------------------\n"
<< "Please go to this URL and get an authentication code:\n\n" << "Please open this URL in your browser to authenticate Grive2:\n\n"
<< token.MakeAuthURL() << token.MakeAuthURL()
<< std::endl ; << std::endl ;
std::cout if ( !token.GetCode() )
<< "\n-----------------------\n" {
<< "Please input the authentication code here: " << std::endl ; std::cout << "Authentication failed\n";
std::string code ; return -1;
std::cin >> code ; }
token.Auth( code ) ;
// save to config // save to config
config.Set( "id", Val( id ) ) ; config.Set( "id", Val( id ) ) ;
config.Set( "secret", Val( secret ) ) ; config.Set( "secret", Val( secret ) ) ;
config.Set( "refresh_token", Val( token.RefreshToken() ) ) ; config.Set( "refresh_token", Val( token.RefreshToken() ) ) ;
config.Save() ; config.Save() ;
} }
std::string refresh_token ; std::string refresh_token ;
std::string id ; std::string id ;
std::string secret ; std::string secret ;
@ -231,7 +229,7 @@ int Main( int argc, char **argv )
return -1; return -1;
} }
OAuth2 token( http.get(), refresh_token, id, secret ) ; OAuth2 token( http.get(), refresh_token, id, secret ) ;
AuthAgent agent( token, http.get() ) ; AuthAgent agent( token, http.get() ) ;
v2::Syncer2 syncer( &agent ); v2::Syncer2 syncer( &agent );

27
launchd/CMakeLists.txt Normal file
View File

@ -0,0 +1,27 @@
SET(GRIVE_SYNC_SH_BINARY "${CMAKE_INSTALL_PREFIX}/lib/grive/grive-sync.sh")
CONFIGURE_FILE(
com.github.vitalif.grive2.grive-changes.plist.in
com.github.vitalif.grive2.grive-changes.plist
@ONLY
)
CONFIGURE_FILE(
com.github.vitalif.grive2.grive-timer.plist.in
com.github.vitalif.grive2.grive-timer.plist
@ONLY
)
install(
FILES
${CMAKE_BINARY_DIR}/launchd/com.github.vitalif.grive2.grive-changes.plist
${CMAKE_BINARY_DIR}/launchd/com.github.vitalif.grive2.grive-timer.plist
DESTINATION
lib/launchd
)
install(
PROGRAMS
../systemd/grive-sync.sh
DESTINATION
lib/grive
)

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.github.vitalif.grive2.grive-changes</string>
<key>ProgramArguments</key>
<array>
<string>@GRIVE_SYNC_SH_BINARY@</string>
<string>listen</string>
<string>RELATIVE PATH FROM $HOME TO SYNC HERE</string>
</array>
<key>RunAtLoad</key>
<true />
<key>StandardOutPath</key>
<!-- Change HOME below to your $HOME path, e.g. /Users/name -->
<string>HOME/Library/Logs/grive2.log</string>
<key>StandardErrorPath</key>
<string>HOME/Library/Logs/grive2.log</string>
</dict>
</plist>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.github.vitalif.grive2.grive-timer</string>
<key>ProgramArguments</key>
<array>
<string>@GRIVE_SYNC_SH_BINARY@</string>
<string>sync</string>
<string>RELATIVE PATH FROM $HOME TO SYNC HERE</string>
</array>
<key>RunAtLoad</key>
<true />
<key>StartInterval</key>
<!-- Every 5 minutes -->
<integer>300</integer>
<key>StandardOutPath</key>
<!-- Change HOME below to your $HOME path, e.g. /Users/name -->
<string>HOME/Library/Logs/grive2.log</string>
<key>StandardErrorPath</key>
<string>HOME/Library/Logs/grive2.log</string>
</dict>
</plist>

View File

@ -21,6 +21,7 @@
#include "util/FileSystem.hh" #include "util/FileSystem.hh"
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <iosfwd> #include <iosfwd>

View File

@ -25,6 +25,13 @@
#include "http/Header.hh" #include "http/Header.hh"
#include "util/log/Log.hh" #include "util/log/Log.hh"
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
// for debugging // for debugging
#include <iostream> #include <iostream>
@ -50,18 +57,29 @@ OAuth2::OAuth2(
const std::string& client_id, const std::string& client_id,
const std::string& client_secret ) : const std::string& client_secret ) :
m_agent( agent ), m_agent( agent ),
m_port( 0 ),
m_socket( -1 ),
m_client_id( client_id ), m_client_id( client_id ),
m_client_secret( client_secret ) m_client_secret( client_secret )
{ {
} }
void OAuth2::Auth( const std::string& auth_code ) OAuth2::~OAuth2()
{
if ( m_socket >= 0 )
{
close( m_socket );
m_socket = -1;
}
}
bool OAuth2::Auth( const std::string& auth_code )
{ {
std::string post = std::string post =
"code=" + auth_code + "code=" + auth_code +
"&client_id=" + m_client_id + "&client_id=" + m_client_id +
"&client_secret=" + m_client_secret + "&client_secret=" + m_client_secret +
"&redirect_uri=" + "urn:ietf:wg:oauth:2.0:oob" + "&redirect_uri=http%3A%2F%2Flocalhost:" + std::to_string( m_port ) + "%2Fauth" +
"&grant_type=authorization_code" ; "&grant_type=authorization_code" ;
http::ValResponse resp ; http::ValResponse resp ;
@ -77,19 +95,120 @@ void OAuth2::Auth( const std::string& auth_code )
{ {
Log( "Failed to obtain auth token: HTTP %1%, body: %2%", Log( "Failed to obtain auth token: HTTP %1%, body: %2%",
code, m_agent->LastError(), log::error ) ; code, m_agent->LastError(), log::error ) ;
BOOST_THROW_EXCEPTION( AuthFailed() ); return false;
} }
return true;
} }
std::string OAuth2::MakeAuthURL() std::string OAuth2::MakeAuthURL()
{ {
if ( !m_port )
{
sockaddr_storage addr = { 0 };
addr.ss_family = AF_INET;
m_socket = socket( AF_INET, SOCK_STREAM, 0 );
if ( m_socket < 0 )
throw std::runtime_error( std::string("socket: ") + strerror(errno) );
if ( bind( m_socket, (sockaddr*)&addr, sizeof( addr ) ) < 0 )
{
close( m_socket );
m_socket = -1;
throw std::runtime_error( std::string("bind: ") + strerror(errno) );
}
socklen_t len = sizeof( addr );
if ( getsockname( m_socket, (sockaddr *)&addr, &len ) == -1 )
{
close( m_socket );
m_socket = -1;
throw std::runtime_error( std::string("getsockname: ") + strerror(errno) );
}
m_port = ntohs(((sockaddr_in*)&addr)->sin_port);
if ( listen( m_socket, 128 ) < 0 )
{
close( m_socket );
m_socket = -1;
m_port = 0;
throw std::runtime_error( std::string("listen: ") + strerror(errno) );
}
}
return "https://accounts.google.com/o/oauth2/auth" return "https://accounts.google.com/o/oauth2/auth"
"?scope=" + m_agent->Escape( "https://www.googleapis.com/auth/drive" ) + "?scope=" + m_agent->Escape( "https://www.googleapis.com/auth/drive" ) +
"&redirect_uri=urn:ietf:wg:oauth:2.0:oob" "&redirect_uri=http%3A%2F%2Flocalhost:" + std::to_string( m_port ) + "%2Fauth" +
"&response_type=code" "&response_type=code"
"&client_id=" + m_client_id ; "&client_id=" + m_client_id ;
} }
bool OAuth2::GetCode( )
{
sockaddr_storage addr = { 0 };
int peer_fd = -1;
while ( peer_fd < 0 )
{
socklen_t peer_addr_size = sizeof( addr );
peer_fd = accept( m_socket, (sockaddr*)&addr, &peer_addr_size );
if ( peer_fd == -1 && errno != EAGAIN && errno != EINTR )
throw std::runtime_error( std::string("accept: ") + strerror(errno) );
}
fcntl( peer_fd, F_SETFL, fcntl( peer_fd, F_GETFL, 0 ) | O_NONBLOCK );
struct pollfd pfd = (struct pollfd){
.fd = peer_fd,
.events = POLLIN|POLLRDHUP,
};
char buf[4096];
std::string request;
while ( true )
{
pfd.revents = 0;
poll( &pfd, 1, -1 );
if ( pfd.revents & POLLRDHUP )
break;
int r = 1;
while ( r > 0 )
{
r = read( peer_fd, buf, sizeof( buf ) );
if ( r > 0 )
request += std::string( buf, r );
else if ( r == 0 )
break;
else if ( errno != EAGAIN && errno != EINTR )
throw std::runtime_error( std::string("read: ") + strerror(errno) );
}
if ( r == 0 || ( r < 0 && request.find( "\n" ) > 0 ) ) // GET ... HTTP/1.1\r\n
break;
}
bool ok = false;
if ( request.substr( 0, 10 ) == "GET /auth?" )
{
std::string line = request;
int p = line.find( "\n" );
if ( p > 0 )
line = line.substr( 0, p );
p = line.rfind( " " );
if ( p > 0 )
line = line.substr( 0, p );
p = line.find( "code=" );
if ( p > 0 )
line = line.substr( p+5 );
p = line.find( "&" );
if ( p > 0 )
line = line.substr( 0, p );
ok = Auth( line );
}
std::string response = ( ok
? "Authenticated successfully. Please close the page"
: "Authentication error. Please try again" );
response = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/html; charset=utf-8\r\n"
"Connection: close\r\n"
"\r\n"+
response+
"\r\n";
write( peer_fd, response.c_str(), response.size() );
close( peer_fd );
return ok;
}
void OAuth2::Refresh( ) void OAuth2::Refresh( )
{ {
std::string post = std::string post =

View File

@ -41,13 +41,15 @@ public :
const std::string& refresh_code, const std::string& refresh_code,
const std::string& client_id, const std::string& client_id,
const std::string& client_secret ) ; const std::string& client_secret ) ;
~OAuth2( ) ;
std::string Str() const ; std::string Str() const ;
std::string MakeAuthURL() ; std::string MakeAuthURL() ;
void Auth( const std::string& auth_code ) ; bool Auth( const std::string& auth_code ) ;
void Refresh( ) ; void Refresh( ) ;
bool GetCode( ) ;
std::string RefreshToken( ) const ; std::string RefreshToken( ) const ;
std::string AccessToken( ) const ; std::string AccessToken( ) const ;
@ -59,7 +61,9 @@ private :
std::string m_access ; std::string m_access ;
std::string m_refresh ; std::string m_refresh ;
http::Agent* m_agent ; http::Agent* m_agent ;
int m_port ;
int m_socket ;
const std::string m_client_id ; const std::string m_client_id ;
const std::string m_client_secret ; const std::string m_client_secret ;
} ; } ;

View File

@ -84,7 +84,7 @@ void Config::Save( )
void Config::Set( const std::string& key, const Val& value ) void Config::Set( const std::string& key, const Val& value )
{ {
m_file.Add( key, value ) ; m_file.Set( key, value ) ;
} }
Val Config::Get( const std::string& key ) const Val Config::Get( const std::string& key ) const