2 ======================================================================
4 CREATOR: eric 29 April 2000
6 $Id: stow.c,v 1.10 2008-01-02 20:07:46 dothebart Exp $
9 (C) COPYRIGHT 2000 Eric Busboom
10 http://www.softwarestudio.org
12 The contents of this file are subject to the Mozilla Public License
13 Version 1.0 (the "License"); you may not use this file except in
14 compliance with the License. You may obtain a copy of the License at
15 http://www.mozilla.org/MPL/
17 Software distributed under the License is distributed on an "AS IS"
18 basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
19 the License for the specific language governing rights and
20 limitations under the License.
22 The Initial Developer of the Original Code is Eric Busboom
24 ======================================================================*/
34 #include <limits.h> /* for PATH_MAX */
37 #include <sys/utsname.h> /* for uname */
38 #include <sys/stat.h> /* for stat */
39 #if defined(HAVE_UNISTD_H)
40 #include <unistd.h> /* for stat, getpid, getopt */
42 #include <pwd.h> /* For getpwent */
43 #include <sys/types.h> /* For getpwent */
44 #include <ctype.h> /* for tolower */
46 #include <libical/ical.h>
47 #include <libicalss/icalss.h>
51 #define SENDMAIL "/usr/lib/sendmail -t"
53 void usage(char *message);
56 #define PATH_MAX 256 /* HACK */
74 enum options input_type;
75 enum options input_source;
92 enum file_type test_file(char *path)
99 /* Check if the path already exists and if it is a directory*/
100 if (stat(path,&sbuf) != 0){
102 /* A file by the given name does not exist, or there was
112 /* A file by the given name exists, but is it a directory? */
114 if (S_ISDIR(sbuf.st_mode)){
116 } else if(S_ISREG(sbuf.st_mode)){
126 char* lowercase(const char* str)
129 char* new = strdup(str);
135 for(p = new; *p!=0; p++){
143 char* get_local_attendee(struct options_struct *opt)
145 char attendee[PATH_MAX];
149 strncpy(attendee,opt->calid,PATH_MAX);
153 char* user = getenv("USER");
155 uname(&utget_option);
156 /* HACK nodename may not be a fully qualified domain name */
157 snprintf(attendee,PATH_MAX,"%s@%s",user,uts.nodename);
161 return lowercase(attendee);
166 icalcomponent* get_first_real_component(icalcomponent *comp)
170 for(c = icalcomponent_get_first_component(comp,ICAL_ANY_COMPONENT);
172 c = icalcomponent_get_next_component(comp,ICAL_ANY_COMPONENT)){
173 if (icalcomponent_isa(c) == ICAL_VEVENT_COMPONENT ||
174 icalcomponent_isa(c) == ICAL_VTODO_COMPONENT ||
175 icalcomponent_isa(c) == ICAL_VJOURNAL_COMPONENT )
186 char* make_mime(const char* to, const char* from, const char* subject,
187 const char* text_message, const char* method,
188 const char* ical_message)
190 size_t size = strlen(to)+strlen(from)+strlen(subject)+
191 strlen(text_message)+ strlen(ical_message)+TMPSIZE;
193 char mime_part_1[TMPSIZE];
194 char mime_part_2[TMPSIZE];
195 char content_id[TMPSIZE];
196 char boundary[TMPSIZE];
201 if ((m = malloc(sizeof(char)*size)) == 0){
202 fprintf(stderr,"%s: Can't allocate memory: %s\n",program_name,strerror(errno));
208 srand(time(0)<<getpid());
209 sprintf(content_id,"%d-%d@%s",(int)time(0),rand(),uts.nodename);
210 sprintf(boundary,"%d-%d-%s",(int)time(0),rand(),uts.nodename);
212 sprintf(mime_part_1,"Content-ID: %s\n\
213 Content-type: text/plain\n\
214 Content-Description: Text description of error message\n\n\
216 content_id,text_message,boundary);
218 if(ical_message != 0 && method != 0){
219 sprintf(mime_part_2,"Content-ID: %s\n\
220 Content-type: text/calendar; method=%s\n\
221 Content-Description: iCal component reply\n\n\
223 content_id,method,ical_message,boundary);
231 Content-Type: multipart/mixed; boundary=\"%s\"\n\
233 This is a multimedia message in MIME format\n\
238 to,from,subject,content_id,boundary,boundary,
241 if(ical_message != 0 && method != 0){
242 strcat(m, mime_part_2);
250 /* The incoming component had fatal errors */
251 void return_failure(icalcomponent* comp, char* message,
252 struct options_struct *opt)
254 char* local_attendee = opt->calid;
256 const char *org_addr;
258 icalcomponent *inner = get_first_real_component(comp);
260 icalproperty *organizer_prop = icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY);
261 const char *organizer = icalproperty_get_organizer(organizer_prop);
263 org_addr = strchr(organizer,':');
266 org_addr++; /* Skip the ';' */
268 org_addr = organizer;
271 if (opt->errors == ERRORS_TO_ORGANIZER){
272 p = popen(SENDMAIL,"w");
279 "%s: fatal. Could not open pipe to sendmail (\"%s\") \n",
280 program_name,SENDMAIL);
284 fputs(make_mime(org_addr, local_attendee, "iMIP error",
286 icalcomponent_as_ical_string(comp)),p);
288 if (opt->errors == ERRORS_TO_ORGANIZER){
293 /* The program had a fatal error and could not process the incoming component*/
294 void return_error(icalcomponent* comp, char* message, struct options_struct *opt)
298 fputs(make_mime("Dest", "Source", "iMIP system failure",
299 message, 0,0),stdout);
303 icalcomponent* make_reply(icalcomponent *comp, icalproperty *return_status,
304 struct options_struct *opt)
307 icalcomponent *reply, *rinner;
308 icalcomponent *inner = get_first_real_component(comp);
310 char* local_attendee = opt->calid;
311 char attendee[TMPSIZE];
313 char prodid[TMPSIZE];
315 snprintf(attendee,TMPSIZE,"mailto:%s",local_attendee);
317 snprintf(prodid,TMPSIZE,"-//Softwarestudio.org//%s version %s//EN",ICAL_PACKAGE,ICAL_VERSION);
319 /* Create the base component */
320 reply = icalcomponent_vanew(
321 ICAL_VCALENDAR_COMPONENT,
322 icalproperty_new_version(strdup("2.0")),
323 icalproperty_new_prodid(strdup(prodid)),
324 icalproperty_new_method(ICAL_METHOD_REPLY),
326 ICAL_VEVENT_COMPONENT,
327 icalproperty_new_clone(
328 icalcomponent_get_first_property(inner,ICAL_DTSTAMP_PROPERTY)),
329 icalproperty_new_clone(
330 icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY)),
331 icalproperty_new_clone(
332 icalcomponent_get_first_property(inner,ICAL_UID_PROPERTY)),
333 icalproperty_new_attendee(attendee),
338 /* Convert errors into request-status properties and transfers
339 them to the reply component */
341 icalcomponent_convert_errors(comp);
343 rinner = get_first_real_component(reply);
345 for(p = icalcomponent_get_first_property(inner,
346 ICAL_REQUESTSTATUS_PROPERTY);
348 p = icalcomponent_get_next_property(inner,
349 ICAL_REQUESTSTATUS_PROPERTY)){
351 icalcomponent_add_property(rinner,icalproperty_new_clone(p));
354 if(return_status != 0){
355 icalcomponent_add_property(rinner, return_status);
362 int check_attendee(icalproperty *p, struct options_struct *opt){
363 const char* s = icalproperty_get_attendee(p);
364 char* lower_attendee = lowercase(s);
365 char* local_attendee = opt->calid;
367 /* Check that attendee begins with "mailto:" */
368 if (strncmp(lower_attendee,"mailto:",7) == 0){
369 /* skip over the mailto: part */
372 if(strcmp(lower_attendee,local_attendee) == 0){
378 free(lower_attendee);
384 char static_component_error_str[PATH_MAX];
385 char* check_component(icalcomponent* comp, icalproperty **return_status,
386 struct options_struct *opt)
388 char* component_error_str=0;
389 icalcomponent* inner;
392 int found_attendee = 0;
393 struct icalreqstattype rs;
395 rs.code = ICAL_UNKNOWN_STATUS;
400 icalrequeststatus code;
407 /* This do/while loop only executes once because it is being used
408 to fake exceptions */
412 /* Check that we actually got a component */
414 strcpy(static_component_error_str,
415 "Did not find a component");
416 component_error_str = static_component_error_str;
420 /* Check that the root component is a VCALENDAR */
421 if(icalcomponent_isa(comp) != ICAL_VCALENDAR_COMPONENT){
422 strcpy(static_component_error_str,
423 "Root component is not a VCALENDAR");
424 component_error_str = static_component_error_str;
425 rs.code = ICAL_3_11_MISSREQCOMP_STATUS;
431 /* Check that the component has a METHOD */
433 if (icalcomponent_get_first_property(comp,ICAL_METHOD_PROPERTY) == 0)
435 strcpy(static_component_error_str,
436 "The component you sent did not have a METHOD property");
437 component_error_str = static_component_error_str;
438 rs.code = ICAL_3_11_MISSREQCOMP_STATUS;
442 inner = get_first_real_component(comp);
445 /* Check that the compopnent has an organizer */
446 if(icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY) == 0){
447 fprintf(stderr,"%s: fatal. Component does not have an ORGANIZER property\n",program_name);
448 rs.code = ICAL_3_11_MISSREQCOMP_STATUS;
453 /* Check for this user as an attendee or organizer */
455 for(p = icalcomponent_get_first_property(inner,ICAL_ATTENDEE_PROPERTY);
457 p = icalcomponent_get_next_property(inner,ICAL_ATTENDEE_PROPERTY)){
459 found_attendee += check_attendee(p,opt);
462 for(p = icalcomponent_get_first_property(inner,ICAL_ORGANIZER_PROPERTY);
464 p = icalcomponent_get_next_property(inner,ICAL_ORGANIZER_PROPERTY)){
466 found_attendee += check_attendee(p,opt);
469 if (found_attendee == 0){
470 struct icalreqstattype rs;
471 memset(static_component_error_str,0,PATH_MAX);
473 snprintf(static_component_error_str,PATH_MAX,
474 "This target user (%s) is not listed as an attendee or organizer",
476 component_error_str = static_component_error_str;
478 rs.code = ICAL_3_7_INVCU_STATUS;
485 /* Check that the component passes iTIP restrictions */
487 errors = icalcomponent_count_errors(comp);
488 icalrestriction_check(comp);
490 if(errors != icalcomponent_count_errors(comp)){
491 snprintf(static_component_error_str,PATH_MAX,
492 "The component does not conform to iTIP restrictions.\n Here is the original component; look at the X-LIC-ERROR properties\nfor details\n\n%s",icalcomponent_as_ical_string(comp));
493 component_error_str = static_component_error_str;
501 if(rs.code != ICAL_UNKNOWN_STATUS){
502 *return_status = icalproperty_new_requeststatus(rs);
505 return component_error_str;
509 void usage(char *message)
511 fprintf(stderr,"Usage: %s [-emdcn] [-i inputfile] [-o outputfile] [-u calid]\n",program_name);
512 fprintf(stderr,"-e\tInput data is encapsulated in a MIME Message \n\
513 -m\tInput is raw iCal \n\
514 -i\tSpecify input file. Otherwise, input comes from stdin\n\
515 -o\tSpecify file to save incoming message to\n\
516 -d\tSpecify database to send data to\n\
517 -u\tSet the calid to store the data to\n\
518 -n\tSend errors to stdout instead of organizer\n\
524 void get_options(int argc, char* argv[], struct options_struct *opt)
527 #if !defined(HAVE_UNISTD_H)
529 extern int optind, optopt;
533 opt->storage = STORE_IN_FILE;
534 opt->input_source = INPUT_FROM_STDIN;
535 opt->input_type = INPUT_IS_ICAL;
537 opt->errors = ERRORS_TO_ORGANIZER;
539 opt->output_file = 0;
542 while ((c = getopt(argc, argv, "nemu:o:d:b:c:i:")) != -1) {
544 case 'e': { /* Input data is MIME encapsulated */
545 opt->input_type = INPUT_IS_MIME;
548 case 'm': { /* Input is iCal. Default*/
549 opt->input_type = INPUT_IS_ICAL;
552 case 'i': { /* Input comes from named file */
553 opt->input_source = INPUT_FROM_FILE;
554 opt->input_file = strdup(optarg);
557 case 'o': { /* Output goes to named file. Default*/
558 opt->output_file = strdup(optarg);
559 opt->storage = STORE_IN_FILE;
562 case 'd': { /* Output goes to database */
563 fprintf(stderr,"%s: option -d is unimplmented\n",program_name);
564 opt->storage = STORE_IN_DB;
572 case 'u': { /* Set the calid for the output database or
573 file. Default is user name of user running
575 opt->calid = strdup(optarg);
579 case 'n': { /* Dump error to stdout. Default is to
580 send error to the organizer specified
582 opt->errors = ERRORS_TO_STDOUT;
586 case ':': {/* Option given without an operand */
588 "%s: Option -%c requires an operand\n",
589 program_name,optopt);
606 /* If no calid specified, use username */
607 char attendee[PATH_MAX];
608 char* user = getenv("USER");
611 /* HACK nodename may not be a fully qualified domain name */
612 snprintf(attendee,PATH_MAX,"%s@%s",user,uts.nodename);
614 opt->calid = lowercase(attendee);
617 if(opt->storage == STORE_IN_FILE &&
618 opt->output_file ==0){
620 char* user = getenv("USER");
624 fprintf(stderr,"%s: Can't get username. Try explicitly specifing the output file with -o", program_name);
628 /* Find password entry for user */
629 while( (pw = getpwent())!=0){
630 if(strcmp(user,pw->pw_name)==0){
636 fprintf(stderr,"%s: Can't get get password entry for user \"%s\" Try explicitly specifing the output file with -o",
642 fprintf(stderr,"%s: User \"%s\" has no home directory. Try explicitly specifing the output file with -o",
647 snprintf(file,PATH_MAX,"%s/.facs/%s",pw->pw_dir,opt->calid);
649 opt->output_file = strdup(file);
653 /* Now try to create the calendar directory if it does
656 if(opt->storage == STORE_IN_FILE ) {
658 char* facspath = strdup(opt->output_file);
661 /* Cut off the last slash to make it just a directoy */
663 p = strrchr(facspath,'/');
666 /* Use some other directory */
669 type = test_file(facspath);
672 if (type == NO_FILE){
674 if(mkdir(facspath,0775) != 0){
676 "%s: Failed to create calendar directory %s: %s\n",
677 program_name,facspath, strerror(errno));
680 fprintf(stderr,"%s: Creating calendar directory %s\n",
681 program_name,facspath);
684 } else if(type==REGULAR || type == ERROR){
685 fprintf(stderr,"%s: Cannot create calendar directory %s\n",
686 program_name,facspath);
693 char* check_options(struct options_struct *opt)
698 void store_component(icalcomponent *comp, struct options_struct *opt)
703 if(opt->storage == STORE_IN_FILE){
704 icalset *fs = icalfileset_new(opt->output_file);
708 "%s: Failed to get incoming component directory: %s\n",
709 program_name, icalerror_strerror(icalerrno));
714 error = icalfileset_add_component(fs,comp);
716 if (error != ICAL_NO_ERROR){
717 fprintf(stderr,"%s: Failed to write incoming component: %s\n",
718 program_name, icalerror_strerror(icalerrno));
722 error = icalfileset_commit(fs);
724 if (error != ICAL_NO_ERROR){
725 fprintf(stderr,"%s: Failed to commit incoming cluster: %s\n",
726 program_name, icalerror_strerror(icalerrno));
738 char* read_stream(char *s, size_t size, void *d)
740 char *c = fgets(s,size, (FILE*)d);
745 icalcomponent* read_nonmime_component(struct options_struct *opt)
749 icalparser* parser = icalparser_new();
750 icalerrorstate es = icalerror_get_error_state(ICAL_MALFORMEDDATA_ERROR);
753 if(opt->input_source == INPUT_FROM_FILE){
754 stream = fopen(opt->input_file,"r");
757 perror("Can't open input file");
766 icalparser_set_gen_data(parser,stream);
769 line = icalparser_get_line(parser,read_stream);
771 icalerror_set_error_state(ICAL_MALFORMEDDATA_ERROR,ICAL_ERROR_NONFATAL);
772 comp = icalparser_add_line(parser,line);
773 icalerror_set_error_state(ICAL_MALFORMEDDATA_ERROR,es);
779 } while ( line != 0);
781 if(opt->input_source == INPUT_FROM_FILE){
789 icalcomponent* find_vcalendar(icalcomponent* comp)
791 icalcomponent *c,*rtrn;
793 for(c = icalcomponent_get_first_component(comp,ICAL_ANY_COMPONENT);
795 c = icalcomponent_get_next_component(comp,ICAL_ANY_COMPONENT)){
797 if(icalcomponent_isa(c) == ICAL_VCALENDAR_COMPONENT){
798 icalcomponent_remove_component(comp,c);
802 if((rtrn=find_vcalendar(c)) != 0){
810 icalcomponent* read_mime_component(struct options_struct *opt)
812 icalcomponent *comp,*mimecomp;
815 if(opt->input_source == INPUT_FROM_FILE){
816 stream = fopen(opt->input_file,"r");
819 perror("Can't open input file");
829 mimecomp = icalmime_parse(read_stream,(void*)stream);
831 /* now find the iCal component embedded within the mime component */
832 comp = find_vcalendar(mimecomp);
842 icalcomponent* read_component(struct options_struct *opt)
844 if(opt->input_type == INPUT_IS_MIME){
845 return read_mime_component(opt);
846 } else if (opt->input_type == INPUT_IS_ICAL){
847 return read_nonmime_component(opt);
849 fprintf(stderr,"%s: Internal Error; unknown option for input_type\n",
855 int main(int argc, char* argv[] )
857 char* options_error_str;
858 char* component_error_str;
859 icalcomponent* comp, *reply;
860 struct options_struct opt;
861 icalproperty *return_status;
863 program_name = strrchr(argv[0],'/');
865 get_options(argc, argv, &opt);
867 if ( (options_error_str = check_options(&opt)) != 0 ){
868 usage(options_error_str);
872 comp = read_component(&opt);
874 /* If the component had any fatal errors, return an error message
876 if ( (component_error_str =
877 check_component(comp,&return_status,&opt)) != 0){
879 reply = make_reply(comp,return_status,&opt);
881 return_failure(reply, component_error_str, &opt);
882 icalcomponent_free(reply);
887 store_component(comp,&opt);
890 /* Don't free the component comp, since it is now part of the
891 store, and will be freed there */