Demonstration release of the principles underpinning krsd.
[krsd] / src / handler / webfinger.c
1 /*
2  * rs-serve - (c) 2013 Niklas E. Cathor
3  *
4  * This program is distributed in the hope that it will be useful,
5  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7  * GNU Affero General Public License for more details.
8  *
9  * You should have received a copy of the GNU Affero General Public License
10  * along with this program. If not, see <http://www.gnu.org/licenses/>.
11  */
12
13 #include "rs-serve.h"
14
15 #define ADD_CORS_HEADERS(req)                                           \
16   ADD_RESP_HEADER(req, "Access-Control-Allow-Origin", RS_ALLOW_ORIGIN); \
17   ADD_RESP_HEADER(req, "Access-Control-Allow-Headers", RS_ALLOW_HEADERS); \
18   ADD_RESP_HEADER(req, "Access-Control-Allow-Methods", "GET")
19
20 char *lrdd_template = NULL;
21 char *storage_uri_format = NULL;
22 size_t storage_uri_format_len = 0;
23
24 void init_webfinger() {
25   lrdd_template = malloc(strlen(RS_SCHEME) + strlen(RS_HOSTNAME) + 40 + 1);
26   if(lrdd_template == NULL) {
27     log_error("malloc() failed: %s", strerror(errno));
28     exit(EXIT_FAILURE);
29   }
30   sprintf(lrdd_template, "%s://%s/.well-known/webfinger?resource={uri}",
31           RS_SCHEME, RS_HOSTNAME);
32   storage_uri_format_len = strlen(RS_SCHEME) + strlen(RS_HOSTNAME) + 13;
33   storage_uri_format = malloc(storage_uri_format_len + 2 + 1);
34   sprintf(storage_uri_format, "%s://%s/storage/%%s/%%s", RS_SCHEME, RS_HOSTNAME);
35 }
36
37 static size_t json_writer(char *buf, size_t count, void *arg) {
38   return evbuffer_add((struct evbuffer*)arg, buf, count);
39 }
40
41 #if 0
42 static int process_resource(const char *resource, char **storage_uri, char **auth_uri) {
43 #else
44 static int process_resource(const char *resource, char **storage_uri) {
45 #endif
46
47   size_t resource_len = strlen(resource);
48   char *resource_buf = malloc(resource_len + 1);
49   if(resource_buf == NULL) {
50     log_error("malloc() failed: %s", strerror(errno));
51     return -2;
52   }
53
54   memset(resource_buf, 0, resource_len + 1);
55
56   errno = 0;
57   if(evhtp_unescape_string((unsigned char**)&resource_buf, (unsigned char*)resource, resource_len) != 0) {
58     log_error("unescaping string failed (errno: %s)", strerror(errno));
59     return -2;
60   }
61
62   // check scheme
63   if(strncmp(resource_buf, "acct:", 5) == 0) {
64
65     log_debug("scheme matches");
66
67     // extract local-part
68     char *local_part = resource_buf + 5;
69     char *ptr;
70     for(ptr = local_part; *ptr && *ptr != '@'; ptr++);
71     if(*ptr == '@') {
72       // '@' found, host starts (otherwise invalid)
73       *ptr = 0;
74       log_debug("local-part: %s", local_part);
75       char *hostname = ptr + 1;
76       log_debug("hostname: %s", hostname);
77       // check hostname
78       if(strcmp(hostname, RS_HOSTNAME) == 0) {
79 #if 0
80         uid_t uid = user_get_uid(local_part);
81         log_debug("got uid: %d (RS_MIN_UID: %d, allowed: %d)", uid,
82                   RS_MIN_UID, UID_ALLOWED(uid));
83         // check if user is valid
84         if(UID_ALLOWED(uid)) {
85 #endif
86           *storage_uri = malloc(storage_uri_format_len + strlen(hostname) + strlen(local_part) + 1);
87           sprintf(*storage_uri, storage_uri_format, hostname, local_part);
88 #if 0
89           *auth_uri = malloc(RS_AUTH_URI_LEN + strlen(local_part) + 1);
90           sprintf(*auth_uri, RS_AUTH_URI, local_part);
91 #endif
92
93           free(resource_buf);
94           return 0; // success!
95 #if 0
96         }
97 #endif
98       }
99     }
100
101     free(resource_buf);
102   }
103   return -1;
104 }
105
106 void reject_webfinger(evhtp_request_t *req, void *arg) {
107   evhtp_send_reply(req, EVHTP_RES_NOTIMPL);
108 }
109
110 void handle_webfinger(evhtp_request_t *req, void *arg) {
111   struct json *json;
112   switch(evhtp_request_get_method(req)) {
113   case htp_method_GET:
114     ADD_CORS_HEADERS(req);
115     ADD_RESP_HEADER(req, "Content-Type", "application/json");
116     
117     json = new_json(json_writer, req->buffer_out);
118     json_start_object(json);
119
120     const char *resource;
121     if(req->uri->query && (resource = evhtp_kv_find(req->uri->query, "resource"))) {
122 #if 0
123       char *storage_uri = NULL, *auth_uri = NULL;
124 #else
125       char *storage_uri = NULL;
126 #endif
127 #if 0
128       if(process_resource(resource, &storage_uri, &auth_uri) != 0) {
129 #else
130       if(process_resource(resource, &storage_uri) != 0) {
131 #endif
132         req->status = EVHTP_RES_NOTFOUND;
133       } else {
134         json_write_key_val(json, "subject", resource);
135         json_write_key(json, "links");
136         json_start_array(json);
137         json_start_object(json);
138         json_write_key_val(json, "rel", "remotestorage");
139         json_write_key_val(json, "type", RS_STORAGE_API);
140         json_write_key_val(json, "href", storage_uri);
141         free(storage_uri);
142         json_write_key(json, "properties");
143         json_start_object(json);
144 #if 0
145         json_write_key_val(json, RS_AUTH_METHOD, auth_uri);
146         // (begin legacy support [<= remotestorage-00])
147         json_write_key_val(json, "auth-method", RS_AUTH_METHOD);
148         json_write_key_val(json, "auth-endpoint", auth_uri);
149         // (end legacy support)
150         free(auth_uri);
151 #endif
152         json_end_object(json);  // properties
153         json_end_object(json); // link rel=remotestorage]
154         json_end_array(json); // links
155         req->status = EVHTP_RES_OK;
156       }
157     } else {
158       json_write_key_val(json, "subject", RS_HOSTNAME);
159       json_write_key(json, "links");
160       json_start_array(json);
161       json_start_object(json);
162       json_write_key_val(json, "rel", "lrdd");
163       json_write_key_val(json, "template", lrdd_template);
164       json_end_object(json);
165       json_end_array(json);
166       req->status = EVHTP_RES_OK;
167     }
168
169     json_end_object(json);
170     free_json(json);
171
172     break;
173   case htp_method_OPTIONS:
174     ADD_CORS_HEADERS(req);
175     req->status = EVHTP_RES_NOCONTENT;
176     break;
177   default:
178     req->status = EVHTP_RES_METHNALLOWED;
179   }
180
181   evhtp_send_reply(req, req->status);
182 }