upgrade obexd to 0.47
[profile/ivi/obexd.git] / plugins / irmc.c
1 /*
2  *
3  *  OBEX IrMC Sync Server
4  *
5  *  Copyright (C) 2010  Marcel Mol <marcel@mesa.nl>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  *
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <glib.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <arpa/inet.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <inttypes.h>
38
39 #include "obexd.h"
40 #include "plugin.h"
41 #include "log.h"
42 #include "obex.h"
43 #include "service.h"
44 #include "phonebook.h"
45 #include "mimetype.h"
46 #include "filesystem.h"
47 #include "manager.h"
48
49 #define IRMC_CHANNEL    14
50
51 #define IRMC_RECORD "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>        \
52 <record>                                                                \
53   <attribute id=\"0x0001\">                                             \
54     <sequence>                                                          \
55       <uuid value=\"0x1104\"/>                                          \
56     </sequence>                                                         \
57   </attribute>                                                          \
58                                                                         \
59   <attribute id=\"0x0004\">                                             \
60     <sequence>                                                          \
61       <sequence>                                                        \
62         <uuid value=\"0x0100\"/>                                        \
63       </sequence>                                                       \
64       <sequence>                                                        \
65         <uuid value=\"0x0003\"/>                                        \
66         <uint8 value=\"%u\" name=\"channel\"/>                          \
67       </sequence>                                                       \
68       <sequence>                                                        \
69         <uuid value=\"0x0008\"/>                                        \
70       </sequence>                                                       \
71     </sequence>                                                         \
72   </attribute>                                                          \
73                                                                         \
74   <attribute id=\"0x0009\">                                             \
75     <sequence>                                                          \
76       <sequence>                                                        \
77         <uuid value=\"0x1104\"/>                                        \
78         <uint16 value=\"0x0100\" name=\"version\"/>                     \
79       </sequence>                                                       \
80     </sequence>                                                         \
81   </attribute>                                                          \
82                                                                         \
83   <attribute id=\"0x0100\">                                             \
84     <text value=\"%s\" name=\"name\"/>                                  \
85   </attribute>                                                          \
86                                                                         \
87   <attribute id=\"0x0301\">                                             \
88     <sequence>                                                          \
89       <uint8 value=\"0x01\"/>                                           \
90     </sequence>                                                         \
91   </attribute>                                                          \
92 </record>"
93
94
95 struct aparam_header {
96         uint8_t tag;
97         uint8_t len;
98         uint8_t val[0];
99 } __attribute__ ((packed));
100
101 #define DID_LEN 18
102
103 struct irmc_session {
104         struct obex_session *os;
105         struct apparam_field *params;
106         uint16_t entries;
107         GString *buffer;
108         char sn[DID_LEN];
109         char did[DID_LEN];
110         char manu[DID_LEN];
111         char model[DID_LEN];
112         void *request;
113 };
114
115 #define IRMC_TARGET_SIZE 9
116
117 static const guint8 IRMC_TARGET[IRMC_TARGET_SIZE] = {
118                         0x49, 0x52, 0x4d, 0x43,  0x2d, 0x53, 0x59, 0x4e, 0x43 };
119
120 /* FIXME:
121  * the IrMC specs state the first vcard should be the owner
122  * vcard. As there is no simple way to collect ownerdetails
123  * just create an empty vcard (which is allowed according to the
124  * specs).
125  */
126 static const char *owner_vcard =
127                 "BEGIN:VCARD\r\n"
128                 "VERSION:2.1\r\n"
129                 "N:\r\n"
130                 "TEL:\r\n"
131                 "X-IRMX-LUID:0\r\n"
132                 "END:VCARD\r\n";
133
134 static void phonebook_size_result(const char *buffer, size_t bufsize,
135                                         int vcards, int missed,
136                                         gboolean lastpart, void *user_data)
137 {
138         struct irmc_session *irmc = user_data;
139
140         DBG("vcards %d", vcards);
141
142         irmc->params->maxlistcount = vcards;
143
144         if (irmc->request) {
145                 phonebook_req_finalize(irmc->request);
146                 irmc->request = NULL;
147         }
148 }
149
150 static void query_result(const char *buffer, size_t bufsize, int vcards,
151                                 int missed, gboolean lastpart, void *user_data)
152 {
153         struct irmc_session *irmc = user_data;
154         const char *s, *t;
155
156         DBG("bufsize %zu vcards %d missed %d", bufsize, vcards, missed);
157
158         if (irmc->request) {
159                 phonebook_req_finalize(irmc->request);
160                 irmc->request = NULL;
161         }
162
163         /* first add a 'owner' vcard */
164         if (!irmc->buffer)
165                 irmc->buffer = g_string_new(owner_vcard);
166         else
167                 irmc->buffer = g_string_append(irmc->buffer, owner_vcard);
168
169         if (buffer == NULL)
170                 goto done;
171
172         /* loop around buffer and add X-IRMC-LUID attribs */
173         s = buffer;
174         while ((t = strstr(s, "UID:")) != NULL) {
175                 /* add up to UID: into buffer */
176                 irmc->buffer = g_string_append_len(irmc->buffer, s, t-s);
177                 /*
178                  * add UID: line into buffer
179                  * Not sure if UID is still needed if X-IRMC-LUID is there
180                  */
181                 s = t;
182                 t = strstr(s, "\r\n");
183                 t += 2;
184                 irmc->buffer = g_string_append_len(irmc->buffer, s, t-s);
185                 /* add X-IRMC-LUID with same number as UID */
186                 irmc->buffer = g_string_append_len(irmc->buffer,
187                                                         "X-IRMC-LUID:", 12);
188                 s += 4; /* point to uid number */
189                 irmc->buffer = g_string_append_len(irmc->buffer, s, t-s);
190                 s = t;
191         }
192         /* add remaining bit of buffer */
193         irmc->buffer = g_string_append(irmc->buffer, s);
194
195 done:
196         obex_object_set_io_flags(irmc, G_IO_IN, 0);
197 }
198
199 static void *irmc_connect(struct obex_session *os, int *err)
200 {
201         struct irmc_session *irmc;
202         struct apparam_field *param;
203         int ret;
204
205         DBG("");
206
207         manager_register_session(os);
208
209         irmc = g_new0(struct irmc_session, 1);
210         irmc->os = os;
211
212         /* FIXME:
213          * Ideally get capabilities info here and use that to define
214          * IrMC DID and SN etc parameters.
215          * For now lets used hostname and some 'random' value
216          */
217         gethostname(irmc->did, DID_LEN);
218         strncpy(irmc->sn, "12345", sizeof(irmc->sn) - 1);
219         strncpy(irmc->manu, "obex", sizeof(irmc->manu) - 1);
220         strncpy(irmc->model, "mymodel", sizeof(irmc->model) - 1);
221
222         /* We need to know the number of contact/cal/nt entries
223          * somewhere so why not do it now.
224          */
225         param = g_new0(struct apparam_field, 1);
226         param->maxlistcount = 0; /* to count the number of vcards... */
227         param->filter = 0x200085; /* UID TEL N VERSION */
228         irmc->params = param;
229         irmc->request = phonebook_pull("telecom/pb.vcf", irmc->params,
230                                         phonebook_size_result, irmc, err);
231         ret = phonebook_pull_read(irmc->request);
232         if (err)
233                 *err = ret;
234
235         return irmc;
236 }
237
238 static int irmc_get(struct obex_session *os, void *user_data)
239 {
240         struct irmc_session *irmc = user_data;
241         const char *type = obex_get_type(os);
242         const char *name = obex_get_name(os);
243         char *path;
244         int ret;
245
246         DBG("name %s type %s irmc %p", name, type ? type : "NA", irmc);
247
248         path = g_strdup(name);
249
250         ret = obex_get_stream_start(os, path);
251
252         g_free(path);
253
254         return ret;
255 }
256
257 static void irmc_disconnect(struct obex_session *os, void *user_data)
258 {
259         struct irmc_session *irmc = user_data;
260
261         DBG("");
262
263         manager_unregister_session(os);
264
265         if (irmc->params) {
266                 if (irmc->params->searchval)
267                         g_free(irmc->params->searchval);
268                 g_free(irmc->params);
269         }
270
271         if (irmc->buffer)
272                 g_string_free(irmc->buffer, TRUE);
273
274         g_free(irmc);
275 }
276
277 static int irmc_chkput(struct obex_session *os, void *user_data)
278 {
279         DBG("");
280         /* Reject all PUTs */
281         return -EBADR;
282 }
283
284 static void *irmc_open_devinfo(struct irmc_session *irmc, int *err)
285 {
286         if (!irmc->buffer)
287                 irmc->buffer = g_string_new("");
288
289         g_string_append_printf(irmc->buffer,
290                                 "MANU:%s\r\n"
291                                 "MOD:%s\r\n"
292                                 "SN:%s\r\n"
293                                 "IRMC-VERSION:1.1\r\n"
294                                 "PB-TYPE-TX:VCARD2.1\r\n"
295                                 "PB-TYPE-RX:NONE\r\n"
296                                 "CAL-TYPE-TX:NONE\r\n"
297                                 "CAL-TYPE-RX:NONE\r\n"
298                                 "MSG-TYPE-TX:NONE\r\n"
299                                 "MSG-TYPE-RX:NONE\r\n"
300                                 "NOTE-TYPE-TX:NONE\r\n"
301                                 "NOTE-TYPE-RX:NONE\r\n",
302                                 irmc->manu, irmc->model, irmc->sn);
303
304         return irmc;
305 }
306
307 static void *irmc_open_pb(const char *name, struct irmc_session *irmc,
308                                                                 int *err)
309 {
310         GString *mybuf;
311         int ret;
312
313         if (!g_strcmp0(name, ".vcf")) {
314                 /* how can we tell if the vcard count call already finished? */
315                 irmc->request = phonebook_pull("telecom/pb.vcf", irmc->params,
316                                                 query_result, irmc, &ret);
317                 if (ret < 0) {
318                         DBG("phonebook_pull failed...");
319                         goto fail;
320                 }
321
322                 ret = phonebook_pull_read(irmc->request);
323                 if (ret < 0) {
324                         DBG("phonebook_pull_read failed...");
325                         goto fail;
326                 }
327
328                 return irmc;
329         }
330
331         if (!g_strcmp0(name, "/info.log")) {
332                 mybuf = g_string_new("");
333                 g_string_printf(mybuf, "Total-Records:%d\r\n"
334                                 "Maximum-Records:%d\r\n"
335                                 "IEL:2\r\n"
336                                 "DID:%s\r\n",
337                                 irmc->params->maxlistcount,
338                                 irmc->params->maxlistcount, irmc->did);
339         } else if (!strncmp(name, "/luid/", 6)) {
340                 name += 6;
341                 if (!g_strcmp0(name, "cc.log")) {
342                         mybuf = g_string_new("");
343                         g_string_printf(mybuf, "%d\r\n",
344                                                 irmc->params->maxlistcount);
345                 } else {
346                         int l = strlen(name);
347                         /* FIXME:
348                          * Reply the same to any *.log so we hopefully force a
349                          * full phonebook dump.
350                          * Is IEL:2 ok?
351                          */
352                         if (l > 4 && !g_strcmp0(name + l - 4, ".log")) {
353                                 DBG("changelog request, force whole book");
354                                 mybuf = g_string_new("");
355                                 g_string_printf(mybuf, "SN:%s\r\n"
356                                                         "DID:%s\r\n"
357                                                         "Total-Records:%d\r\n"
358                                                         "Maximum-Records:%d\r\n"
359                                                         "*\r\n",
360                                                 irmc->sn, irmc->did,
361                                                 irmc->params->maxlistcount,
362                                                 irmc->params->maxlistcount);
363                         } else {
364                                 ret = -EBADR;
365                                 goto fail;
366                         }
367                 }
368         } else {
369                 ret = -EBADR;
370                 goto fail;
371         }
372
373         if (!irmc->buffer)
374                 irmc->buffer = mybuf;
375         else {
376                 irmc->buffer = g_string_append(irmc->buffer, mybuf->str);
377                 g_string_free(mybuf, TRUE);
378         }
379
380         return irmc;
381
382 fail:
383         if (err)
384                 *err = ret;
385
386         return NULL;
387 }
388
389 static void *irmc_open_cal(const char *name, struct irmc_session *irmc,
390                                                                 int *err)
391 {
392         /* no suport yet. Just return an empty buffer. cal.vcs */
393         DBG("unsupported, returning empty buffer");
394
395         if (!irmc->buffer)
396                 irmc->buffer = g_string_new("");
397
398         return irmc;
399 }
400
401 static void *irmc_open_nt(const char *name, struct irmc_session *irmc,
402                                                                 int *err)
403 {
404         /* no suport yet. Just return an empty buffer. nt.vnt */
405         DBG("unsupported, returning empty buffer");
406
407         if (!irmc->buffer)
408                 irmc->buffer = g_string_new("");
409
410         return irmc;
411 }
412
413 static void *irmc_open(const char *name, int oflag, mode_t mode, void *context,
414                                                         size_t *size, int *err)
415 {
416         struct irmc_session *irmc = context;
417         int ret = 0;
418         const char *p;
419
420         DBG("name %s context %p", name, context);
421
422         if (oflag != O_RDONLY) {
423                 ret = -EPERM;
424                 goto fail;
425         }
426         if (name == NULL || strncmp(name, "telecom/", 8) != 0) {
427                 ret = -EBADR;
428                 goto fail;
429         }
430
431         p = name + 8;
432         if (!g_strcmp0(p, "devinfo.txt"))
433                 return irmc_open_devinfo(irmc, err);
434         else if (!strncmp(p, "pb", 2))
435                 return irmc_open_pb(p+2, irmc, err);
436         else if (!strncmp(p, "cal", 3))
437                 return irmc_open_cal(p+3, irmc, err);
438         else if (!strncmp(p, "nt", 2))
439                 return irmc_open_nt(p+2, irmc, err);
440
441 fail:
442         if (err)
443                 *err = ret;
444
445         return NULL;
446 }
447
448 static int irmc_close(void *object)
449 {
450         struct irmc_session *irmc = object;
451
452         DBG("");
453
454         if (irmc->buffer) {
455                 g_string_free(irmc->buffer, TRUE);
456                 irmc->buffer = NULL;
457         }
458
459         if (irmc->request) {
460                 phonebook_req_finalize(irmc->request);
461                 irmc->request = NULL;
462         }
463
464         return 0;
465 }
466
467 static ssize_t irmc_read(void *object, void *buf, size_t count)
468 {
469         struct irmc_session *irmc = object;
470         int len;
471
472         DBG("buffer %p count %zu", irmc->buffer, count);
473         if (!irmc->buffer)
474                 return -EAGAIN;
475
476         len = string_read(irmc->buffer, buf, count);
477         DBG("returning %d bytes", len);
478         return len;
479 }
480
481 static struct obex_mime_type_driver irmc_driver = {
482         .target = IRMC_TARGET,
483         .target_size = IRMC_TARGET_SIZE,
484         .open = irmc_open,
485         .close = irmc_close,
486         .read = irmc_read,
487 };
488
489 static struct obex_service_driver irmc = {
490         .name = "IRMC Sync server",
491         .service = OBEX_IRMC,
492         .channel = IRMC_CHANNEL,
493         .secure = TRUE,
494         .record = IRMC_RECORD,
495         .target = IRMC_TARGET,
496         .target_size = IRMC_TARGET_SIZE,
497         .connect = irmc_connect,
498         .get = irmc_get,
499         .disconnect = irmc_disconnect,
500         .chkput = irmc_chkput
501 };
502
503 static int irmc_init(void)
504 {
505         int err;
506
507         DBG("");
508         err = phonebook_init();
509         if (err < 0)
510                 return err;
511
512         err = obex_mime_type_driver_register(&irmc_driver);
513         if (err < 0)
514                 goto fail_mime_irmc;
515
516         err = obex_service_driver_register(&irmc);
517         if (err < 0)
518                 goto fail_irmc_reg;
519
520         return 0;
521
522 fail_irmc_reg:
523         obex_mime_type_driver_unregister(&irmc_driver);
524 fail_mime_irmc:
525         phonebook_exit();
526
527         return err;
528 }
529
530 static void irmc_exit(void)
531 {
532         DBG("");
533         obex_service_driver_unregister(&irmc);
534         obex_mime_type_driver_unregister(&irmc_driver);
535         phonebook_exit();
536 }
537
538 OBEX_PLUGIN_DEFINE(irmc, irmc_init, irmc_exit)