diary

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

commit 29424c70a80767831c2ca3e2996c4df2b62d601d
parent f7ca86d4c2b5bbbad94d6ab8db7c46c31f4603ca
Author: Andreas Gruhler <agruhl@gmx.ch>
Date:   Sat, 15 May 2021 01:12:32 +0200

google oauth socket

Diffstat:
MMakefile | 6++++--
MREADME.md | 13++++++++++---
Acaldav.c | 208+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acaldav.h | 37+++++++++++++++++++++++++++++++++++++
Mdiary.c | 12++++++++++--
Mdiary.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"