6f1efad76aab90642d86ceddd6cd0e8e5b821a56
[citadel.git] / citadel / serv_vcard.c
1 /*
2  * $Id$
3  * 
4  * A server-side module for Citadel which supports address book information
5  * using the standard vCard format.
6  *
7  */
8
9 #define ADDRESS_BOOK_ROOM       "Global Address Book"
10
11 #include "sysdep.h"
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <stdio.h>
15 #include <fcntl.h>
16 #include <signal.h>
17 #include <pwd.h>
18 #include <errno.h>
19 #include <sys/types.h>
20 #include <sys/time.h>
21 #include <sys/wait.h>
22 #include <string.h>
23 #include <limits.h>
24 #include "citadel.h"
25 #include "server.h"
26 #include <time.h>
27 #include "sysdep_decls.h"
28 #include "citserver.h"
29 #include "support.h"
30 #include "config.h"
31 #include "control.h"
32 #include "dynloader.h"
33 #include "room_ops.h"
34 #include "user_ops.h"
35 #include "policy.h"
36 #include "database.h"
37 #include "msgbase.h"
38 #include "tools.h"
39 #include "vcard.h"
40
41 struct vcard_internal_info {
42         long msgnum;
43 };
44
45 /* Message number symbol used internally by these functions */
46 unsigned long SYM_VCARD;
47 #define VC ((struct vcard_internal_info *)CtdlGetUserData(SYM_VCARD))
48
49
50 /*
51  * This handler detects whether the user is attempting to save a new
52  * vCard as part of his/her personal configuration, and handles the replace
53  * function accordingly (delete the user's existing vCard in the config room
54  * and in the global address book).
55  */
56 int vcard_upload_beforesave(struct CtdlMessage *msg) {
57         char *ptr;
58         int linelen;
59         char config_rm[ROOMNAMELEN];
60         char buf[256];
61
62
63         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
64         TRACE;
65
66         /* If this isn't the configuration room, or if this isn't a MIME
67          * message, don't bother.  (Check for NULL room first, otherwise
68          * some messages will cause it to crash!!)
69          */
70         if (msg->cm_fields['O'] == NULL) return(0);
71         TRACE;
72         if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
73         TRACE;
74         if (msg->cm_format_type != 4) return(0);
75         TRACE;
76
77         ptr = msg->cm_fields['M'];
78         if (ptr == NULL) return(0);
79         TRACE;
80         while (ptr != NULL) {
81         
82                 linelen = strcspn(ptr, "\n");
83                 if (linelen == 0) return(0);    /* end of headers */    
84                 
85                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
86                         /* Bingo!  The user is uploading a new vCard, so
87                          * delete the old one.
88                          */
89
90                         /* Delete the user's old vCard.  This would probably
91                          * get taken care of by the replication check, but we
92                          * want to make sure there is absolutely only one
93                          * vCard in the user's config room at all times.
94                          * 
95                          * FIXME ... this needs to be tweaked to allow an admin
96                          * to make changes to another user's vCard instead of
97                          * assuming that it's always the user saving his own.
98                          */
99                         TRACE;
100                         MailboxName(config_rm, &CC->usersupp, USERCONFIGROOM);
101                         TRACE;
102                         CtdlDeleteMessages(config_rm, 0L, "text/x-vcard");
103                         TRACE;
104
105                         /* Set the Extended-ID to a standardized one so the
106                          * replication always works correctly
107                          */
108                         if (msg->cm_fields['E'] != NULL)
109                                 phree(msg->cm_fields['E']);
110                         TRACE;
111
112                         sprintf(buf,
113                                 "Citadel vCard: personal card for %s at %s",
114                                 msg->cm_fields['A'], NODENAME);
115                         msg->cm_fields['E'] = strdoop(buf);
116                         TRACE;
117
118                         /* Now allow the save to complete. */
119                         TRACE;
120                         return(0);
121                 }
122                 TRACE;
123
124                 ptr = strchr((char *)ptr, '\n');
125                 TRACE;
126                 if (ptr != NULL) ++ptr;
127         }
128         TRACE;
129
130         return(0);
131 }
132
133
134
135 /*
136  * This handler detects whether the user is attempting to save a new
137  * vCard as part of his/her personal configuration, and handles the replace
138  * function accordingly (copy the vCard from the config room to the global
139  * address book).
140  */
141 int vcard_upload_aftersave(struct CtdlMessage *msg) {
142         char *ptr;
143         int linelen;
144         long I;
145
146
147         if (!CC->logged_in) return(0);  /* Only do this if logged in. */
148
149         /* If this isn't the configuration room, or if this isn't a MIME
150          * message, don't bother.
151          */
152         if (msg->cm_fields['O'] == NULL) return(0);
153         if (strcasecmp(msg->cm_fields['O'], USERCONFIGROOM)) return(0);
154         if (msg->cm_format_type != 4) return(0);
155
156         ptr = msg->cm_fields['M'];
157         if (ptr == NULL) return(0);
158         while (ptr != NULL) {
159         
160                 linelen = strcspn(ptr, "\n");
161                 if (linelen == 0) return(0);    /* end of headers */    
162                 
163                 if (!strncasecmp(ptr, "Content-type: text/x-vcard", 26)) {
164                         /* Bingo!  The user is uploading a new vCard, so
165                          * copy it to the Global Address Book room.
166                          */
167
168                         TRACE;
169                         I = atol(msg->cm_fields['I']);
170                         TRACE;
171                         if (I < 0L) return(0);
172
173                         CtdlSaveMsgPointerInRoom(ADDRESS_BOOK_ROOM, I,
174                                 (SM_VERIFY_GOODNESS | SM_DO_REPL_CHECK) );
175
176                         return(0);
177                 }
178
179                 ptr = strchr((char *)ptr, '\n');
180                 if (ptr != NULL) ++ptr;
181         }
182
183         return(0);
184 }
185
186
187
188 /*
189  * back end function used for callbacks
190  */
191 void vcard_gu_backend(long msgnum, void *userdata) {
192         VC->msgnum = msgnum;
193 }
194
195
196 /*
197  * If this user has a vcard on disk, read it into memory, otherwise allocate
198  * and return an empty vCard.
199  */
200 struct vCard *vcard_get_user(struct usersupp *u) {
201         char hold_rm[ROOMNAMELEN];
202         char config_rm[ROOMNAMELEN];
203         struct CtdlMessage *msg;
204         struct vCard *v;
205
206         strcpy(hold_rm, CC->quickroom.QRname);  /* save current room */
207         MailboxName(config_rm, u, USERCONFIGROOM);
208
209         if (getroom(&CC->quickroom, config_rm) != 0) {
210                 getroom(&CC->quickroom, hold_rm);
211                 return vcard_new();
212         }
213
214         /* We want the last (and probably only) vcard in this room */
215         VC->msgnum = (-1);
216         CtdlForEachMessage(MSGS_LAST, 1, (-127), "text/x-vcard",
217                 NULL, vcard_gu_backend, NULL);
218         getroom(&CC->quickroom, hold_rm);       /* return to saved room */
219
220         if (VC->msgnum < 0L) return vcard_new();
221
222         msg = CtdlFetchMessage(VC->msgnum);
223         if (msg == NULL) return vcard_new();
224
225         v = vcard_load(msg->cm_fields['M']);
226         CtdlFreeMessage(msg);
227         return v;
228 }
229
230
231 /*
232  * Store this user's vCard in the appropriate place
233  */
234 /*
235  * Write our config to disk
236  */
237 void vcard_write_user(struct usersupp *u, struct vCard *v) {
238         char temp[PATH_MAX];
239         FILE *fp;
240         char *ser;
241
242         strcpy(temp, tmpnam(NULL));
243         ser = vcard_serialize(v);
244
245         fp = fopen(temp, "w");
246         if (fp == NULL) return;
247         if (ser == NULL) {
248                 fprintf(fp, "begin:vcard\r\nend:vcard\r\n");
249         } else {
250                 fwrite(ser, strlen(ser), 1, fp);
251                 phree(ser);
252         }
253         fclose(fp);
254
255         /* This handy API function does all the work for us.
256          * NOTE: normally we would want to set that last argument to 1, to
257          * force the system to delete the user's old vCard.  But it doesn't
258          * have to, because the vcard_upload_beforesave() hook above
259          * is going to notice what we're trying to do, and delete the old vCard.
260          */
261         CtdlWriteObject(USERCONFIGROOM, /* which room */
262                         "text/x-vcard", /* MIME type */
263                         temp,           /* temp file */
264                         u,              /* which user */
265                         0,              /* not binary */
266                         0,              /* don't delete others of this type */
267                         0);             /* no flags */
268
269         unlink(temp);
270 }
271
272
273
274 /*
275  * old style "enter registration info" command
276  */
277 void cmd_regi(char *argbuf) {
278         int a,b,c;
279         char buf[256];
280         struct vCard *my_vcard;
281
282         char tmpaddr[256];
283         char tmpcity[256];
284         char tmpstate[256];
285         char tmpzip[256];
286         char tmpaddress[512];
287         char tmpcountry[256];
288
289         if (!(CC->logged_in)) {
290                 cprintf("%d Not logged in.\n",ERROR+NOT_LOGGED_IN);
291                 return;
292                 }
293
294         my_vcard = vcard_get_user(&CC->usersupp);
295         strcpy(tmpaddr, "");
296         strcpy(tmpcity, "");
297         strcpy(tmpstate, "");
298         strcpy(tmpzip, "");
299         strcpy(tmpcountry, "USA");
300
301         cprintf("%d Send registration...\n", SEND_LISTING);
302         a=0;
303         while (client_gets(buf), strcmp(buf,"000")) {
304                 if (a==0) vcard_set_prop(my_vcard, "n", buf);
305                 if (a==1) strcpy(tmpaddr,buf);
306                 if (a==2) strcpy(tmpcity,buf);
307                 if (a==3) strcpy(tmpstate,buf);
308                 if (a==4) {
309                         for (c=0; c<strlen(buf); ++c) {
310                                 if ((buf[c]>='0')&&(buf[c]<='9')) {
311                                         b=strlen(tmpzip);
312                                         tmpzip[b]=buf[c];
313                                         tmpzip[b+1]=0;
314                                         }
315                                 }
316                         }
317                 if (a==5) vcard_set_prop(my_vcard, "tel;home", buf);
318                 if (a==6) vcard_set_prop(my_vcard, "email;internet", buf);
319                 if (a==7) strcpy(tmpcountry, buf);
320                 ++a;
321                 }
322         sprintf(tmpaddress, ";;%s;%s;%s;%s;%s",
323                 tmpaddr, tmpcity, tmpstate, tmpzip, tmpcountry);
324         vcard_set_prop(my_vcard, "adr", tmpaddress);
325         vcard_write_user(&CC->usersupp, my_vcard);
326         vcard_free(my_vcard);
327
328         lgetuser(&CC->usersupp, CC->curr_user);
329         CC->usersupp.flags=(CC->usersupp.flags|US_REGIS|US_NEEDVALID);
330         lputuser(&CC->usersupp);
331
332         /* set global flag calling for validation */
333         begin_critical_section(S_CONTROL);
334         get_control();
335         CitControl.MMflags = CitControl.MMflags | MM_VALID ;
336         put_control();
337         end_critical_section(S_CONTROL);
338         }
339
340
341
342 /*
343  * get registration info for a user
344  */
345 void cmd_greg(char *argbuf)
346 {
347         struct usersupp usbuf;
348         struct vCard *v;
349         char *s;
350         char who[256];
351         char adr[256];
352         char buf[256];
353
354         extract(who, argbuf, 0);
355
356         if (!(CC->logged_in)) {
357                 cprintf("%d Not logged in.\n", ERROR+NOT_LOGGED_IN);
358                 return;
359         }
360
361         if (!strcasecmp(who,"_SELF_")) strcpy(who,CC->curr_user);
362
363         if ((CC->usersupp.axlevel < 6) && (strcasecmp(who,CC->curr_user))) {
364                 cprintf("%d Higher access required.\n",
365                         ERROR+HIGHER_ACCESS_REQUIRED);
366                 return;
367         }
368
369         if (getuser(&usbuf, who) != 0) {
370                 cprintf("%d '%s' not found.\n", ERROR+NO_SUCH_USER, who);
371                 return;
372         }
373
374         v = vcard_get_user(&usbuf);
375
376         cprintf("%d %s\n", LISTING_FOLLOWS, usbuf.fullname);
377         cprintf("%ld\n", usbuf.usernum);
378         cprintf("%s\n", usbuf.password);
379         s = vcard_get_prop(v, "n", 0);
380         cprintf("%s\n", s ? s : " ");   /* name */
381
382         s = vcard_get_prop(v, "adr", 0);
383         sprintf(adr, "%s", s ? s : " ");/* address... */
384
385         extract_token(buf, adr, 2, ';');
386         cprintf("%s\n", buf);                           /* street */
387         extract_token(buf, adr, 3, ';');
388         cprintf("%s\n", buf);                           /* city */
389         extract_token(buf, adr, 4, ';');
390         cprintf("%s\n", buf);                           /* state */
391         extract_token(buf, adr, 5, ';');
392         cprintf("%s\n", buf);                           /* zip */
393
394         s = vcard_get_prop(v, "tel;home", 0);
395         if (s == NULL) s = vcard_get_prop(v, "tel", 1);
396         if (s != NULL) {
397                 cprintf("%s\n", s);
398                 }
399         else {
400                 cprintf(" \n");
401         }
402
403         cprintf("%d\n", usbuf.axlevel);
404
405         s = vcard_get_prop(v, "email;internet", 0);
406         cprintf("%s\n", s ? s : " ");
407         s = vcard_get_prop(v, "adr", 0);
408         sprintf(adr, "%s", s ? s : " ");/* address... */
409
410         extract_token(buf, adr, 6, ';');
411         cprintf("%s\n", buf);                           /* country */
412         cprintf("000\n");
413         }
414
415
416 /*
417  * When a user is being deleted, we have to remove his/her vCard.
418  * This is accomplished by issuing a message with 'CANCEL' in the S (special)
419  * field, and the same Extended ID as the existing card.
420  */
421 void vcard_purge(char *username, long usernum) {
422         struct CtdlMessage *msg;
423         char buf[256];
424
425         msg = (struct CtdlMessage *) mallok(sizeof(struct CtdlMessage));
426         if (msg == NULL) return;
427         memset(msg, 0, sizeof(struct CtdlMessage));
428
429         msg->cm_magic = CTDLMESSAGE_MAGIC;
430         msg->cm_anon_type = MES_NORMAL;
431         msg->cm_format_type = 0;
432         msg->cm_fields['A'] = strdoop(username);
433         msg->cm_fields['O'] = strdoop(ADDRESS_BOOK_ROOM);
434         msg->cm_fields['N'] = strdoop(NODENAME);
435         msg->cm_fields['M'] = strdoop("Purge this vCard\n");
436
437         sprintf(buf,
438                 "Citadel vCard: personal card for %s at %s",
439                 msg->cm_fields['A'], NODENAME);
440         msg->cm_fields['E'] = strdoop(buf);
441
442         msg->cm_fields['S'] = strdoop("CANCEL");
443
444         CtdlSaveMsg(msg, "", ADDRESS_BOOK_ROOM, MES_LOCAL);
445         CtdlFreeMessage(msg);
446
447         /* Start a netproc run in the background, so the "purge" message
448          * gets flushed out of the room immediately
449          */
450         system("./netproc &");
451 }
452         
453         
454
455
456 /*
457  * Session startup, allocate some per-session data
458  */
459 void vcard_session_startup_hook(void) {
460         CtdlAllocUserData(SYM_VCARD, sizeof(struct vcard_internal_info));
461 }
462
463
464 char *Dynamic_Module_Init(void)
465 {
466         SYM_VCARD = CtdlGetDynamicSymbol();
467         CtdlRegisterSessionHook(vcard_session_startup_hook, EVT_START);
468         CtdlRegisterMessageHook(vcard_upload_beforesave, EVT_BEFORESAVE);
469         CtdlRegisterMessageHook(vcard_upload_aftersave, EVT_AFTERSAVE);
470         CtdlRegisterProtoHook(cmd_regi, "REGI", "Enter registration info");
471         CtdlRegisterProtoHook(cmd_greg, "GREG", "Get registration info");
472         CtdlRegisterUserHook(vcard_purge, EVT_PURGEUSER);
473         create_room(ADDRESS_BOOK_ROOM, 3, "", 0);
474         return "$Id$";
475 }
476
477
478