1 /*
2  * jabberd - Jabber Open Source Server
3  * Copyright (c) 2002 Jeremie Miller, Thomas Muldowney,
4  *                    Ryan Eatmon, Robert Norris
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307USA
19  */
20 
21 #include "sm.h"
22 
23 /** @file sm/mod_iq_vcard.c
24   * @brief user profiles (vcard)
25   * @author Robert Norris
26   * $Date: 2005/08/17 07:48:28 $
27   * $Revision: 1.25 $
28   */
29 
30 #define uri_VCARD    "vcard-temp"
31 static int ns_VCARD = 0;
32 
33 #define VCARD_MAX_FIELD_SIZE    (16384)
34 
35 typedef struct _mod_iq_vcard_st {
36     size_t vcard_max_field_size_default;
37     size_t vcard_max_field_size_avatar;
38 } *mod_iq_vcard_t;
39 
40 /**
41  * these are the vcard attributes that gabber supports. they're also
42  * all strings, and thus easy to automate. there might be more in
43  * regular use, we need to check that out. one day, when we're all
44  * using real foaf profiles, we'll have bigger things to worry about :)
45  *
46  * darco(2005-09-15): Added quite a few more fields, including those
47  * necessary for vCard avatar support.
48  */
49 
50 static const char *_iq_vcard_map[] = {
51     "FN",           "fn",
52     "N/FAMILY",     "n-family",
53     "N/GIVEN",      "n-given",
54     "N/MIDDLE",     "n-middle",
55     "N/PREFIX",     "n-prefix",
56     "N/SUFFIX",     "n-suffix",
57     "NICKNAME",     "nickname",
58     "PHOTO/TYPE",   "photo-type",
59     "PHOTO/BINVAL", "photo-binval",
60     "PHOTO/EXTVAL", "photo-extval",
61     "BDAY",         "bday",
62     "ADR/POBOX",    "adr-pobox",
63     "ADR/EXTADD",   "adr-extadd",
64     "ADR/STREET",   "adr-street",
65     "ADR/LOCALITY", "adr-locality",
66     "ADR/REGION",   "adr-region",
67     "ADR/PCODE",    "adr-pcode",
68     "ADR/CTRY",     "adr-country",
69     "TEL/NUMBER",   "tel",
70     "EMAIL/USERID", "email",
71     "JABBERID",     "jabberid",
72     "MAILER",       "mailer",
73     "TZ",           "tz",
74     "GEO/LAT",      "geo-lat",
75     "GEO/LON",      "geo-lon",
76     "TITLE",        "title",
77     "ROLE",         "role",
78     "LOGO/TYPE",    "logo-type",
79     "LOGO/BINVAL",  "logo-binval",
80     "LOGO/EXTVAL",  "logo-extval",
81     "AGENT/EXTVAL", "agent-extval",
82     "ORG/ORGNAME",  "org-orgname",
83     "ORG/ORGUNIT",  "org-orgunit",
84     "NOTE",         "note",
85     "REV",          "rev",
86     "SORT-STRING",  "sort-string",
87     "SOUND/PHONETIC","sound-phonetic",
88     "SOUND/BINVAL", "sound-binval",
89     "SOUND/EXTVAL", "sound-extval",
90     "UID",          "uid",
91     "URL",          "url",
92     "DESC",         "desc",
93     "KEY/TYPE",     "key-type",
94     "KEY/CRED",     "key-cred",
95     NULL,           NULL
96 };
97 
_iq_vcard_to_object(mod_instance_t mi,pkt_t pkt)98 static os_t _iq_vcard_to_object(mod_instance_t mi, pkt_t pkt) {
99     os_t os;
100     os_object_t o;
101     int i = 0, elem;
102     char ekey[10], *cdata;
103     const char *vkey, *dkey, *vskey;
104     size_t fieldsize;
105     mod_iq_vcard_t iq_vcard = (mod_iq_vcard_t) mi->mod->private;
106 
107     log_debug(ZONE, "building object from packet");
108 
109     os = os_new();
110     o = os_object_new(os);
111 
112     while(_iq_vcard_map[i] != NULL) {
113         vkey = _iq_vcard_map[i];
114         dkey = _iq_vcard_map[i + 1];
115 
116         i += 2;
117 
118         if( !strcmp(vkey, "PHOTO/BINVAL") ) {
119             fieldsize = iq_vcard->vcard_max_field_size_avatar;
120         } else {
121             fieldsize = iq_vcard->vcard_max_field_size_default;
122         }
123 
124         vskey = strchr(vkey, '/');
125         if(vskey == NULL) {
126             vskey = vkey;
127             elem = 2;
128         } else {
129             sprintf(ekey, "%.*s", (int) (vskey - vkey), vkey);
130             elem = nad_find_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 2), ekey, 1);
131             if(elem < 0)
132                 continue;
133             vskey++;
134         }
135 
136         elem = nad_find_elem(pkt->nad, elem, NAD_ENS(pkt->nad, 2), vskey, 1);
137         if(elem < 0 || NAD_CDATA_L(pkt->nad, elem) == 0)
138             continue;
139 
140         log_debug(ZONE, "extracted vcard key %s val '%.*s' for db key %s", vkey, NAD_CDATA_L(pkt->nad, elem), NAD_CDATA(pkt->nad, elem), dkey);
141 
142         cdata = malloc(fieldsize);
143         if(cdata) {
144             snprintf(cdata, fieldsize, "%.*s", NAD_CDATA_L(pkt->nad, elem), NAD_CDATA(pkt->nad, elem));
145             cdata[fieldsize-1] = '\0';
146             os_object_put(o, dkey, cdata, os_type_STRING);
147             free(cdata);
148         }
149     }
150 
151     return os;
152 }
153 
_iq_vcard_to_pkt(sm_t sm,os_t os)154 static pkt_t _iq_vcard_to_pkt(sm_t sm, os_t os) {
155     pkt_t pkt;
156     os_object_t o;
157     int i = 0, elem;
158     char ekey[10], *dval;
159     const char *vkey, *dkey, *vskey;
160 
161     log_debug(ZONE, "building packet from object");
162 
163     pkt = pkt_create(sm, "iq", "result", NULL, NULL);
164     nad_append_elem(pkt->nad, nad_add_namespace(pkt->nad, uri_VCARD, NULL), "vCard", 2);
165 
166     if(!os_iter_first(os))
167         return pkt;
168     o = os_iter_object(os);
169 
170     while(_iq_vcard_map[i] != NULL) {
171         vkey = _iq_vcard_map[i];
172         dkey = _iq_vcard_map[i + 1];
173 
174         i += 2;
175 
176         if(!os_object_get_str(os, o, dkey, &dval))
177             continue;
178 
179         vskey = strchr(vkey, '/');
180         if(vskey == NULL) {
181             vskey = vkey;
182             elem = 2;
183         } else {
184             sprintf(ekey, "%.*s", (int) (vskey - vkey), vkey);
185             elem = nad_find_elem(pkt->nad, 2, NAD_ENS(pkt->nad, 2), ekey, 1);
186             if(elem < 0)
187                 elem = nad_append_elem(pkt->nad, NAD_ENS(pkt->nad, 2), ekey, 3);
188             vskey++;
189         }
190 
191         log_debug(ZONE, "extracted dbkey %s val '%s' for vcard key %s", dkey, dval, vkey);
192 
193         if (!strcmp(dkey, "tel")) {
194             nad_append_elem(pkt->nad, NAD_ENS(pkt->nad, 2), "VOICE", pkt->nad->elems[elem].depth + 1);
195         }
196         nad_append_elem(pkt->nad, NAD_ENS(pkt->nad, 2), vskey, pkt->nad->elems[elem].depth + 1);
197         nad_append_cdata(pkt->nad, dval, strlen(dval), pkt->nad->elems[elem].depth + 2);
198     }
199 
200     return pkt;
201 }
202 
_iq_vcard_in_sess(mod_instance_t mi,sess_t sess,pkt_t pkt)203 static mod_ret_t _iq_vcard_in_sess(mod_instance_t mi, sess_t sess, pkt_t pkt) {
204     os_t os;
205     st_ret_t ret;
206     pkt_t result;
207 
208     /* only handle vcard sets and gets that aren't to anyone */
209     if(pkt->to != NULL || (pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_VCARD)
210         return mod_PASS;
211 
212     /* get */
213     if(pkt->type == pkt_IQ) {
214         if (sm_storage_rate_limit(sess->user->sm, jid_user(sess->jid)))
215             return -stanza_err_RESOURCE_CONSTRAINT;
216 
217         ret = storage_get(sess->user->sm->st, "vcard", jid_user(sess->jid), NULL, &os);
218         switch(ret) {
219             case st_FAILED:
220                 return -stanza_err_INTERNAL_SERVER_ERROR;
221 
222             case st_NOTIMPL:
223                 return -stanza_err_FEATURE_NOT_IMPLEMENTED;
224 
225             case st_NOTFOUND:
226                 nad_set_attr(pkt->nad, 1, -1, "type", "result", 6);
227                 nad_set_attr(pkt->nad, 1, -1, "to", NULL, 0);
228                 nad_set_attr(pkt->nad, 1, -1, "from", NULL, 0);
229 
230                 pkt_sess(pkt, sess);
231 
232                 return mod_HANDLED;
233 
234             case st_SUCCESS:
235                 result = _iq_vcard_to_pkt(sess->user->sm, os);
236                 os_free(os);
237 
238                 nad_set_attr(result->nad, 1, -1, "type", "result", 6);
239                 pkt_id(pkt, result);
240 
241                 pkt_sess(result, sess);
242 
243                 pkt_free(pkt);
244 
245                 return mod_HANDLED;
246         }
247 
248         /* we never get here */
249         pkt_free(pkt);
250         return mod_HANDLED;
251     }
252 
253     os = _iq_vcard_to_object(mi, pkt);
254 
255     if (sm_storage_rate_limit(sess->user->sm, jid_user(sess->jid)))
256         return -stanza_err_RESOURCE_CONSTRAINT;
257 
258     ret = storage_replace(sess->user->sm->st, "vcard", jid_user(sess->jid), NULL, os);
259     os_free(os);
260 
261     switch(ret) {
262         case st_FAILED:
263             return -stanza_err_INTERNAL_SERVER_ERROR;
264 
265         case st_NOTIMPL:
266             return -stanza_err_FEATURE_NOT_IMPLEMENTED;
267 
268         default:
269             result = pkt_create(sess->user->sm, "iq", "result", NULL, NULL);
270 
271             pkt_id(pkt, result);
272 
273             pkt_sess(result, sess);
274 
275             pkt_free(pkt);
276 
277             return mod_HANDLED;
278     }
279 
280     /* we never get here */
281     pkt_free(pkt);
282     return mod_HANDLED;
283 }
284 
285 /* for the special JID of your jabber server bare domain.
286  * You can have one for every virtual host
287  * you can populate it using your DBMS frontend
288  */
_iq_vcard_pkt_sm(mod_instance_t mi,pkt_t pkt)289 static mod_ret_t _iq_vcard_pkt_sm(mod_instance_t mi, pkt_t pkt) {
290     os_t os;
291     st_ret_t ret;
292     pkt_t result;
293 
294     /* only handle vcard sets and gets */
295     if((pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_VCARD)
296         return mod_PASS;
297 
298     /* error them if they're trying to do a set */
299     if(pkt->type == pkt_IQ_SET)
300         return -stanza_err_FORBIDDEN;
301 
302     /* a vcard for the server */
303     ret = storage_get(mi->sm->st, "vcard", pkt->to->domain, NULL, &os);
304     switch(ret) {
305         case st_FAILED:
306             return -stanza_err_INTERNAL_SERVER_ERROR;
307 
308         case st_NOTIMPL:
309             return -stanza_err_FEATURE_NOT_IMPLEMENTED;
310 
311         case st_NOTFOUND:
312             return -stanza_err_ITEM_NOT_FOUND;
313 
314         case st_SUCCESS:
315             result = _iq_vcard_to_pkt(mi->sm, os);
316             os_free(os);
317 
318             result->to = jid_dup(pkt->from);
319             result->from = jid_dup(pkt->to);
320 
321             nad_set_attr(result->nad, 1, -1, "to", jid_full(result->to), 0);
322             nad_set_attr(result->nad, 1, -1, "from", jid_full(result->from), 0);
323 
324             pkt_id(pkt, result);
325 
326             pkt_router(result);
327 
328             pkt_free(pkt);
329 
330             return mod_HANDLED;
331     }
332 
333     /* we never get here */
334     pkt_free(pkt);
335     return mod_HANDLED;
336 }
337 
_iq_vcard_pkt_user(mod_instance_t mi,user_t user,pkt_t pkt)338 static mod_ret_t _iq_vcard_pkt_user(mod_instance_t mi, user_t user, pkt_t pkt) {
339     os_t os;
340     st_ret_t ret;
341     pkt_t result;
342 
343     /* only handle vcard sets and gets, without resource */
344     if((pkt->type != pkt_IQ && pkt->type != pkt_IQ_SET) || pkt->ns != ns_VCARD || pkt->to->resource[0] !='\0')
345         return mod_PASS;
346 
347     /* error them if they're trying to do a set */
348     if(pkt->type == pkt_IQ_SET)
349         return -stanza_err_FORBIDDEN;
350 
351     if (sm_storage_rate_limit(user->sm, jid_user(pkt->from)))
352         return -stanza_err_RESOURCE_CONSTRAINT;
353 
354     ret = storage_get(user->sm->st, "vcard", jid_user(user->jid), NULL, &os);
355     switch(ret) {
356         case st_FAILED:
357             return -stanza_err_INTERNAL_SERVER_ERROR;
358 
359         case st_NOTIMPL:
360             return -stanza_err_FEATURE_NOT_IMPLEMENTED;
361 
362         case st_NOTFOUND:
363             return -stanza_err_SERVICE_UNAVAILABLE;
364 
365         case st_SUCCESS:
366             result = _iq_vcard_to_pkt(user->sm, os);
367             os_free(os);
368 
369             result->to = jid_dup(pkt->from);
370             result->from = jid_dup(pkt->to);
371 
372             nad_set_attr(result->nad, 1, -1, "to", jid_full(result->to), 0);
373             nad_set_attr(result->nad, 1, -1, "from", jid_full(result->from), 0);
374 
375             pkt_id(pkt, result);
376 
377             pkt_router(result);
378 
379             pkt_free(pkt);
380 
381             return mod_HANDLED;
382     }
383 
384     /* we never get here */
385     pkt_free(pkt);
386     return mod_HANDLED;
387 }
388 
_iq_vcard_user_delete(mod_instance_t mi,jid_t jid)389 static void _iq_vcard_user_delete(mod_instance_t mi, jid_t jid) {
390     log_debug(ZONE, "deleting vcard for %s", jid_user(jid));
391 
392     storage_delete(mi->sm->st, "vcard", jid_user(jid), NULL);
393 }
394 
_iq_vcard_free(module_t mod)395 static void _iq_vcard_free(module_t mod) {
396     sm_unregister_ns(mod->mm->sm, uri_VCARD);
397     feature_unregister(mod->mm->sm, uri_VCARD);
398     free(mod->private);
399 }
400 
module_init(mod_instance_t mi,const char * arg)401 DLLEXPORT int module_init(mod_instance_t mi, const char *arg) {
402     module_t mod = mi->mod;
403     mod_iq_vcard_t iq_vcard;
404 
405     if(mod->init) return 0;
406 
407     mod->pkt_sm = _iq_vcard_pkt_sm;
408     mod->in_sess = _iq_vcard_in_sess;
409     mod->pkt_user = _iq_vcard_pkt_user;
410     mod->user_delete = _iq_vcard_user_delete;
411     mod->free = _iq_vcard_free;
412 
413     ns_VCARD = sm_register_ns(mod->mm->sm, uri_VCARD);
414     feature_register(mod->mm->sm, uri_VCARD);
415 
416     iq_vcard = (mod_iq_vcard_t) calloc(1, sizeof(struct _mod_iq_vcard_st));
417     iq_vcard->vcard_max_field_size_default = j_atoi(config_get_one(mod->mm->sm->config, "user.vcard.max-field-size.default", 0), VCARD_MAX_FIELD_SIZE);
418     iq_vcard->vcard_max_field_size_avatar = j_atoi(config_get_one(mod->mm->sm->config, "user.vcard.max-field-size.avatar", 0), VCARD_MAX_FIELD_SIZE);
419     mod->private = iq_vcard;
420 
421     return 0;
422 }
423