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