commit 29424c70a80767831c2ca3e2996c4df2b62d601d
parent f7ca86d4c2b5bbbad94d6ab8db7c46c31f4603ca
Author: Andreas Gruhler <agruhl@gmx.ch>
Date: Sat, 15 May 2021 01:12:32 +0200
google oauth socket
Diffstat:
M | Makefile | | | 6 | ++++-- |
M | README.md | | | 13 | ++++++++++--- |
A | caldav.c | | | 208 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | caldav.h | | | 37 | +++++++++++++++++++++++++++++++++++++ |
M | diary.c | | | 12 | ++++++++++-- |
M | diary.h | | | 5 | +++-- |
6 files changed, 272 insertions(+), 9 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,5 +1,5 @@
TARGET = diary
-SRC = diary.c
+SRC = caldav.c diary.c
PREFIX ?= /usr/local
BINDIR ?= $(DESTDIR)$(PREFIX)/bin
@@ -7,7 +7,9 @@ MANDIR := $(DESTDIR)$(PREFIX)/share/man
MAN1 = diary.1
CC = gcc
-CFLAGS = -Wall
+CFLAGS = -Wall \
+ -DGOOGLE_OAUTH_CLIENT_ID=\"$(GOOGLE_OAUTH_CLIENT_ID)\" \
+ -DGOOGLE_OAUTH_CLIENT_SECRET=\"$(GOOGLE_OAUTH_CLIENT_SECRET)\"
UNAME = ${shell uname}
ifeq ($(UNAME),FreeBSD)
diff --git a/README.md b/README.md
@@ -27,7 +27,7 @@ This is a text based diary, inspired by [khal](https://github.com/pimutils/khal)
e, Enter Edit the current entry
d, x Delete/remove current entry
t Jump to today
- s Jump to specific day
+ f Find specific date
j, down go forward by 1 week
k, up go backward by 1 week
@@ -43,6 +43,8 @@ This is a text based diary, inspired by [khal](https://github.com/pimutils/khal)
J Go forward by 1 month
K Go backward by 1 month
+ s Sync changes with CalDAV server
+
q quit the program
```
@@ -65,14 +67,19 @@ Server = https://downloadcontent.opensuse.org/repositories/home:/in0rdr/Arch/$ar
## Build
[![Build Status](https://travis-ci.org/in0rdr/diary.svg?branch=master)](https://travis-ci.org/in0rdr/diary)
+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=""
+ ```
-1. Compile (requires ncurses):
+2. Compile (requires ncurses and libcurl):
```
make
```
Note: for *BSD users run gmake.
-2. Install the binary (optional):
+3. Install the binary (optional):
```
sudo make install
```
diff --git a/caldav.c b/caldav.c
@@ -0,0 +1,208 @@
+#include "caldav.h"
+
+/* 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);
+
+ for (int i = 0; i < len; i++) {
+ dest[i] = alphabet[rand() % alphabet_size];
+ }
+ dest[len-1] = '\0';
+}
+
+void* get_in_addr(struct sockaddr *sa) {
+ if (sa->sa_family == AF_INET) {
+ return &(((struct sockaddr_in*)sa)->sin_addr);
+ }
+
+ return &(((struct sockaddr_in6*)sa)->sin6_addr);
+}
+
+void caldav_sync(const struct tm* date, WINDOW* header) {
+ char challenge[GOOGLE_OAUTH_CODE_VERIFIER_LENGTH];
+ random_code_challenge(GOOGLE_OAUTH_CODE_VERIFIER_LENGTH, challenge);
+
+ struct addrinfo hints, *res;
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+
+ int status;
+ if ((status=getaddrinfo(NULL, MKSTR(GOOGLE_OAUTH_REDIRECT_PORT), &hints, &res)) != 0) {
+ fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
+ }
+
+ void *addr;
+ char *ipver;
+ //todo: extract
+ //addr = get_in_addr(res->ai_addr);
+ if (res->ai_family == AF_INET) {
+ struct sockaddr_in *ipv4 = (struct sockaddr_in *) res->ai_addr;
+ addr = &(ipv4->sin_addr);
+ ipver = "IPv4";
+ } else { // IPv6
+ struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *) res->ai_addr;
+ addr = &(ipv6->sin6_addr);
+ ipver = "IPv6";
+ }
+
+ // Reserve 2 chars for the ipv6 square brackets
+ char ip[INET6_ADDRSTRLEN], ipstr[INET6_ADDRSTRLEN+2];
+ inet_ntop(res->ai_family, addr, ip, sizeof ip);
+
+ if (strcmp("IPv6", ipver) == 0) {
+ sprintf(ipstr, "[%s]", ip);
+ }
+
+ // Show Google Oauth URI
+ fprintf(stderr, "Code challenge: %s\n", challenge);
+ char uri[250];
+ sprintf(uri, "%s?scope=%s&response_type=%s&redirect_uri=http://%s:%i&client_id=%s",
+ GOOGLE_OAUTH_AUTHZ_URL,
+ GOOGLE_OAUTH_SCOPE,
+ GOOGLE_OAUTH_RESPONSE_TYPE,
+ ipstr,
+ GOOGLE_OAUTH_REDIRECT_PORT,
+ GOOGLE_OAUTH_CLIENT_ID);
+ //fprintf(stderr, "Google OAuth2 authorization URI: %s\n", uri);
+
+ // Show the Google OAuth2 authorization URI in the header
+ wclear(header);
+ int row, col;
+ getmaxyx(header, row, col);
+ wresize(header, row+5, col);
+ //curs_set(2);
+ mvwprintw(header, 0, 0, "Go to Google OAuth2 authorization URI. Use 'ESC', 'CTRL+C' or 'q' to quit authorization process.\n%s", uri);
+
+ //curs_set(0);
+ wrefresh(header);
+
+ int socketfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+ if (socketfd < 0) {
+ perror("Error opening socket");
+ }
+
+ if (bind(socketfd, res->ai_addr, res->ai_addrlen) < 0) {
+ perror("Error binding socket");
+ }
+
+ freeaddrinfo(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 len, bytes_rec, bytes_sent;
+ char* msg =
+ "HTTP/1.1 200 OK\r\n\r\n"
+ "hello world\r\n";
+ len = strlen(msg);
+ char code[GOOGLE_OAUTH_RESPONSE_HEADER_SIZE];
+
+ for (;;) {
+ int poll_count = poll(pfds, fd_count, -1);
+
+ if (poll_count == -1) {
+ perror("poll");
+ break;
+ }
+ if (pfds[0].revents & POLLIN) {
+ int ch = fgetc(stdin);
+ // sudo showkey -a
+ // Ctrl+c: ^C 0x03
+ // Escape: ^[ 0x1b
+ // q : q 0x71
+ if (ch == 0x03 || ch == 0x1b || ch == 0x71) {
+ fprintf(stderr, "Hanging up, closing server socket\n");
+ close(pfds[1].fd);
+ break;
+ }
+ }
+ for (int i = 0; i < fd_count; i++) {
+ if (pfds[i].revents & POLLIN && pfds[i].fd == socketfd) {
+ // accept connections but ignore client addr
+ int connfd = accept(socketfd, NULL, NULL);
+ if (connfd < 0) {
+ perror("Error accepting connection");
+ }
+
+ bytes_rec = recv(connfd, code, sizeof code, 0);
+ if (bytes_rec == 0) {
+ fprintf(stderr, "Remote hung up\n");
+ break;
+ } else if (bytes_rec < 0) {
+ perror("Error reading stream message");
+ break;
+ }
+ fprintf(stderr, "Received code: %s\n", code);
+
+ // "Authorization step successfull: You consented that diary can access your Google calendar.\r\n"
+ // "Pleasee close this window and return to diary.\r\n";
+
+ bytes_sent = send(connfd, msg, len, 0);
+ if (bytes_sent < 0) {
+ perror("Error sending");
+ }
+
+ close(connfd);
+ //close(socketfd);
+
+ break;
+ } // end if server socket
+ } // end for fd_count
+ } // end for ;;
+
+// int bytes_rec, bytes_sent;
+// char buff[50];
+// // reading stream returns number of bytes read,
+// // until stream ends, then it returns 0
+// while ( (bytes_rec=read(connfd, buff, strlen(buff))) ) {
+// if (bytes_rec < 0) {
+// perror("Error reading stream message");
+// break;
+// }
+// fprintf(stderr, "reading");
+// }
+// while ( (bytes_sent=write(2, buff, strlen(buff))) ) {
+// // write buffer to stderr
+// if (bytes_sent < 0) {
+// perror("Error writing buffer to stderr");
+// }
+// fprintf(stderr, "writing");
+// }
+//
+// write(connfd, "GET /\r\n", strlen("GET /\r\n"));
+
+// close(connfd);
+// close(socketfd);
+
+
+// CURL *curl;
+// CURLcode res;
+//
+// curl = curl_easy_init();
+// if (curl) {
+// curl_easy_setops(curl, CURLOPT_URL, "https://accounts.google.com/o/oauth2/auth");
+// res = curl_easy_perform(curl);
+// if (res != CURLE_OK) {
+// fprintf(stderr, "curl_easy_perorm() failed: %s\n", curl_easy_strerror(res));
+// }
+// curl_easy_cleanup(curl);
+// }
+}
+
diff --git a/caldav.h b/caldav.h
@@ -0,0 +1,37 @@
+#ifndef DIARY_CALDAV_H
+#define DIARY_CALDAV_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <ncurses.h>
+//#include <openssl/sha.h>
+#include <curl/curl.h>
+
+#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_SCOPE "calendar.events.owned"
+#define GOOGLE_OAUTH_RESPONSE_TYPE "code"
+#define GOOGLE_OAUTH_REDIRECT_HOST "127.0.0.1"
+#define GOOGLE_OAUTH_REDIRECT_PORT 9004
+#define GOOGLE_OAUTH_REDIRECT_URI "http://" GOOGLE_OAUTH_REDIRECT_HOST ":" MKSTR(GOOGLE_OAUTH_REDIRECT_PORT)
+#define GOOGLE_OAUTH_REDIRECT_SOCKET_BACKLOG 10
+#define GOOGLE_OAUTH_RESPONSE_HEADER_SIZE 84
+
+void caldav_sync(const struct tm* date, WINDOW* header);
+
+#endif
diff --git a/diary.c b/diary.c
@@ -606,8 +606,8 @@ int main(int argc, char** argv) {
mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos);
break;
- // search for specific date
- case 's':
+ // find specific date
+ case 'f':
wclear(header);
curs_set(2);
mvwprintw(header, 0, 0, "Go to date [YYYY-MM-DD]: ");
@@ -699,6 +699,14 @@ int main(int argc, char** argv) {
new_date = find_closest_entry(new_date, false, CONFIG.dir, diary_dir_size);
mv_valid = go_to(cal, aside, mktime(&new_date), &pad_pos);
break;
+ // Sync with CalDAV server
+ case 's':
+ get_date_str(&curs_date, dstr, sizeof dstr);
+ fprintf(stderr, "\nCursor date: %s\n\n", dstr);
+
+ caldav_sync(&curs_date, header);
+
+ break;
}
if (mv_valid) {
diff --git a/diary.h b/diary.h
@@ -1,5 +1,5 @@
-#ifndef DIARY
-#define DIARY
+#ifndef DIARY_H
+#define DIARY_H
#ifdef __MACH__
@@ -18,6 +18,7 @@
#include <ncurses.h>
#include <locale.h>
#include <langinfo.h>
+#include "caldav.h"
#define XDG_CONFIG_HOME_FALLBACK "~/.config"
#define CONFIG_FILE_PATH "diary/diary.cfg"