diary

Text-based journaling program
git clone https://git.in0rdr.ch/diary.git
Log | Files | Refs | Pull requests | README | LICENSE

commit a1024d94542fb39c56b1bf6422367f49dc930989
parent 8604d4895b93cf133b74b89a821db223efdbe98e
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Sun,  1 Sep 2024 18:59:08 +0200

feat(caldav): oauth with oauth_eval_cmd

Diffstat:
MMakefile | 4+---
MREADME.md | 15++-------------
Mconfig/diary.cfg | 7++-----
Mman1/diary.1 | 89+++++++++++++++++++++++++++++++++++--------------------------------------------
Mman1/diary.1.html | 85++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mman1/diary.1.scd | 67+++++++++++++++++++++++++++++++++----------------------------------
Msrc/caldav.c | 551++++++++++++-------------------------------------------------------------------
Msrc/caldav.h | 11-----------
Msrc/diary.c | 18+++++-------------
Msrc/utils.c | 25++++++++++++++++++++-----
Msrc/utils.h | 18++++--------------
11 files changed, 225 insertions(+), 665 deletions(-)

diff --git a/Makefile b/Makefile @@ -9,9 +9,7 @@ MANDIR := $(DESTDIR)$(PREFIX)/share/man MAN1 = man1/diary.1 CC = gcc -CFLAGS = -Wall \ - -DGOOGLE_OAUTH_CLIENT_ID=\"$(GOOGLE_OAUTH_CLIENT_ID)\" \ - -DGOOGLE_OAUTH_CLIENT_SECRET=\"$(GOOGLE_OAUTH_CLIENT_SECRET)\" +CFLAGS = -Wall UNAME = ${shell uname} ifeq ($(UNAME),FreeBSD) diff --git a/README.md b/README.md @@ -75,18 +75,7 @@ Server = https://downloadcontent.opensuse.org/repositories/home:/in0rdr/Arch/$ar ``` ## Build from Source -1. Define [OAuth2 application credentials](https://developers.google.com/identity/protocols/oauth2) - if CalDAV sync should be effective: - - ``` - export GOOGLE_OAUTH_CLIENT_ID="" - export GOOGLE_OAUTH_CLIENT_SECRET="" - ``` - - Alternatively, leave these two variables unset and define the - clientid/secret in the configuration file at run-time (see diary man page). - -2. The compilation step requires development libraries for `ncurses`, `libcurl` +1. The compilation step requires development libraries for `ncurses`, `libcurl` and `liblttng-ust`. These packages might have a slightly different naming scheme depending on the distribution. To compile, run: @@ -96,7 +85,7 @@ Server = https://downloadcontent.opensuse.org/repositories/home:/in0rdr/Arch/$ar Note: for \*BSD users run gmake. -3. Install the binary (optional): +2. Install the binary (optional): ``` sudo make install diff --git a/config/diary.cfg b/config/diary.cfg @@ -18,12 +18,9 @@ editor = #caldav_server = # Calendar name for CalDAV sync #caldav_calendar = -# Google OAuth2 clientid and secretid -#google_clientid = -#google_secretid = -# Google OAuth2 tokenfile -#google_tokenfile = ~/.diary-token # CalDAV server username #caldav_username = # CalDAV server password #caldav_password = +# OAuth command to fetch access token. For example, "oama access" +#oauth_eval_cmd = diff --git a/man1/diary.1 b/man1/diary.1 @@ -5,7 +5,7 @@ .nh .ad l .\" Begin generated content: -.TH "DIARY" "1" "2024-06-22" +.TH "DIARY" "1" "2024-09-01" .PP .SH NAME diary - text-based journaling program @@ -298,15 +298,12 @@ editor = #caldav_server = # Calendar name for CalDAV sync #caldav_calendar = -# Google OAuth2 clientid and secretid -#google_clientid = -#google_secretid = -# Google OAuth2 tokenfile -#google_tokenfile = ~/\&.diary-token # CalDAV server username #caldav_username = # CalDAV server password #caldav_password = +# OAuth command to fetch access token\&. For example, "oama access" +#oauth_eval_cmd = EOF .fi .RE @@ -320,8 +317,8 @@ cp config/diary\&.cfg ${XDG_CONFIG_HOME:-~/\&.config}/diary/ .RE .PP The file "${XDG_CONFIG_HOME:-~/.\&config}/diary/diary.\&cfg" should adhere to a -basic "key = value" format.\& Lines can be commented with the special characters -"#"or ";".\& +basic "key = value" format.\& The value is read until the end of the line '\&\en'\&.\& +Lines can be commented with the special characters "#"or ";".\& .PP The following configuration keys are currently supported: .PP @@ -339,8 +336,6 @@ l lx lx lx lx l lx lx lx lx l lx lx lx lx l lx lx lx lx -l lx lx lx lx -l lx lx lx lx l lx lx lx lx. T{ \fBCommand Line Option\fR @@ -446,11 +441,11 @@ n/a T} T{ caldav_server T} T{ -Google calendar "https://apidata.\&googleusercontent.\&com/caldav/v2", Yahoo calendar "https://caldav.\&calendar.\&yahoo.\&com", GMX "https://caldav.\&gmx.\&net" or other CalDAV compatible provider +Google calendar "https://apidata.\&googleusercontent.\&com/caldav/v2", Yahoo calendar "https://caldav.\&calendar.\&yahoo.\&com", GMX "https://caldav.\&gmx.\&net" or other CalDAV compatible provider.\& T} T{ (empty) T} T{ -CalDAV server for \fBCALDAV SYNC\fR +CalDAV server for \fBCALDAV SYNC\fR (required).\& T} T{ n/a @@ -461,62 +456,40 @@ diary T} T{ (empty) T} T{ -Calendar name for \fBCALDAV SYNC\fR +Calendar name for \fBCALDAV SYNC\fR (required).\& T} T{ n/a T} T{ -google_clientid -T} T{ - -T} T{ -built-in at build time (see \fBGOOGLE CALENDAR OAUTH2\fR) or empty -T} T{ -Google Calendar for \fBGOOGLE CALENDAR OAUTH2\fR clientid -T} -T{ -n/a -T} T{ -google_secretid +caldav_username T} T{ T} T{ -built-in at build time (see \fBGOOGLE CALENDAR OAUTH2\fR) or empty -T} T{ -Google Calendar for \fBGOOGLE CALENDAR OAUTH2\fR secretid -T} -T{ -n/a -T} T{ -google_tokenfile -T} T{ -~/.\&diary-token -T} T{ -~/.\&diary-token +(empty) T} T{ -\fBGOOGLE CALENDAR OAUTH2\fR API token file location +CalDAV server username for \fBCALDAV SYNC\fR (optional).\& T} T{ n/a T} T{ -caldav_username +caldav_password T} T{ T} T{ (empty) T} T{ -CalDAV server username for \fBCALDAV SYNC\fR +CalDAV server password for \fBCALDAV SYNC\fR (optional).\& T} T{ n/a T} T{ -caldav_password +oauth_eval_cmd T} T{ - +"oama access <email>" or "pass" T} T{ (empty) T} T{ -CalDAV server password for \fBCALDAV SYNC\fR +OAuth command to fetch access token for \fBCALDAV SYNC\fR (optional).\& T} .TE .sp 1 @@ -562,9 +535,25 @@ calendar for syncing purposes and not to have multiple events for a day on the remote calendar.\& The diary only considers the first event of a given day.\& .PP The calender for synchronization can be defined with the configuration key -"caldav_calendar", see \fBCONFIGURATION FILE\fR.\& This key is empty by default.\& +"caldav_calendar".\& This key is empty by default.\& It is required to configure at +least following parameters in the \fBCONFIGURATION FILE\fR: +.PP +.PD 0 +.IP \(bu 4 +caldav_server: Calendar server API +.IP \(bu 4 +caldav_calendar: Calendar name for CalDAV sync +.PD +.PP +In addition, it is required to specifiy either "caldav_username" and +"caldav_password" for basicauth (takes precedence) OR the "oauth_eval_cmd" to +retreive an OAuth access token from a password manager or tool that prints the +token to stdout.\& Leave username/password empty if you intend to use OAuth +("oauth_eval_cmd").\& .PP -Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google server.\& +Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google server.\& Other +CalDAV servers and APIs that are secured with OAuth2 might work as well +(untested).\& .PP Use the EXPORT/IMPORT functionality for more batch oriented processing.\& Making a network request and deciding the most recently modified entry (local/remote) @@ -580,11 +569,11 @@ The Google Calendar CalDAV API is protected with OAuth2: .PP https://developers.\&google.\&com/identity/protocols/oauth2 .PP -The credentials and the consent screen can be redefined at compile time (export -"GOOGLE_OAUTH_CLIENT_ID"/"GOOGLE_OAUTH_CLIENT_SECRET" to environment) or during -runtime with the keys "google_clientid"/"google_secretid" in the \fBCONFIGURATION -FILE\fR.\& The token used to authenticate with the Google API is stored in the file -specified by "google_tokenfile" and renewed automatically.\& +The "application", the "consent screen" and the "credentials" (client id and +secret) need to be defined in the Google cloud console.\& Client id and secret +should be stored in an utility application that can fetch and refresh the OAuth +access token (e.\&g.\&, "oama").\& The command to access the access token +("oauth_eval_cmd") should be configured in the \fBCONFIGURATION FILE\fR.\& .PP The application requires two OAuth2 scopes (https://developers.\&google.\&com/calendar/api/auth) for CalDAV requests: diff --git a/man1/diary.1.html b/man1/diary.1.html @@ -240,15 +240,12 @@ editor = #caldav_server = # Calendar name for CalDAV sync #caldav_calendar = -# Google OAuth2 clientid and secretid -#google_clientid = -#google_secretid = -# Google OAuth2 tokenfile -#google_tokenfile = ~/.diary-token # CalDAV server username #caldav_username = # CalDAV server password #caldav_password = +# OAuth command to fetch access token. For example, &quot;oama access&quot; +#oauth_eval_cmd = EOF</pre> </div> <p class="Pp">To copy the sample file from the source repository:</p> @@ -257,8 +254,9 @@ EOF</pre> <pre>cp config/diary.cfg ${XDG_CONFIG_HOME:-~/.config}/diary/</pre> </div> <p class="Pp">The file &quot;${XDG_CONFIG_HOME:-~/.config}/diary/diary.cfg&quot; - should adhere to a basic &quot;key = value&quot; format. Lines can be - commented with the special characters &quot;#&quot;or &quot;;&quot;.</p> + should adhere to a basic &quot;key = value&quot; format. The value is read + until the end of the line '\n'. Lines can be commented with the special + characters &quot;#&quot;or &quot;;&quot;.</p> <p class="Pp">The following configuration keys are currently supported:</p> <p class="Pp"></p> <table class="tbl" border="1" style="border-style: solid;"> @@ -359,51 +357,38 @@ EOF</pre> &quot;https://apidata.googleusercontent.com/caldav/v2&quot;, Yahoo calendar &quot;https://caldav.calendar.yahoo.com&quot;, GMX &quot;https://caldav.gmx.net&quot; or other CalDAV compatible - provider</td> + provider.</td> <td>(empty)</td> - <td>CalDAV server for <b>CALDAV SYNC</b></td> + <td>CalDAV server for <b>CALDAV SYNC</b> (required).</td> </tr> <tr> <td>n/a</td> <td>caldav_calendar</td> <td>diary </td> <td>(empty)</td> - <td>Calendar name for <b>CALDAV SYNC</b></td> + <td>Calendar name for <b>CALDAV SYNC</b> (required).</td> </tr> <tr> <td>n/a</td> - <td>google_clientid</td> - <td></td> - <td>built-in at build time (see <b>GOOGLE CALENDAR OAUTH2</b>) or empty</td> - <td>Google Calendar for <b>GOOGLE CALENDAR OAUTH2</b> clientid</td> - </tr> - <tr> - <td>n/a</td> - <td>google_secretid</td> + <td>caldav_username</td> <td></td> - <td>built-in at build time (see <b>GOOGLE CALENDAR OAUTH2</b>) or empty</td> - <td>Google Calendar for <b>GOOGLE CALENDAR OAUTH2</b> secretid</td> - </tr> - <tr> - <td>n/a</td> - <td>google_tokenfile</td> - <td>~/.diary-token</td> - <td>~/.diary-token</td> - <td><b>GOOGLE CALENDAR OAUTH2</b> API token file location</td> + <td>(empty)</td> + <td>CalDAV server username for <b>CALDAV SYNC</b> (optional).</td> </tr> <tr> <td>n/a</td> - <td>caldav_username</td> + <td>caldav_password</td> <td></td> <td>(empty)</td> - <td>CalDAV server username for <b>CALDAV SYNC</b></td> + <td>CalDAV server password for <b>CALDAV SYNC</b> (optional).</td> </tr> <tr> <td>n/a</td> - <td>caldav_password</td> - <td></td> + <td>oauth_eval_cmd</td> + <td>&quot;oama access &lt;email&gt;&quot; or &quot;pass&quot;</td> <td>(empty)</td> - <td>CalDAV server password for <b>CALDAV SYNC</b></td> + <td>OAuth command to fetch access token for <b>CALDAV SYNC</b> + (optional).</td> </tr> </table> <p class="Pp"></p> @@ -459,10 +444,23 @@ EOF</pre> day on the remote calendar. The diary only considers the first event of a given day.</p> <p class="Pp">The calender for synchronization can be defined with the - configuration key &quot;caldav_calendar&quot;, see <b>CONFIGURATION - FILE</b>. This key is empty by default.</p> + configuration key &quot;caldav_calendar&quot;. This key is empty by default. + It is required to configure at least following parameters in the + <b>CONFIGURATION FILE</b>:</p> +<p class="Pp"></p> +<ul class="Bl-bullet"> + <li>caldav_server: Calendar server API</li> + <li>caldav_calendar: Calendar name for CalDAV sync</li> +</ul> +<p class="Pp">In addition, it is required to specifiy either + &quot;caldav_username&quot; and &quot;caldav_password&quot; for basicauth + (takes precedence) OR the &quot;oauth_eval_cmd&quot; to retreive an OAuth + access token from a password manager or tool that prints the token to + stdout. Leave username/password empty if you intend to use OAuth + (&quot;oauth_eval_cmd&quot;).</p> <p class="Pp">Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google - server.</p> + server. Other CalDAV servers and APIs that are secured with OAuth2 might + work as well (untested).</p> <p class="Pp">Use the EXPORT/IMPORT functionality for more batch oriented processing. Making a network request and deciding the most recently modified entry (local/remote) takes quite some time for large batches.</p> @@ -476,14 +474,13 @@ EOF</pre> <p class="Pp">https://console.cloud.google.com/apis/api/caldav.googleapis.com</p> <p class="Pp">The Google Calendar CalDAV API is protected with OAuth2:</p> <p class="Pp">https://developers.google.com/identity/protocols/oauth2</p> -<p class="Pp">The credentials and the consent screen can be redefined at compile - time (export - &quot;GOOGLE_OAUTH_CLIENT_ID&quot;/&quot;GOOGLE_OAUTH_CLIENT_SECRET&quot; to - environment) or during runtime with the keys - &quot;google_clientid&quot;/&quot;google_secretid&quot; in the - <b>CONFIGURATION</b> <b>FILE</b>. The token used to authenticate with the - Google API is stored in the file specified by &quot;google_tokenfile&quot; - and renewed automatically.</p> +<p class="Pp">The &quot;application&quot;, the &quot;consent screen&quot; and + the &quot;credentials&quot; (client id and secret) need to be defined in the + Google cloud console. Client id and secret should be stored in an utility + application that can fetch and refresh the OAuth access token (e.g., + &quot;oama&quot;). The command to access the access token + (&quot;oauth_eval_cmd&quot;) should be configured in the <b>CONFIGURATION + FILE</b>.</p> <p class="Pp">The application requires two OAuth2 scopes (https://developers.google.com/calendar/api/auth) for CalDAV requests:</p> <p class="Pp"></p> @@ -607,7 +604,7 @@ EOF</pre> </div> <table class="foot"> <tr> - <td class="foot-date">2024-06-22</td> + <td class="foot-date">2024-09-01</td> <td class="foot-os"></td> </tr> </table> diff --git a/man1/diary.1.scd b/man1/diary.1.scd @@ -180,15 +180,12 @@ editor = #caldav_server = # Calendar name for CalDAV sync #caldav_calendar = -# Google OAuth2 clientid and secretid -#google_clientid = -#google_secretid = -# Google OAuth2 tokenfile -#google_tokenfile = ~/.diary-token # CalDAV server username #caldav_username = # CalDAV server password #caldav_password = +# OAuth command to fetch access token. For example, "oama access" +#oauth_eval_cmd = EOF ``` @@ -199,8 +196,8 @@ cp config/diary.cfg ${XDG_CONFIG_HOME:-~/.config}/diary/ ``` The file "${XDG_CONFIG_HOME:-~/.config}/diary/diary.cfg" should adhere to a -basic "key = value" format. Lines can be commented with the special characters -"#"or ";". +basic "key = value" format. The value is read until the end of the line '\\n'. +Lines can be commented with the special characters "#"or ";". The following configuration keys are currently supported: @@ -274,39 +271,29 @@ The following configuration keys are currently supported: : caldav_server : Google calendar "https://apidata.googleusercontent.com/caldav/v2", Yahoo calendar "https://caldav.calendar.yahoo.com", GMX "https://caldav.gmx.net" or - other CalDAV compatible provider + other CalDAV compatible provider. : (empty) -: CalDAV server for *CALDAV SYNC* +: CalDAV server for *CALDAV SYNC* (required). | n/a : caldav_calendar : diary : (empty) -: Calendar name for *CALDAV SYNC* -| n/a -: google_clientid -: -: built-in at build time (see *GOOGLE CALENDAR OAUTH2*) or empty -: Google Calendar for *GOOGLE CALENDAR OAUTH2* clientid -| n/a -: google_secretid -: -: built-in at build time (see *GOOGLE CALENDAR OAUTH2*) or empty -: Google Calendar for *GOOGLE CALENDAR OAUTH2* secretid -| n/a -: google_tokenfile -: ~/.diary-token -: ~/.diary-token -: *GOOGLE CALENDAR OAUTH2* API token file location +: Calendar name for *CALDAV SYNC* (required). | n/a : caldav_username : : (empty) -: CalDAV server username for *CALDAV SYNC* +: CalDAV server username for *CALDAV SYNC* (optional). | n/a : caldav_password : : (empty) -: CalDAV server password for *CALDAV SYNC* +: CalDAV server password for *CALDAV SYNC* (optional). +| n/a +: oauth_eval_cmd +: "oama access <email>" or "pass" +: (empty) +: OAuth command to fetch access token for *CALDAV SYNC* (optional). # CUSTOM FORMATTING The preview of the journal entries can be processed with a custom *--fmt-cmd*. @@ -350,9 +337,21 @@ calendar for syncing purposes and not to have multiple events for a day on the remote calendar. The diary only considers the first event of a given day. The calender for synchronization can be defined with the configuration key -"caldav_calendar", see *CONFIGURATION FILE*. This key is empty by default. +"caldav_calendar". This key is empty by default. It is required to configure at +least following parameters in the *CONFIGURATION FILE*: + +- caldav_server: Calendar server API +- caldav_calendar: Calendar name for CalDAV sync + +In addition, it is required to specifiy either "caldav_username" and +"caldav_password" for basicauth (takes precedence) OR the "oauth_eval_cmd" to +retreive an OAuth access token from a password manager or tool that prints the +token to stdout. Leave username/password empty if you intend to use OAuth +("oauth_eval_cmd"). -Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google server. +Read GOOGLE CALENDAR OAUTH2 to setup authorization with the Google server. Other +CalDAV servers and APIs that are secured with OAuth2 might work as well +(untested). Use the EXPORT/IMPORT functionality for more batch oriented processing. Making a network request and deciding the most recently modified entry (local/remote) @@ -368,11 +367,11 @@ The Google Calendar CalDAV API is protected with OAuth2: https://developers.google.com/identity/protocols/oauth2 -The credentials and the consent screen can be redefined at compile time (export -"GOOGLE_OAUTH_CLIENT_ID"/"GOOGLE_OAUTH_CLIENT_SECRET" to environment) or during -runtime with the keys "google_clientid"/"google_secretid" in the *CONFIGURATION -FILE*. The token used to authenticate with the Google API is stored in the file -specified by "google_tokenfile" and renewed automatically. +The "application", the "consent screen" and the "credentials" (client id and +secret) need to be defined in the Google cloud console. Client id and secret +should be stored in an utility application that can fetch and refresh the OAuth +access token (e.g., "oama"). The command to access the access token +("oauth_eval_cmd") should be configured in the *CONFIGURATION FILE*. The application requires two OAuth2 scopes (https://developers.google.com/calendar/api/auth) for CalDAV requests: diff --git a/src/caldav.c b/src/caldav.c @@ -2,29 +2,11 @@ CURL* curl; char access_token[sizeof(char)*2048] = ""; -char refresh_token[sizeof(char)*512] = ""; -int token_ttl = 0; // Local bind address for receiving OAuth callbacks. // Reserve 2 chars for the ipv6 square brackets. char ip[INET6_ADDRSTRLEN], ipstr[INET6_ADDRSTRLEN + 2]; -/* Write a random code challenge of size len to dest */ -void random_code_challenge(size_t len, char* dest) { - // https://developers.google.com/identity/protocols/oauth2/native-app#create-code-challenge - // A code_verifier is a random string using characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~" - srand(time(NULL)); - - char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; - size_t alphabet_size = strlen(alphabet); - - int i = 0; - for (i = 0; i < len; i++) { - dest[i] = alphabet[rand() % alphabet_size]; - } - dest[len] = '\0'; -} - static size_t curl_write_mem_callback(void* contents, size_t size, size_t nmemb, void* userp) { size_t realsize = size * nmemb; struct curl_mem_chunk* mem = (struct curl_mem_chunk*)userp; @@ -53,363 +35,25 @@ void* get_in_addr(struct sockaddr* sa) { return &(((struct sockaddr_in6*)sa)->sin6_addr); } -/* -* Extract OAuth2 code from http header. Reads the code from -* the http header in http_header and writes the OAuth2 code -* to the char string pointed to by code. -*/ -char* extract_oauth_code(char* http_header) { - // example token: "/?code=&scope=" - char* res = strtok(http_header, " "); - while (res != NULL) { - if (strstr(res, "code") != NULL) { - res = strtok(res, "="); // code key - res = strtok(NULL, "&"); // code value - break; - } - res = strtok(NULL, " "); - } - return res; -} - -/* Read access token from file and refresh global token vars */ -char* read_tokenfile() { - FILE* token_file; - char* token_buf; - long token_bytes; - - char* tokenfile_path = expand_path(CONFIG.google_tokenfile); - token_file = fopen(tokenfile_path, "r"); - free(tokenfile_path); - - if (token_file == NULL) { - perror("Warning - failed to open tokenfile in read_tokenfile()"); - return NULL; - } - - fseek(token_file, 0, SEEK_END); - token_bytes = ftell(token_file); - rewind(token_file); - - token_buf = malloc(token_bytes + 1); - if (token_buf != NULL) { - size_t items_read = fread(token_buf, sizeof(char), token_bytes, token_file); - if (items_read != token_bytes) { - perror("Error while reading in read_tokenfile()"); - fclose(token_file); - return NULL; - } - token_buf[token_bytes] = '\0'; - - update_global_token_vars(token_buf); - } else { - perror("malloc failed"); - fclose(token_file); - return NULL; - } - fclose(token_file); - return token_buf; -} - -/* Refresh global token variables from input buffer */ -void update_global_token_vars(char* input) { - char* new_access_token = extract_json_value(input, "access_token", true); - tracepoint(diary, debug_string, "New access token", new_access_token); - if (new_access_token != NULL) { - strncpy(access_token, new_access_token, 250); - free(new_access_token); - } - - // program segfaults if NULL value is provided to atoi - char* token_ttl_str = extract_json_value(input, "expires_in", false); - if (token_ttl_str == NULL) { - token_ttl = 0; - } else { - token_ttl = atoi(token_ttl_str); - } - free(token_ttl_str); - - // only update the existing refresh token if the request actually - // contained a valid refresh_token, i.e, if it was the initial - // interactive authZ request from token code confirmed by the user - char* new_refresh_token = extract_json_value(input, "refresh_token", true); - tracepoint(diary, debug_string, "New refresh token", new_refresh_token); - if (new_refresh_token != NULL) { - strncpy(refresh_token, new_refresh_token, 250); - free(new_refresh_token); - } -} - -/* Persist contents from global token file variables to tokenfile */ -void update_tokenfile_from_global_vars() { - char* tokenfile_path = expand_path(CONFIG.google_tokenfile); - FILE* tokenfile = fopen(tokenfile_path, "wb"); - - if (tokenfile == NULL) { - perror("Warning - failed to open tokenfile in update_tokenfile_from_global_vars()"); - } else { - char contents[sizeof(char)*2048 + sizeof(char)*512 + 200]; - char* tokenfile_contents = "{\n" - " \"access_token\": \"%s\",\n" - " \"expires_in\": %i,\n" - " \"refresh_token\": \"%s\"\n" - "}\n"; - sprintf(contents, tokenfile_contents, - access_token, - token_ttl, - // Make sure the refresh token is re-written and persistet - // to the tokenfile for further requests, because this token - // is not returned by the refresh_token call: - // https://developers.google.com/identity/protocols/oauth2/native-app#offline - refresh_token); - fputs(contents, tokenfile); - } - fclose(tokenfile); - - if (chmod(tokenfile_path, S_IRUSR|S_IWUSR) == -1) { - perror("chmod"); - } - - free(tokenfile_path); -} - -/* Get access token with authorization code or refresh token */ -void get_access_token(char* code, char* verifier, bool refresh) { - CURLcode res; - char* postfields_refresh = "client_id=%s&client_secret=%s&grant_type=refresh_token&refresh_token=%s"; - char* postfields_code = "client_id=%s&client_secret=%s&code=%s&code_verifier=%s&grant_type=authorization_code&redirect_uri=http://%s:%i"; - char* postfields; - - tracepoint(diary, debug, "Getting access token get_access_token()"); - - if (refresh) { - // work with refresh token, subtract 3 placholders - tracepoint(diary, debug_sizet, "strlen(refresh_token)", strlen(refresh_token)); - postfields = calloc(strlen(postfields_refresh) - 6 + strlen(CONFIG.google_clientid) + strlen(CONFIG.google_secretid) + strlen(refresh_token) + 1, sizeof(char)); - sprintf(postfields, postfields_refresh, - CONFIG.google_clientid, - CONFIG.google_secretid, - refresh_token); - } else { - // work with auth code, subtract 6 placeholders - tracepoint(diary, debug_sizet, "strlen(postfields_code)", strlen(postfields_code)); - tracepoint(diary, debug_sizet, "strlen(CONFIG.google_clientid)", strlen(CONFIG.google_clientid)); - tracepoint(diary, debug_sizet, "strlen(CONFIG.google_secretid))", strlen(CONFIG.google_secretid)); - tracepoint(diary, debug_sizet, "strlen(code)", strlen(code)); - tracepoint(diary, debug_sizet, "strlen(verifier)", strlen(verifier)); - tracepoint(diary, debug_sizet, "strlen(ipstr)", strlen(ipstr)); - tracepoint(diary, debug_sizet, "strlen(ipstr)", strlen(MKSTR(GOOGLE_OAUTH_REDIRECT_PORT))); - postfields = calloc(strlen(postfields_code) - 12 - + strlen(CONFIG.google_clientid) - + strlen(CONFIG.google_secretid) - + strlen(code) - + strlen(verifier) - + strlen(ipstr) - + strlen(MKSTR(GOOGLE_OAUTH_REDIRECT_PORT)) + 1, sizeof(char)); - sprintf(postfields, postfields_code, - CONFIG.google_clientid, - CONFIG.google_secretid, - code, - verifier, - ipstr, - GOOGLE_OAUTH_REDIRECT_PORT); - } - - curl = curl_easy_init(); - - // https://curl.se/libcurl/c/getinmemory.html - struct curl_mem_chunk token_resp; - token_resp.memory = malloc(1); - if (token_resp.memory == NULL) { - perror("malloc failed"); - return; - } - token_resp.size = 0; - - if (curl) { - curl_easy_setopt(curl, CURLOPT_URL, GOOGLE_OAUTH_TOKEN_URL); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_mem_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&token_resp); - - res = curl_easy_perform(curl); - - curl_easy_cleanup(curl); - - // Only write tokenfile if the token in token_resp.memory looks valid. - // Assume it's valid, when the curl request succeeds (no HTTP errors). - // Otherwise, a valid token in the possisbly already existing file would - // be overwritten. This happens usually during syncing (s/S) while offline. - if (res == CURLE_OK) { - // update global token variables - update_global_token_vars(token_resp.memory); - update_tokenfile_from_global_vars(); - } else { - tracepoint(diary, error_string, "curl_easy_perform() failed in get_access_token()", curl_easy_strerror(res)); - } - } - - free(token_resp.memory); - free(postfields); -} - -/* Perform challenge/request to fetch authorization code */ -char* get_oauth_code(const char* verifier, WINDOW* header) { - struct addrinfo hints, *addr_res; - - memset(&hints, 0, sizeof(struct addrinfo)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - +/* Get access token from "oauth_eval_cmd" and refresh global var */ +void get_access_token() { + FILE *fp; int status; - if ((status=getaddrinfo(NULL, MKSTR(GOOGLE_OAUTH_REDIRECT_PORT), &hints, &addr_res)) != 0) { - tracepoint(diary, error_string, "getaddrinfo() failed in get_oauth_code()", gai_strerror(status)); - } - void *addr; - char *ipver; - //todo: extract - //addr = get_in_addr(addr_res->ai_addr); - if (addr_res->ai_family == AF_INET) { - struct sockaddr_in *ipv4 = (struct sockaddr_in *) addr_res->ai_addr; - addr = &(ipv4->sin_addr); - ipver = "IPv4"; - } else { // IPv6 - struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) addr_res->ai_addr; - addr = &(ipv6->sin6_addr); - ipver = "IPv6"; - } + fp = popen(CONFIG.oauth_eval_cmd, "r"); + if (fp == NULL) + perror("Failure running oauth_eval_cmd in get_access_token()"); - inet_ntop(addr_res->ai_family, addr, ip, sizeof ip); - if (strcmp("IPv6", ipver) == 0) { - sprintf(ipstr, "[%s]", ip); - } + while (fgets(access_token, sizeof(char)*2048, fp) != NULL) + printf("%s", access_token); - // Show Google OAuth URI - char uri[500]; - sprintf(uri, "%s?scope=%s&code_challenge=%s&response_type=%s&redirect_uri=http://%s:%i&client_id=%s", - GOOGLE_OAUTH_AUTHZ_URL, - GOOGLE_OAUTH_SCOPE, - verifier, - GOOGLE_OAUTH_RESPONSE_TYPE, - ipstr, - GOOGLE_OAUTH_REDIRECT_PORT, - CONFIG.google_clientid); - - // Show the Google OAuth2 authorization URI in the header - wclear(header); - wresize(header, LINES, getmaxx(header)); - mvwprintw(header, 0, 0, "Go to Google OAuth2 authorization URI. Use 'q' or 'Ctrl+c' to quit authorization process.\n%s", uri); - wrefresh(header); + // strip any newline characters + access_token[strcspn(access_token, "\n")] = '\0'; - int socketfd = socket(addr_res->ai_family, addr_res->ai_socktype, addr_res->ai_protocol); - if (socketfd < 0) { - perror("Error opening socket"); + status = pclose(fp); + if (status == -1) { + perror("Failure during pclose in get_access_token()"); } - - // reuse socket address - int yes=1; - setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); - - if (bind(socketfd, addr_res->ai_addr, addr_res->ai_addrlen) < 0) { - perror("Error binding socket"); - } - - freeaddrinfo(addr_res); - - int ls = listen(socketfd, GOOGLE_OAUTH_REDIRECT_SOCKET_BACKLOG); - if (ls < 0) { - perror("Listen error"); - } - - struct pollfd pfds[2]; - - pfds[0].fd = STDIN_FILENO; - pfds[0].events = POLLIN; - pfds[1].fd = socketfd; - pfds[1].events = POLLIN; - int fd_count = 2; - - int connfd, bytes_rec = 0, bytes_sent = 0; - char http_header[8*1024]; - char* reply = - "HTTP/1.1 200 OK\n" - "Content-Type: text/html\n" - "Connection: close\n\n" - "<html>" - "<head><title>Authorization successfull</title></head>" - "<body>" - "<p><b>Authorization successfull.</b></p>" - "<p>You consented that diary can access your Google calendar.<br/>" - "Please close this window and return to diary.</p>" - "</body>" - "</html>"; - - // Handle descriptors read-to-read (POLLIN), - // stdin or server socker, whichever is first - for (;;) { - int poll_count = poll(pfds, fd_count, -1); - - if (poll_count == -1) { - perror("Erro in poll"); - break; - } - - // Cancel through stdin - if (pfds[0].revents & POLLIN) { - noecho(); - int ch = wgetch(header); - echo(); - // sudo showkey -a - // Ctrl+c: ^C 0x03 - // q : q 0x71 - if (ch == 0x03 || ch == 0x71) { - tracepoint(diary, debug, "Hanging up, closing server socket"); - break; - } - } - if (pfds[1].revents & POLLIN) { - // accept connections but ignore client addr - connfd = accept(socketfd, NULL, NULL); - if (connfd < 0) { - perror("Error accepting connection"); - break; - } - - bytes_rec = recv(connfd, http_header, sizeof http_header, 0); - if (bytes_rec < 0) { - perror("Error reading stream message"); - break; - } - - bytes_sent = send(connfd, reply, strlen(reply), 0); - if (bytes_sent < 0) { - perror("Error sending"); - } - - close(connfd); - break; - } - } // end for ;; - - // close server socket - close(pfds[1].fd); - - if (bytes_rec == 0) { - tracepoint(diary, error, "No OAuth header found"); - return NULL; - } - - char* code = extract_oauth_code(http_header); - if (code == NULL) { - tracepoint(diary, error, "No OAuth code in http header"); - return NULL; - } - - char* oauth_code = malloc(strlen(code) + 1); - strcpy(oauth_code, code); - - return oauth_code; } /* Make a CalDAV request */ @@ -445,13 +89,14 @@ char* caldav_req(struct tm* date, char* url, char* http_method, char* postfields // construct header fields struct curl_slist *header = NULL; - // default to basic auth when Google credentials are not set + // prefer basicauth credentials when set if (basicauth) { char basicauth [strlen(CONFIG.caldav_username) + strlen(CONFIG.caldav_password) + 2]; sprintf(basicauth, "%s:%s", CONFIG.caldav_username, CONFIG.caldav_password); curl_easy_setopt(curl, CURLOPT_USERPWD, basicauth); tracepoint(diary, debug_string, "curl_easy_perform() basicauth", basicauth); } else { + // use OAuth credentials char bearer_token[strlen("Authorization: Bearer ") + strlen(access_token) + 1]; sprintf(bearer_token, "Authorization: Bearer %s", access_token); header = curl_slist_append(header, bearer_token); @@ -522,7 +167,7 @@ char* parse_caldav_current_user_principal(char* xml) { } /* Return home set uri from CalDAV home set XML response */ -char* parse_home_set(char* xml) { +char* parse_home_set(const char* xml) { char* homeset_needle = "calendar-home-set"; char* homeset_pos = strstr(xml, homeset_needle); @@ -541,7 +186,16 @@ char* parse_home_set(char* xml) { href = strchr(href, '>'); href++; // cut > } - return href; + + // return a copy of the href + char* res = (char*) malloc(strlen(href) * sizeof(char) + 1); + if (res == NULL) { + perror("malloc failed in parse_home_set()"); + return NULL; + } + strcpy(res, href); + + return res; } return NULL; } @@ -554,25 +208,34 @@ char* parse_caldav_calendar(char* xml, char* calendar) { char* displayname_pos = strstr(xml, displayname_needle); // this XML multistatus response does not contain the users calendar if (displayname_pos == NULL) { - tracepoint(diary, debug, "parse_caldav_calenar() displayname_pos was null"); + tracepoint(diary, debug, "parse_caldav_calendar() displayname_pos was null"); return NULL; } - tracepoint(diary, debug_string, "parse_caldav_calenar() displayname_pos", displayname_pos); + tracepoint(diary, debug_string, "parse_caldav_calendar() displayname_pos", displayname_pos); // shorten multistatus response and find last hyperlink - *displayname_pos= '\0'; + *displayname_pos = '\0'; - tracepoint(diary, debug_string, "parse_caldav_calenar() shortened multistatus response", xml); + tracepoint(diary, debug_string, "parse_caldav_calendar() shortened multistatus response", xml); char* href = strrstr(xml, "href>/"); if (href != NULL) { - tracepoint(diary, debug_string, "parse_caldav_calenar() last href", href); + tracepoint(diary, debug_string, "parse_caldav_calendar() last href", href); href = strtok(href, "<"); // :href>/caldav/v2/aaa%40group.calendar.google.com/events/ - tracepoint(diary, debug_string, "parse_caldav_calenar() last href strtok", href); + tracepoint(diary, debug_string, "parse_caldav_calendar() last href strtok", href); if (href != NULL) { href = strchr(href, '>'); href++; // cut > } - return href; + + // return a copy of the href + char* res = (char*) malloc(strlen(href) * sizeof(char) + 1); + if (res == NULL) { + perror("malloc failed in parse_caldav_calendar"); + return NULL; + } + strcpy(res, href); + + return res; } return NULL; } @@ -611,7 +274,7 @@ void put_event(struct tm* date, const char* dir, size_t dir_size, char* calendar size_t items_read = fread(descr + descr_label_size, sizeof(char), descr_bytes, fp); if (items_read != descr_bytes) { - perror("Error while reading in read_tokenfile()"); + perror("Error while reading in put_event()"); fclose(fp); return; } @@ -672,52 +335,27 @@ int caldav_sync(struct tm* date, bool confirm) { pthread_t progress_tid; - char* info_txt; - - bool google_oauth_enabled = !(strcmp(CONFIG.google_clientid, "") == 0 || strcmp(CONFIG.google_clientid, "") == 0); + bool oauth_enabled = !(strcmp(CONFIG.oauth_eval_cmd, "") == 0); bool basicauth_enabled = !(strcmp(CONFIG.caldav_username, "") == 0 || strcmp(CONFIG.caldav_password, "") == 0); + tracepoint(diary, debug_int, "oauth_enabled: ", oauth_enabled); + tracepoint(diary, debug_int, "basicauth_enabled", basicauth_enabled); - if (strcmp(CONFIG.caldav_server, "") == 0 || strcmp(CONFIG.caldav_calendar, "") == 0 || !(google_oauth_enabled || basicauth_enabled)) { + // check remote server and calendar are defined + if (strcmp(CONFIG.caldav_server, "") == 0 || strcmp(CONFIG.caldav_calendar, "") == 0) { tracepoint(diary, debug_string, "CONFIG.caldav_server", CONFIG.caldav_server); tracepoint(diary, debug_string, "CONFIG.caldav_calendar", CONFIG.caldav_calendar); tracepoint(diary, debug_string, "CONFIG.caldav_username", CONFIG.caldav_username); tracepoint(diary, debug_string, "CONFIG.caldav_password", CONFIG.caldav_password); - wclear(header); - wresize(header, LINES, getmaxx(header)); - info_txt = "Missing sync parameters. Configure CalDAV server and credentials.\n" - "Press any key to continue."; - mvwaddstr(header, 0, 0, info_txt); - wrefresh(header); - - // accept any input to proceed - noecho(); - wgetch(header); - echo(); - wclear(header); + show_info(header, "CalDAV config incomplete, press any key to continue.", NULL); + return -1; } - if (google_oauth_enabled) { - // fetch existing API tokens from file - char* tokfile = read_tokenfile(); - free(tokfile); - + if (oauth_enabled) { if (strcmp(access_token, "") == 0) { - // no access token exists yet, create new verifier - char challenge[GOOGLE_OAUTH_CODE_VERIFIER_LENGTH + 1]; - random_code_challenge(GOOGLE_OAUTH_CODE_VERIFIER_LENGTH, challenge); - - // fetch new code with verifier - char* code = get_oauth_code(challenge, header); - if (code == NULL) { - tracepoint(diary, error, "Error retrieving oauth code in caldav_sync()"); - return -1; - } - - // get acess token using code and verifier - tracepoint(diary, debug, "Fetching access token with code challenge"); - get_access_token(code, challenge, false); - - free(code); + // get acess token with oauth_eval_cmd + tracepoint(diary, debug_string, "Fetching access token using oauth_eval_cmd", CONFIG.oauth_eval_cmd); + get_access_token(); + tracepoint(diary, debug_string, "OAuth access token", access_token); } } @@ -729,39 +367,13 @@ int caldav_sync(struct tm* date, "</d:propfind>"; - // check if we can use the token from the tokenfile char* user_principal = caldav_req(date, CONFIG.caldav_server, "PROPFIND", principal_postfields, 0, basicauth_enabled, "application/xml", 1); tracepoint(diary, debug_string, "User principal", user_principal); if (user_principal == NULL) { // The principal could not be fetched - tracepoint(diary, debug, "Unable to fetch principal"); - if (google_oauth_enabled) { - // get new acess token with refresh token - tracepoint(diary, debug, "Fetching access token with refresh token"); - get_access_token(NULL, NULL, true); - // Retry request for event with new token - user_principal = caldav_req(date, CONFIG.caldav_server, "PROPFIND", principal_postfields, 0, basicauth_enabled, "application/xml", 1); - } - } - - if (user_principal == NULL) { - pthread_cancel(progress_tid); - wclear(header); - wresize(header, LINES, getmaxx(header)); - // TODO: this could also be wrong basicauth credentials - info_txt = "Offline, corrupted or otherwise invalid OAuth2 credential tokenfile.\n" - "Go online or delete tokenfile '%s' and restart diary to retry.\n" - "Press any key to continue."; - mvwprintw(header, 0, 0, info_txt, CONFIG.google_tokenfile); - wrefresh(header); - - // accept any input to proceed - noecho(); - wgetch(header); - echo(); - - wclear(header); + tracepoint(diary, debug, "Unable to fetch user principal"); + show_info(header, "Unable to fetch user principal, press any key to continue.", &progress_tid); return -1; } @@ -770,9 +382,7 @@ int caldav_sync(struct tm* date, // extract host without path as basis for further api paths char *caldav_host; char* caldav_host_scheme; - CURLU *h = curl_url(); - CURLUcode uc = curl_url_set(h, CURLUPART_URL, CONFIG.caldav_server, 0); - uc = curl_url_get(h, CURLUPART_HOST, &caldav_host, 0); + CURLU *h = curl_url(); CURLUcode uc = curl_url_set(h, CURLUPART_URL, CONFIG.caldav_server, 0); uc = curl_url_get(h, CURLUPART_HOST, &caldav_host, 0); uc = curl_url_get(h, CURLUPART_SCHEME, &caldav_host_scheme, 0); tracepoint(diary, debug_string, "Caldav server host name", caldav_host); tracepoint(diary, debug_string, "Caldav server scheme/protocol", caldav_host_scheme); @@ -796,27 +406,17 @@ int caldav_sync(struct tm* date, char* home_set = caldav_req(date, uri, "PROPFIND", homeset_request, 0, basicauth_enabled, "application/xml", 0); tracepoint(diary, debug_string, "Home set xml", home_set); - if (home_set == NULL) { - pthread_cancel(progress_tid); - wclear(header); - wresize(header, LINES, getmaxx(header)); - info_txt = "Error while fetching home set in caldav_sync()\n" - "Press any key to continue."; - mvwaddstr(header, 0, 0, info_txt); - wrefresh(header); - - // accept any input to proceed - noecho(); - wgetch(header); - echo(); - - wclear(header); - return -1; - } // parse home set from xml response char* home_set_parsed = parse_home_set(home_set); + tracepoint(diary, debug_string, "home_set_parsed", home_set_parsed); + free(home_set); + if (home_set_parsed == NULL) { + show_info(header, "Error while fetching home set in caldav_sync, press any key to continue", &progress_tid); + return -1; + } sprintf(uri, "%s://%s%s", caldav_host_scheme, caldav_host, home_set_parsed); + free(home_set_parsed); char* calendar_request = "<d:propfind xmlns:d='DAV:' xmlns:cs='http://calendarserver.org/ns/' xmlns:c='urn:ietf:params:xml:ns:caldav'>" " <d:prop>" @@ -834,6 +434,16 @@ int caldav_sync(struct tm* date, // get calendar URI from the home-set char* calendar_href = parse_caldav_calendar(calendar_xml, CONFIG.caldav_calendar); free(calendar_xml); + tracepoint(diary, debug_string, "calendar_href", calendar_href); + + if (calendar_href == NULL) { + tracepoint(diary, debug_string, "Could not find CalDAV calendar", CONFIG.caldav_calendar); + char* msg_fmtstr = "Could not find CalDAV calendar '%s', press any key to continue."; + char msg[strlen(msg_fmtstr) + strlen(CONFIG.caldav_calendar)]; + sprintf(msg, msg_fmtstr, CONFIG.caldav_calendar); + show_info(header, msg, &progress_tid); + return -1; + } char* xml_filter = "<c:calendar-query xmlns:d='DAV:' xmlns:c='urn:ietf:params:xml:ns:caldav'>" "<d:prop><d:getetag/><c:calendar-data/></d:prop>" @@ -864,14 +474,9 @@ int caldav_sync(struct tm* date, // DTSTAMP, UID and DESCRIPTION (from the first event) if (event == NULL) { - // Event not found. The curl request probably failed due to one of the missing input parameters: - // - Google Client ID - // - Google Secret ID - // - Google Calendar Name tracepoint(diary, debug, "Event not found"); pthread_cancel(progress_tid); wclear(header); - free(home_set); return -1; } @@ -933,7 +538,6 @@ int caldav_sync(struct tm* date, curl_free(caldav_host); free(event); free(remote_uid); - free(home_set); return 0; } else if (timediff > 0) { tracepoint(diary, debug, "Local file is newer. Uploading to remote."); @@ -963,7 +567,6 @@ int caldav_sync(struct tm* date, curl_free(caldav_host); free(event); free(remote_uid); - free(home_set); return -1; } @@ -1024,6 +627,8 @@ int caldav_sync(struct tm* date, curl_free(caldav_host); free(event); free(remote_uid); - free(home_set); // home_set was required for sprintfing calendar_href to event_uri + free(calendar_href); + pthread_join(progress_tid, NULL); + wclear(header); return conf_ch; } diff --git a/src/caldav.h b/src/caldav.h @@ -22,16 +22,6 @@ #define STR(s) #s #define MKSTR(s) STR(s) -// https://developers.google.com/identity/protocols/oauth2/native-app#create-code-challenge -// A valid code_verifier has a length between 43 and 128 characters -#define GOOGLE_OAUTH_CODE_VERIFIER_LENGTH 43 -#define GOOGLE_OAUTH_AUTHZ_URL "https://accounts.google.com/o/oauth2/auth" -#define GOOGLE_OAUTH_TOKEN_URL "https://oauth2.googleapis.com/token" -#define GOOGLE_OAUTH_SCOPE "https://www.googleapis.com/auth/calendar%20https://www.googleapis.com/auth/calendar.events" -#define GOOGLE_OAUTH_RESPONSE_TYPE "code" -#define GOOGLE_OAUTH_REDIRECT_PORT 9004 -#define GOOGLE_OAUTH_REDIRECT_SOCKET_BACKLOG 10 - int caldav_sync(struct tm* date, WINDOW* header, WINDOW* cal, @@ -39,7 +29,6 @@ int caldav_sync(struct tm* date, const char* dir, size_t dir_size, bool confirm); -void update_global_token_vars(char*); struct curl_mem_chunk { char* memory; diff --git a/src/diary.c b/src/diary.c @@ -322,7 +322,7 @@ bool read_config(const char* file_path) { // ignore comment lines if (*line == '#' || *line == ';') continue; - if (sscanf(line, "%79s = %79s", key_buf, value_buf) == 2) { + if (sscanf(line, "%79s = %79[^\n]", key_buf, value_buf) == 2) { if (strcmp("dir", key_buf) == 0) { expanded_value = expand_path(value_buf); CONFIG.dir = (char *) malloc(strlen(expanded_value) + 1 * sizeof(char)); @@ -352,17 +352,6 @@ bool read_config(const char* file_path) { } else if (strcmp("editor", key_buf) == 0) { CONFIG.editor = (char *) malloc(strlen(value_buf) + 1 * sizeof(char)); strcpy(CONFIG.editor, value_buf); - } else if (strcmp("google_tokenfile", key_buf) == 0) { - expanded_value = expand_path(value_buf); - CONFIG.google_tokenfile = (char *) malloc(strlen(expanded_value) + 1 * sizeof(char)); - strcpy(CONFIG.google_tokenfile, expanded_value); - free(expanded_value); - } else if (strcmp("google_clientid", key_buf) == 0) { - CONFIG.google_clientid = (char *) malloc(strlen(value_buf) + 1 * sizeof(char)); - strcpy(CONFIG.google_clientid, value_buf); - } else if (strcmp("google_secretid", key_buf) == 0) { - CONFIG.google_secretid = (char *) malloc(strlen(value_buf) + 1 * sizeof(char)); - strcpy(CONFIG.google_secretid, value_buf); } else if (strcmp("caldav_server", key_buf) == 0) { CONFIG.caldav_server = (char *) malloc(strlen(value_buf) + 1 * sizeof(char)); strcpy(CONFIG.caldav_server, value_buf); @@ -375,7 +364,10 @@ bool read_config(const char* file_path) { } else if (strcmp("caldav_password", key_buf) == 0) { CONFIG.caldav_password = (char *) malloc(strlen(value_buf) + 1 * sizeof(char)); strcpy(CONFIG.caldav_password, value_buf); - } + } else if (strcmp("oauth_eval_cmd", key_buf) == 0) { + CONFIG.oauth_eval_cmd = (char *) malloc(strlen(value_buf) + 1 * sizeof(char)); + strcpy(CONFIG.oauth_eval_cmd, value_buf); + } } } fclose (pfile); diff --git a/src/utils.c b/src/utils.c @@ -373,7 +373,6 @@ bool go_to(WINDOW* calendar, WINDOW* aside, time_t date, int* cur_pad_pos, struc return true; } - void* show_progress(void* vargp){ struct timespec timer = { 0, 70000000 }; WINDOW* header = (WINDOW*) vargp; @@ -394,6 +393,24 @@ void* show_progress(void* vargp){ } } +void show_info(WINDOW* w, char* msg, pthread_t* p) { + if (p != NULL) { + // cancel any progress spinner + pthread_cancel(*p); + } + + wclear(w); + wresize(w, LINES, getmaxx(w)); + mvwaddstr(w, 0, 0, msg); + wrefresh(w); + + // accept any input to proceed + noecho(); + wgetch(w); + echo(); + wclear(w); +} + config CONFIG = { .dir = NULL, .range = 1, @@ -403,11 +420,9 @@ config CONFIG = { .no_pty = false, .no_mouse = false, .editor = "", - .google_tokenfile = GOOGLE_OAUTH_TOKEN_FILE, - .google_clientid = GOOGLE_OAUTH_CLIENT_ID, - .google_secretid = GOOGLE_OAUTH_CLIENT_SECRET, .caldav_calendar = "", .caldav_server = "", .caldav_username = "", - .caldav_password = "" + .caldav_password = "", + .oauth_eval_cmd = "" }; diff --git a/src/utils.h b/src/utils.h @@ -7,20 +7,13 @@ #include <stdlib.h> #include <time.h> #include <string.h> +#include <pthread.h> #include <wordexp.h> #include <stdbool.h> #include <ncurses.h> #include "diary-tp.h" -#define GOOGLE_OAUTH_TOKEN_FILE "~/.diary-token" -#ifndef GOOGLE_OAUTH_CLIENT_ID - #define GOOGLE_OAUTH_CLIENT_ID "" -#endif -#ifndef GOOGLE_OAUTH_CLIENT_SECRET - #define GOOGLE_OAUTH_CLIENT_SECRET "" -#endif - #define CAL_WIDTH 21 #define ASIDE_WIDTH 4 #define MAX_MONTH_HEIGHT 6 @@ -36,6 +29,7 @@ char* strrstr(char *haystack, char *needle); void fpath(const char* dir, size_t dir_size, const struct tm* date, char** rpath, size_t rpath_size); bool go_to(WINDOW* calendar, WINDOW* aside, time_t date, int* cur_pad_pos, struct tm* curs_date, struct tm* cal_start, struct tm* cal_end); void* show_progress(void* vargp); +void show_info(WINDOW* w, char* msg, pthread_t* p); typedef struct { @@ -56,12 +50,6 @@ typedef struct bool no_mouse; // Editor to open journal files with char* editor; - // File for Google OAuth access token - char* google_tokenfile; - // Google client id - char* google_clientid; - // Google secret id - char* google_secretid; // CalDAV calendar to synchronize char* caldav_calendar; // CalDAV server URI @@ -70,6 +58,8 @@ typedef struct char* caldav_username; // CalDAV password char* caldav_password; + // OAuth command to fetch access token + char* oauth_eval_cmd; } config; extern config CONFIG;