#include #include /* for rand() */ #include #include /* for tolower() */ #include /* for mkdir() */ #include /* for exec(), wait() et. al */ #include /* for stat() */ #include #include #include #include #include "global.h" #include "custom.h" /* for date FORMAT strings */ #include "manhat-lib/shared_cs_util.h" #include "manhat-lib/shared_util.h" #include "manhat-lib/shared_news_util.h" #include "manhat-lib/shared_clipboard_util.h" #include "manhat-lib/shared_teamstring.h" #include "manhat-lib/shared_lock.h" #include "manhat-lib/shared_copy_file.h" #include "manhat-lib/shared_translate_url.h" #include "manhat-lib/shared_file_util.h" #include "manhat-lib/shared_access.h" #include "manhat-lib/shared_fold_file.h" #include "manhat-lib/shared_bb_dat_parser.h" typedef struct html_file_node { char *fname; struct html_file_node *next; } HTML_FILE_NODE; HTML_FILE_NODE *html_file_head; FILE_NODE *file_head = (FILE_NODE *) 0; /*list of attached files */ int attachments = 0; /* how many files are attached? - **same as number of nodes pointed to ** by file_head */ FILE_NODE *pasted_file_head = (FILE_NODE *) 0; /* list of 'pasted' files */ int pasted_attachments = 0; /* how many pasted files are attached? - **same as number of nodes pointed to ** by pasted_file_head */ int attachments_written = 0; /* set to 1 when uploaded files are written to disk */ /* deletes all files in list headed by file_head ** called to clean up sender directory when a ** critical error occurs after the files were written */ void delete_attachment_files () { FILE_NODE *ptr; char fname[MAX_PATH + 1]; for (ptr = file_head; ptr; ptr = ptr->next) { snprintf (fname, MAX_PATH + 1, "%s/%s", discussion_path, ptr->unique_fname); unlink (fname); } } void big_error (const char *error_code, char *msg) { if (attachments_written) delete_attachment_files (); cs_critical_error (error_code, msg); } static void read_parameters (char *course, char *key, char *fromprog, int *grp, int *topic, int *reply_to, int *website, int *pasted_website, int *pasted_files, char *to, char *subject, int *hide, int *hotpot_exam, int *width) { char string[80]; int temp_int; cs_get_required_parameter ("crs", course, MAX_PATH); cs_get_required_parameter ("id", key, MAX_KEY); cs_get_required_parameter ("from", fromprog, MAX_PATH); cs_get_required_parameter ("grp", string, 79); *grp = atoi (string); cs_get_required_parameter ("topic", string, 79); *topic = atoi (string); cs_get_required_parameter("reply_to", string, 79); *reply_to = atoi(string); cs_get_optional_parameter("to", to, MAX_USERNAME); /* did the teacher check the 'post message as hidden' box */ cs_get_optional_parameter("hide", string, 79); if(strlen(string)) *hide =1; else *hide =0; /* did user check the "I'm attaching a website' box? or the "I'm attaching a Hot Potatoes exam" box? */ cs_get_optional_parameter("website", string, 79); if(strlen(string)) { temp_int = !isdigit(*string)? 1: atoi(string); switch(temp_int) { case 0: *website = 0; *hotpot_exam = 0; break; case 1: *website = 1; *hotpot_exam = 0; break; case 2: *website = 0; *hotpot_exam = 1; break; default: *website = 0; *hotpot_exam = 0; break; } } else { *website = 0; *hotpot_exam = 0; } /* the width parameter specifies the width of the ** textarea used to type the msg, and is used to ** split the msg into lines. It should always be ** there, but we'll treat it as an optional parameter. ** If the parameter is not supplied, *width will be set to ** zero and the calling program must get a valid value from ** the course configuration file. */ cs_get_optional_parameter("width", string, 79); if(strlen(string)) *width = atoi(string); else *width = 0; cs_get_required_parameter("subject", subject, MAX_SUBJECT); /* was a website pasted from the clipboard? If so, this will be set to '1' */ cs_get_optional_parameter("pasted_website", string, 79); if(strlen(string)) *pasted_website = atoi(string); else *pasted_website = 0; /* were files pasted from the clipboard, If, so this will be set to the MAXIMUM number of * pasted attachments we could find. the user could have de-selected some or all of them */ cs_get_optional_parameter("pasted_files", string, 79); if(strlen(string)) *pasted_files = atoi(string); else *pasted_files = 0; } /* make sure no empty files have been uploaded and that there is an entry in ** the first file field, that is 'file1' must not be empty. **/ static void check_website_individual_files (CONFIG_STRUCT *conf) { #define MAX_TMP_NAME 30 int homefile = 0; int i; char name[MAX_TMP_NAME]; char filename[MAX_PATH +1]; FILE *fp; struct stat buf; for(i =1; i<= conf->mail_attachments; i++) { snprintf(name, MAX_TMP_NAME, "file%d", i); cs_get_optional_parameter(name, filename, MAX_PATH); if (strlen(filename)) { fp = cgi_filehandle(global_cgi, name); fstat(fileno(fp), &buf); if (buf.st_size == 0) big_error(ERR_EMPTY_FILE, filename); else if (!strcmp (name, "file1")) homefile = 1; } } if (!homefile) big_error (ERR_FIRST_ATTACHMENT_FIELD_BLANK, ""); #undef MAX_TMP_NAME } static void strtolower (char *string) { char *ptr; for (ptr = string; *ptr; ptr++) *ptr = tolower (*ptr); } void check_more_than_one_files(CONFIG_STRUCT *conf) { int i; char fname[MAX_PATH +1]; char value[MAX_PATH +1]; int count =0; for(i =0; imail_attachments; i++) { snprintf(fname, MAX_PATH +1, "file%d", i); cs_get_optional_parameter(fname, value, MAX_PATH); if(strlen(value)) count++; } if(count > 1) cs_critical_error(ERR_ATTACH_ZIP_ONLY_ONE_FILE, ""); } /** when attaching a website or a Hot Potatoes exam, the first attachment must contain *** either a zip file (ending in *.zip) in which case it is the *** only attachment, or an html file (ending in html or htm), *** or a Hot Potatoes file (ending in *.jbc, *.jqz) etc. *** the iszip parameter returns whether a zip file was found **/ static void check_website_upload_type (int *iszip, int hotpot_exam, CONFIG_STRUCT *conf) { #define MAX_MSG 500 char fname[MAX_PATH +1]; char *orig_fname; char ext[MAX_FILENAME + 1]; char msg[MAX_MSG]; cs_get_required_parameter("file1",fname, MAX_PATH); /* get lowercased extention of the first attachment */ orig_fname = (char *) malloc((strlen(fname) + 1) * sizeof(char)); if(!orig_fname) big_error(ERR_MALLOC_FAILED, "check_website_upload_type()"); strcpy (orig_fname, fname); strtolower (fname); get_extension (fname, ext); /* shared_news_util.c */ if (!strcmp (ext, "zip")) /* did the first attachment end in *.zip? */ { *iszip = 1; /* this is returned to the caller */ check_more_than_one_files(conf); } else { /* it's not a zip file -check to see if file1 has proper extension */ *iszip = 0; if(hotpot_exam) /* then first file must end in 'jbc' or 'jqz' */ { if(strcmp(ext,"jbc") && strcmp(ext,"jqz")) { snprintf (msg, MAX_MSG, "(%s)",orig_fname); big_error (ERR_FIRST_FIELD_NOT_ZIP_OR_HOTPOT, msg); } } else /* first file must end in 'htm' or 'html' */ { if (strcmp (ext, "htm") && strcmp (ext, "html")) { snprintf (msg, MAX_MSG,"(%s)",orig_fname); big_error (ERR_FIRST_FIELD_NOT_ZIP_OR_HTML, msg); } } } /* not a zip file */ free(orig_fname); #undef MAX_MSG } /* this simply ensures that the zipfile is not empty ** additional integrity checks are done later **/ static void check_empty_zipfile () { char str[MAX_PATH +1]; FILE *fp; struct stat buf; cs_get_required_parameter("file1", str, MAX_PATH); fp = cgi_filehandle(global_cgi, "file1"); fstat(fileno(fp), &buf); if(buf.st_size ==0) big_error (ERR_EMPTY_FILE, str); } /** user attached a website *** did they leave the subject: blank? *** did they attach a zip file or individual files? *** did they follow the rules? *** did they attach any empty files? **/ static void check_website_data (int *iszip, int hotpot_exam, CONFIG_STRUCT *conf) { check_website_upload_type (iszip, hotpot_exam, conf); /* did user upload a zip file or multiple files, and did they follow the rules?*/ if (*iszip) check_empty_zipfile (); /* make sure the zip file is not empty */ else check_website_individual_files (conf); /* make sure individual files are not empty */ } static void add_to_file_list ( FILE_NODE **head, const char *unique_fname, const char *orig_fname) { FILE_NODE *ptr; if (!*head) { *head = (FILE_NODE *) malloc (sizeof (FILE_NODE)); if (!*head) { big_error (ERR_MALLOC_FAILED, "add_to_file_list()"); } ptr = *head; } else { for (ptr = *head; ptr->next; ptr = ptr->next); ptr->next = (FILE_NODE *) malloc (sizeof (FILE_NODE)); if (!ptr->next) { big_error (ERR_MALLOC_FAILED, "add_to_file_list()"); } ptr = ptr->next; } strncpy (ptr->unique_fname, unique_fname, MAX_FILENAME + 1); strncpy (ptr->orig_fname, orig_fname, MAX_FILENAME + 1); ptr->next = (FILE_NODE *) 0; } /** breaks raw_fname, which is of the form orig_fname::unique_fname ** into its component parts **/ static void parse_raw_filename (char *raw_fname, char *orig_fname, char *unique_fname) { char *ptr; ptr = strstr (raw_fname, "::"); if (!ptr) big_error (ERR_PARAM_FORMAT, raw_fname); *ptr = '\0'; strncpy (orig_fname, raw_fname, MAX_FILENAME + 1); ptr += 2; /* skip past "::" */ strncpy (unique_fname, ptr, MAX_FILENAME + 1); } /* pasted_website is '1' if a website has been pasted ** pasted_files == the MAXIMUM number of files that could ** have been pasted to this message. The actual number of ** files can be less, since the user may have de-selected them ** on the news_new_memo form. ** ** this fcn builds a linked list of the files that have been ** pasted to this message **/ static void build_pasted_attachment_list (int pasted_website, int pasted_files) { int i; char parameter[10]; /* letter 'f' followed by a small integer, eg 'f1' f2' */ char raw_filename[2 * (MAX_FILENAME + 1)]; /* consists of "orig_fname::unique_fname" */ char orig_fname[MAX_FILENAME + 1]; char unique_fname[MAX_FILENAME + 1]; /* the files that have been pasted are represented by hidden parameters named ** f1, f2, f3...fn The values of these parameters are of the form orig_fname::unique_fname **/ for (i = 1; i <= pasted_files + pasted_website; i++) { snprintf (parameter, 10, "f%d", i); cs_get_optional_parameter (parameter, raw_filename, 2*MAX_FILENAME); if(strlen(raw_filename)) { parse_raw_filename (raw_filename, orig_fname, unique_fname); add_to_file_list (&pasted_file_head, unique_fname, orig_fname); pasted_attachments++; } } } /* note that this creates the file */ void get_unique_fname(char *unique_fname, const char *base_dir, const char *filetype, time_t *time_sent) { int fd; fd = get_unique_fd(base_dir, filetype, unique_fname, time_sent); close(fd); } static void get_system_filenames (const char *full_orig_name, char *unique_fname, char *orig_fname, time_t * time_sent) { get_unique_fname (unique_fname, discussion_path, "fil", time_sent); /* shared_news_util.c */ get_orig_fname (full_orig_name, orig_fname); /* shared_news_uitl.c */ } static void build_normal_file_list (time_t * time_sent, CONFIG_STRUCT *conf) { #define MAX_TMP_NAME 30 char unique_fname[MAX_PATH + 1]; char orig_fname[MAX_PATH + 1]; char name[MAX_TMP_NAME]; char filename[MAX_PATH +1]; int i; FILE *fp; struct stat buf; for(i =1; i<= conf->mail_attachments; i++) { snprintf(name, MAX_TMP_NAME, "file%d", i); cs_get_optional_parameter(name, filename, MAX_PATH ); if (strlen(filename)) { fp = cgi_filehandle(global_cgi, name); if(fstat(fileno(fp), &buf)) big_error(ERR_STAT_FAILED, ""); if(buf.st_size == 0) big_error(ERR_EMPTY_FILE, ""); get_system_filenames (filename, unique_fname, orig_fname, time_sent); add_to_file_list (&file_head, unique_fname, orig_fname); write_cgi_file (fp, buf.st_size, unique_fname); /* shared_news_util.c */ attachments_written = 1; /* set global flag for possible use in big_error() */ attachments++; } } #undef MAX_TMP_NAME } /** tests whether: *** msg is empty *** user uploaded a website (or Hot Potatoes exam) properly (preliminary checks) *** user isn't trying to post both file attachments and a pasted website *** Also builds a linked list of 'normal' file attachments ***/ static void check_form_data (int *is_web_zipfile, int website, int hotpot_exam, int pasted_website, int pasted_files, time_t * time_sent, CONFIG_STRUCT *conf) { /* the following test should always fail, unless someone * is mucking around with the form - you cannot paste both * a website and files */ if (pasted_website && pasted_files) big_error (ERR_INTERNAL_ERROR, ""); /* build a linked list of attachments that have been pasted to this message */ build_pasted_attachment_list (pasted_website, pasted_files); if (website || hotpot_exam) { check_incomplete_upload(); /* shared_news_util.c */ check_website_data (is_web_zipfile, hotpot_exam, conf); /* make sure message is not empty + do preliminary checks on web files */ /* still here? make sure nothing but the new web site is attached */ if (pasted_attachments) big_error (ERR_CANT_ATTACH_NEW_WEB_SITE, ""); } else { *is_web_zipfile = 0; build_normal_file_list (time_sent, conf); //verify_non_empty (); /* make sure message is not empty */ if(attachments) check_incomplete_upload(); /*shared_news_util.c */ if (pasted_website && pasted_attachments && attachments) big_error (ERR_CANT_MAKE_ADDITIONAL_ATTACHMENTS, ""); } } static void free_file_list (FILE_NODE ** head) { FILE_NODE *ptr; while (*head) { ptr = *head; *head = (*head)->next; free (ptr); } } static void free_html_filelist (void) { HTML_FILE_NODE *ptr; while (html_file_head) { ptr = html_file_head; html_file_head = html_file_head->next; if(ptr->fname) free(ptr->fname); free (ptr); } } static void process_message_file (char *msg_fname, time_t *time_sent, int width) { char *str =0; FILE *fp; char filepath[MAX_PATH +1]; int msg_len; hdf_get_copy(global_cgi->hdf, "Query.msg", &str, ""); get_unique_fname (msg_fname, discussion_path, "msg", time_sent); /* shared_news_util.c */ snprintf(filepath, MAX_PATH +1, "%s%s", discussion_path, msg_fname); msg_len = !str? 0 : strlen(str); if( (msg_len < width) || USE_TEXTAREA_HARD_WRAP) /* custom.h */ { fp = fopen(filepath,"w"); if(!fp) cs_critical_error(ERR_FOPEN_WRITE_FAILED, filepath); if(msg_len) { if(fwrite(str, strlen(str), 1, fp) != 1) cs_critical_error(ERR_FWRITE_FAILED, filepath); } fclose(fp); } else write_folded_text(filepath, str, width); /* shared_fold_file.c */ } static void process_pasted_attached_files (int pasted_files, int pasted_website, time_t * time_sent, SESSION *user) { FILE_NODE *fptr; char clipboard_path[MAX_PATH + 1]; char unique_fname[MAX_FILENAME + 1]; char sourcepath[MAX_PATH + 1]; char destpath[MAX_PATH + 1]; if ((pasted_files || pasted_website) && pasted_file_head) { snprintf (clipboard_path, MAX_PATH + 1, "%s%s/", CLIPBOARD_PATH, user->id); if (pasted_website) { get_unique_fname (unique_fname, discussion_path, is_hotpot_exam_dir(pasted_file_head->unique_fname)?HOTPOT_EXAM_DIR_PREFIX:NORMAL_WEBSITE_DIR_PREFIX, time_sent); copy_dir (clipboard_path, /* shared_copy_file.c */ discussion_path, pasted_file_head->unique_fname, unique_fname); strncpy (pasted_file_head->unique_fname, unique_fname, MAX_FILENAME + 1); } else { for (fptr = pasted_file_head; fptr; fptr = fptr->next) { get_unique_fname (unique_fname, discussion_path, "fil", time_sent); snprintf (sourcepath, MAX_PATH + 1, "%s%s", clipboard_path, fptr->unique_fname); snprintf (destpath, MAX_PATH + 1, "%s%s", discussion_path, unique_fname); copy_file (sourcepath, destpath); /* shared_copy_file.c */ strncpy (fptr->unique_fname, unique_fname, MAX_FILENAME + 1); } } } } static void complete_send (int grp, const char *msg_fname, SESSION *user, const char *receiver_username, time_t time_sent, int website, int hotpot_exam, int pasted_website, int reply_to, int topic, const char *subject, int hide, long *offset, char *info_fname) { ATTACHMENT attach; FILE_NODE *fptr; FILE *inbox_fp; int info_fd, inbox_fd; struct stat buf; NEWS_PERSON person; char inbox_fname[MAX_PATH + 1]; int msg_id; CENTRAL_INBOX_RECORD inbox_record, new_inbox_record; int inbox_record_size, attachment_size; /* memorize the sizes of these data structure, ** so we don't have to repeatedly call sizeof() */ inbox_record_size = sizeof(CENTRAL_INBOX_RECORD); attachment_size = sizeof(ATTACHMENT); /* this user has already read this message ** set a NEWS_PERSON record to be written ** to the end of the new inf file. */ strncpy(person.username,user->username, MAX_USERNAME + 1); strncpy(person.realname, user->realname, MAX_NAME + 1); person.time_read = time_sent; person.user_unlocked = 0; /* set up as much of the new inbox record as we can before locking ** the inbox.dat file */ strncpy(new_inbox_record.info.msg_fname, msg_fname, MAX_FILENAME + 1); strncpy(new_inbox_record.info.sender, user->username, MAX_USERNAME + 1); strncpy(new_inbox_record.info.sender_realname, grp== ANONYMOUS_DISCUSSION?user->alias:user->realname, MAX_NAME + 1); strncpy(new_inbox_record.info.receiver_username,receiver_username,MAX_USERNAME + 1); strncpy(new_inbox_record.info.subject, subject, MAX_SUBJECT + 1); new_inbox_record.info.time_sent = time_sent; new_inbox_record.info.attachments = website || hotpot_exam || (pasted_website && pasted_attachments) ? -1 : attachments + pasted_attachments; new_inbox_record.info.sender_team = user->team; new_inbox_record.info.reply_to = reply_to; /* get the name of the 'inf' file to do so, we need to open ** (create) the file */ info_fd = get_unique_fd (discussion_path,"inf", info_fname, &time_sent); /* don't bother to lock it, since no one knows it's there yet! */ strncpy(new_inbox_record.info_fname,info_fname,MAX_FILENAME + 1); new_inbox_record.release_time = 0; /* no release time set */ new_inbox_record.unrelease_time = 0; /* no unrelease time either */ new_inbox_record.status = hide? TEACHER_HIDDEN: NORMAL; /* mark msg as teacher hidden, if appropriate*/ /* now open and lock the inbox.dat file */ snprintf(inbox_fname,MAX_PATH + 1,"%s%s",central_discussion_path,INBOX_FNAME); inbox_fp = fopen(inbox_fname,"a+"); if(!inbox_fp) /* couldn't open or create inbox.dat */ big_error(ERR_FOPEN_WRITE_FAILED,""); inbox_fd = fileno(inbox_fp); get_exclusive_lock(inbox_fd, 1); /* shared_lock.c */ /* get the next available msg_id, by incrementing the msg_id of the ** last msg in the inbox.dat file */ fstat(inbox_fd, &buf); if(!buf.st_size) /* this is the first message */ msg_id = 1; else { if(fseek(inbox_fp,-inbox_record_size,SEEK_END) == -1) big_error(ERR_FSEEK_FAILED, ""); if(fread( &inbox_record, inbox_record_size, 1, inbox_fp) != 1) big_error(ERR_FREAD_FAILED, ""); msg_id = inbox_record.info.msg_id + 1; } new_inbox_record.info.msg_id = msg_id; new_inbox_record.info.topic_id = topic == -1? msg_id : topic; if(fseek(inbox_fp,0,SEEK_END) == -1) big_error(ERR_FSEEK_FAILED, ""); *offset = ftell(inbox_fp); /* offset is needed if this is a hotpot exam */ if(fwrite(&new_inbox_record, inbox_record_size,1,inbox_fp) != 1) big_error(ERR_FWRITE_FAILED, ""); /* now write the info file */ if (write (info_fd, &new_inbox_record.info, sizeof (NEWS_INFO)) != sizeof(NEWS_INFO)) big_error (ERR_FWRITE_FAILED, ""); for (fptr = file_head; fptr; fptr = fptr->next) { strncpy (attach.unique_fname, fptr->unique_fname, MAX_FILENAME + 1); strncpy (attach.orig_fname, fptr->orig_fname, MAX_FILENAME + 1); if (write (info_fd, &attach, attachment_size) != attachment_size) big_error (ERR_FWRITE_FAILED, ""); } for (fptr = pasted_file_head; fptr; fptr = fptr->next) { strncpy (attach.unique_fname, fptr->unique_fname, MAX_FILENAME + 1); strncpy (attach.orig_fname, fptr->orig_fname, MAX_FILENAME + 1); if (write (info_fd, &attach, attachment_size) != attachment_size) big_error (ERR_FWRITE_FAILED, ""); } if(write (info_fd,&person, sizeof(NEWS_PERSON))!= sizeof(NEWS_PERSON)) big_error (ERR_FWRITE_FAILED, ""); close (info_fd); release_lock(inbox_fd); /* shared_lock.c */ fclose(inbox_fp); write_user_inbox_record(msg_id, time_sent); /* shared_news_util.c */ } static void back_up (char *program) { char temp[MAX_PATH + 1]; translate_url_substitutes (temp, program, MAX_PATH + 1, 0); /* shared_translate_url.c 0= use &, not & */ // cs_cgi_redirect(temp); /* shared_cs_util.h */ printf ("Location:%s\n\n", temp); } /* reads the 'team' parameter from the CGI input stream ** sets this user's team to that value */ static void get_set_team (SESSION *user) { char team[2]; cs_get_required_parameter ("team", team, 1); /* a one character team= parameter MUST be there */ if(!is_valid_team(*team)) /* shared_teamstring.c */ cs_critical_error (ERR_TEAM_PARAM_NOT_FROM_A_Z, "get_set_team()"); /* shared_cs_util.c */ user->team = *team; /* this user is on that team for the rest of this program */ } int isdir (const char *dirname, const char *fname) { struct stat buf; char path[MAX_PATH + 1]; snprintf (path, MAX_PATH + 1, "%s/%s", dirname, fname); stat (path, &buf); return S_ISDIR (buf.st_mode); } void clean_zip_temp_dir () { char dirname[MAX_PATH + 1]; snprintf (dirname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_DIRNAME); kill_directory(dirname); /* shared_file_util.c */ } void create_tmp_zip_dir () { char dirname[MAX_PATH + 1]; snprintf (dirname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_DIRNAME); if (mkdir (dirname, 0777)) big_error (ERR_MKDIR_FAILED, dirname); } void delete_zip_tmp_file () { char fname[MAX_PATH + 1]; snprintf (fname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_FNAME); unlink (fname); } static void check_unzip_status (int error) { delete_zip_tmp_file (); switch (error) { /* this is my error code if exec() failed */ case 88: big_error (ERR_CANT_START_UNZIP, "exec()"); break; /* remaining errors reported by unzip */ case 0: return; /* everything went OK */ case 1: big_error (ERR_UNZIP_CODE_1, "unzip1"); break; case 2: big_error (ERR_UNZIP_CODE_2, "unzip2"); break; case 3: big_error (ERR_UNZIP_CODE_3, "unzip3"); break; case 51: big_error (ERR_UNZIP_CODE_51, "unzip51"); break; case 4: /* memory allocation error 4-8 */ case 5: case 6: case 7: case 8: big_error (ERR_UNZIP_MEMORY_ERROR, "allocation_error"); break; case 9: big_error (ERR_UNZIP_CODE_9, "unzip9"); break; case 10: big_error (ERR_UNZIP_CODE_10, "unzip10"); break; case 11: big_error (ERR_UNZIP_CODE_11, "unzip11"); break; case 50: big_error (ERR_UNZIP_CODE_50, "unzip50"); break; default: big_error (ERR_UNZIP_UNDOCUMENTED_ERROR, "unzip undocumented error"); break; } } static void unzip_zipfile () { char zip_path[MAX_PATH + 1]; char zipdir_path[MAX_PATH + 1]; char zipdir_switch[MAX_PATH + 1]; char zipfile[MAX_PATH +1]; FILE *fp; struct stat buf; pid_t pid, status; cs_get_required_parameter("file1", zipfile, MAX_PATH); fp = cgi_filehandle(global_cgi, "file1"); if(fstat(fileno(fp), &buf)) big_error (ERR_CANT_FIND_ZIPFILE, ""); if(buf.st_size ==0) big_error (ERR_EMPTY_FILE, ""); write_cgi_file (fp, buf.st_size, ZIP_TMP_FNAME); snprintf (zipdir_path, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_DIRNAME); clean_zip_temp_dir (); snprintf (zipdir_switch, MAX_PATH + 1, "-d%s", zipdir_path); /* tells unzip to extract to this directory */ snprintf (zip_path, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_FNAME); if ((pid = fork ()) < 0) big_error (ERR_CANT_START_NEW_PROCESS, "unzip_zipfile()"); if (pid == 0) { /* child process */ execl (UNZIP_PATH, "unzip", zipdir_switch, "-o", "-qq", zip_path, (char *) 0); exit (88); /* this is returned if above execl() fails */ } /*** parent process */ while (wait (&status) != pid); check_unzip_status (WEXITSTATUS (status)); /* report on status */ } static void get_lowercase_extension (const char *filename, char *extension) { char *ptr; ptr = strrchr (filename, '.'); if (!ptr) { *extension = '\0'; return; } strncpy (extension, ptr + 1, MAX_FILENAME + 1); for (ptr = extension; *ptr; ptr++) *ptr = tolower (*ptr); } static void add_to_html_file_list (const char *fname) { HTML_FILE_NODE *ptr; if (!html_file_head) { html_file_head = (HTML_FILE_NODE *) malloc (sizeof (HTML_FILE_NODE)); if (!html_file_head) big_error (ERR_MALLOC_FAILED, "add_to_html_file_list()"); ptr = html_file_head; } else { for (ptr = html_file_head; ptr->next; ptr = ptr->next); ptr->next = (HTML_FILE_NODE *) malloc (sizeof (HTML_FILE_NODE)); if (!ptr->next) big_error (ERR_MALLOC_FAILED, "add_to_html_file_list()"); ptr = ptr->next; } ptr->fname = (char *)malloc(sizeof(char)*strlen(fname)+1); strncpy (ptr->fname, fname, strlen(fname) + 1); ptr->next = (HTML_FILE_NODE *) 0; } /** if hotpot_exam, then build a list of files ending in *.jbc or *.jqz *** otherwise, build a list of files ending in *.htm or *.html **/ static void build_html_file_list (int *file_count, int hotpot_exam) { char dirname[MAX_PATH + 1]; DIR *dir_p; struct dirent *dir_entry_p; char extension[MAX_FILENAME + 1]; char new_file[MAX_PATH + 1]; *file_count = 0; snprintf (dirname, MAX_PATH + 1, "%s/%s", discussion_path, ZIP_TMP_DIRNAME); dir_p = opendir (dirname); if (!dir_p) big_error (ERR_OPENDIR_FAILED, ""); while (NULL != (dir_entry_p = readdir (dir_p))) { if (!isdir (dirname, dir_entry_p->d_name)) { get_lowercase_extension (dir_entry_p->d_name, extension); if ( (hotpot_exam && (!strcmp(extension, "jbc") || !strcmp(extension, "dat") || !strcmp(extension, "jqz")) ) || (!hotpot_exam && (!strcmp (extension, "htm") || !strcmp (extension, "html"))) ) { if(!strcmp(extension, "dat")) { convert_bb_to_jbc( dirname, dir_entry_p->d_name, new_file); /* shared_bb_dat_parser.c */ add_to_html_file_list (new_file); } else add_to_html_file_list (dir_entry_p->d_name); (*file_count)++; } } } closedir (dir_p); } /** This function examines the files that were unzipped to the *** temporary ZIP_TMP_DIRNAME directory, looking for the 'start' *** file for the attached website. *** *** If the uploaded zip file looks like a Lectern II RealAudio *** lecture, then the start_fname is set to a special flag value *** ZIP_LECTERN_START_FNAME (#defined in global.h). This will flag *** other programs, including web_page_start, to generate some on-the-fly *** HTML to make this lecture behave properly. *** *** otherwise.... *** *** If a file named ZIP_START_FNAME (i.e. 'index.html') is found *** then that is the start file. *** Else if there is only one file ending in .htm or .html *** in the extracted zip file, then that is the file that is *** used as the start file. *** Else if there is more than one file ending in .html or *** .html in the directory, then done is set to 0, flagging that *** we'll have to ask the user later which file to use as the *** start file **/ static void verify_start_file (char *start_fname, int *done) { #define MAX_MSG 100 char start_path[MAX_PATH + 1]; struct stat buf; int file_count; char msg[MAX_MSG]; if(is_lectern_lecture()) /* shared_news_util.c */ { strncpy(start_fname, ZIP_LECTERN_START_FNAME, MAX_FILENAME + 1); *done = 1; return ; } *done = 1; snprintf (start_path, MAX_PATH + 1, "%s/%s/%s", discussion_path, ZIP_TMP_DIRNAME, ZIP_START_FNAME); if (!stat (start_path, &buf)) /* if a file named ZIP_START_FNAME exists ... */ { if (S_ISDIR (buf.st_mode)) /* does the user's website include a DIRECTORY named ZIP_START_FNAME? */ { build_html_file_list (&file_count,0); /* examine the rest of the files in the directory */ if (file_count == 1) strncpy (start_fname, html_file_head->fname, MAX_FILENAME + 1); /* there's only one html file */ else /* no html files, but a directory named ZIP_START_FNAME */ { snprintf (msg, MAX_MSG, "(%s)", ZIP_START_FNAME); big_error (ERR_DIR_NAMED_X_FOUND_X_IS_RESERVED, msg); } } else /* ZIP_START_FNAME was not a directory */ strncpy (start_fname, ZIP_START_FNAME, MAX_FILENAME + 1); /* use it as the start_fname */ } else { /* no file named ZIP_START_FNAME was found */ build_html_file_list (&file_count, 0); /* build a list of files in the directory */ if (!file_count) /* if no html files were found */ big_error (ERR_NO_HTML_FILES_FOUND, ""); /* report the error */ else if (file_count == 1) /* only one html file found */ strncpy (start_fname, html_file_head->fname, MAX_FILENAME + 1); /* then use it */ else /* multiple html files were found, none of them named ZIP_START_FNAME */ { *done = 0; /* we'll have to ask the user to name the file (in another CGI program) */ strncpy (start_fname, ZIP_START_FNAME, MAX_FILENAME + 1); /* use ZIP_START_FNAME as the start file, we'll symlink it to the one the user specifies */ } } #undef MAX_MSG } /** This function examines the files which were unzipped to the *** temporary ZIP_TMP_DIRNAME directory, looking for the 'start' *** file for the attached HOT POTATOES exam. **/ static void verify_hotpot_start_file (char *start_fname) { int file_count; build_html_file_list (&file_count,1); /* build a list of Hot Potato files in the directory */ if (!file_count) /* if no Hot Potato files were found */ big_error (ERR_NO_HOTPOTATO_FILES_FOUND, ""); /* report the error */ else if (file_count == 1) /* only one hotpot file found */ strncpy (start_fname, html_file_head->fname, MAX_FILENAME + 1); /* then use it */ else /* multiple hot potato files were found */ big_error (ERR_MORE_THAN_ONE_HOTPOT_FILE_FOUND, ""); /* report the error */ } /* copies the individual files attached by the user to ** the (newly created) ZIP_TMP_DIR */ static void process_individual_website_files (char *start_fname, CONFIG_STRUCT *conf) { #define MAX_TMP_NAME 30 char name[MAX_TMP_NAME]; char orig_fname[MAX_PATH + 1]; char write_dest_fname[MAX_PATH + 1]; int i; char filename[MAX_PATH +1]; FILE *fp; struct stat buf; clean_zip_temp_dir (); create_tmp_zip_dir (); strncpy (start_fname, "index.html", MAX_FILENAME + 1); /* assume this, just to be safe */ for(i =1; i<= conf->mail_attachments; i++) { snprintf(name, MAX_TMP_NAME, "file%d", i); cs_get_optional_parameter(name, filename, MAX_PATH); if (strlen(filename)) { fp = cgi_filehandle(global_cgi, name); if(fstat(fileno(fp), &buf)) big_error(ERR_STAT_FAILED, ""); if(buf.st_size == 0) big_error(ERR_EMPTY_FILE, ""); get_orig_fname (filename, orig_fname); snprintf (write_dest_fname, MAX_PATH + 1, "%s/%s", ZIP_TMP_DIRNAME, orig_fname); /* so we can spoof write_cgi_file */ write_cgi_file (fp,buf.st_size, write_dest_fname); if (!strcmp (name, "file1")) strncpy (start_fname, orig_fname, MAX_FILENAME + 1); } } #undef MAX_TMP_NAME } static void rename_tmp_dir (char *directory_fname, time_t * time_sent, int hotpot_exam) { char oldname[MAX_PATH + 1]; char newname[MAX_PATH + 1]; snprintf (oldname, MAX_PATH + 1, "%s%s", discussion_path, ZIP_TMP_DIRNAME); /* if this is a hot potatoes exam, the directory name starts with 'hot' instead of the ** 'dir' used by ordinary websites */ get_unique_fname (directory_fname, discussion_path, hotpot_exam?HOTPOT_EXAM_DIR_PREFIX:NORMAL_WEBSITE_DIR_PREFIX, time_sent); snprintf (newname, MAX_PATH + 1, "%s%s", discussion_path, directory_fname); unlink(newname); /* because get_unique_fname() created the file */ if (rename (oldname, newname)) big_error (ERR_CANT_RENAME_TEMP_DIR, oldname); } static void process_website_attached_files (time_t * time_sent, int is_web_zipfile, int hotpot_exam, int *done, CONFIG_STRUCT *conf) { char start_file[MAX_FILENAME + 1]; char directory_fname[MAX_FILENAME + 1]; if (is_web_zipfile) { unzip_zipfile (); if(!hotpot_exam) /* if an ordinary web site is being attached */ verify_start_file (start_file, done); else { verify_hotpot_start_file(start_file); *done = 1; } } else { process_individual_website_files (start_file, conf); *done = 1; } rename_tmp_dir (directory_fname, time_sent,hotpot_exam); add_to_file_list (&file_head, directory_fname, start_file); } void ask_which_fname ( char *course, char *key, int grp, char *from, CONFIG_STRUCT *conf) { #define MAX_TMP_NAME 30 HTML_FILE_NODE *ptr; int i =0; char name[MAX_TMP_NAME]; cs_set_course_info(conf); cs_set_current_time(); cs_set_int_value("grp", grp); cs_set_value("crs", course); cs_set_value("id", key); cs_set_value("from", from); cs_set_value("dir", file_head->unique_fname); for (ptr = html_file_head; ptr; ptr = ptr->next) { snprintf(name, MAX_TMP_NAME, "file.%d.name", i); cs_set_value(name, "fname"); snprintf(name, MAX_TMP_NAME, "file.%d.value", i); cs_set_value(name, ptr->fname); if(i ==0) { snprintf(name, MAX_TMP_NAME, "file.%d.checked", i); cs_set_int_value(name, 1); } i++; } free_html_filelist (); free_file_list (&file_head); #undef MAX_TMP_NAME } static void hotpot_exam_check(const char *crs, const char *key, const char *sender, const char *info_fname, long offset) { char url[MAX_PATH +1]; snprintf(url, MAX_PATH +1, "%s?crs=%s&id=%s&sender=%s&inf=%s&loc=%ld", "hotpot_exam_check", crs, key, sender, info_fname, offset); cs_set_value("hotpot_url", url); } int main (void) { char course[MAX_PATH + 1]; /* from crs=? command line */ char key[MAX_KEY + 1]; /* from id=? command line */ char from[MAX_PATH + 1]; /* from from=? command line */ int grp; /* from grp=? command line */ int topic; /* from topic=? command line */ int reply_to; /* from reply_to=? command line */ char to[MAX_USERNAME + 1]; /* from to=? command line for ASSIGNMENTS */ int pasted_website; /* from optional pasted_website=? cmd line */ int pasted_files; /* from optional pasted_files=? cmd line */ int hide; /* from optional hide=? cmd line */ int width; /* from width=? cmd line, used to split msg into lines */ char msg_fname[MAX_FILENAME + 1]; /* name given to the message */ time_t time_sent; char subject[MAX_SUBJECT + 1]; int website; /* is the user posting a website? */ int is_web_zipfile; /* was a web page attached as a single zip file? */ int hotpot_exam =0; /* is the user posting a Hot Potatoes exam? */ int done; /* are we done, or do we have to ask user to pick the starting html file from a list */ /*** next two are used for hotpot exams to redirect user to hotpot_exam_check program */ long offset; /** offset into central inbox.dat file where this msg is stored */ char info_fname[MAX_FILENAME + 1]; /* name of info_fname for this message */ SESSION user; CONFIG_STRUCT conf; cs_cgi_init(); /* shared_cs_util.c */ read_parameters (course, key, from, &grp, &topic, &reply_to, &website, &pasted_website, &pasted_files, to, subject, &hide, &hotpot_exam, &width); read_configuration_file (course, &conf); /* shared_util.c */ validate_key (key, &user, &conf); /* shared_util.c */ if(!has_write_permission(&user, &conf)) /* shared_access.c */ access_denied_error(); /* shared_access.c */ /** If a width (for the textarea) was not supplied on the parameter list, *** then default to the standard editor width defined for this classroom. **/ if(!width) width=editor_width_config(conf.misc); /* shared_news_util.c */ /* the 'hide' parameter, which controls whether or not this message is posted as ** hidden by the teacher, can only be set if the user is FACULTY and if the msg ** posted is a new topic, OR if we are in the Assignments module, where students ** taking a Hot Potatoes exam can (behind the scenes) post a message as teacher-hidden ** Instead of printing an error when bogus values are ** received, we'll just unset the 'hide' flag */ if( (grp != ASSIGNMENTS) && ((user.group != FACULTY) || (topic != -1)) ) hide = 0; /*** only teachers in the Assignments module should be able to post a Hot Potatoes exam **** the next test is done just to foil people trying to break the system ***/ if(!allow_hotpot_exam_post(&user, grp, topic)) /* shared_news_util.c */ hotpot_exam = 0; /* if this it the Team/Teach module and a faculty member is running the program, there must be a 'team' ** parameter. This user will switch to that team for the duration of the run */ if ((grp == TEAM_TEACH) && (user.group == FACULTY)) get_set_team (&user); time_sent = time (NULL); /* this is the timestamp of when this msg was sent */ set_discussion_names (conf.course_path, user.username, grp); /* shared_news_util.c */ /* perform a variety of sanity checks on what the user is trying to send */ check_form_data (&is_web_zipfile, website, hotpot_exam, pasted_website, pasted_files, &time_sent, &conf); /* if the user is attaching a website, we next deal with those attachments ** the 'done' variable is used to determine whether we need to ask the user ** an additional question re: the website via another CGI program */ if (website || hotpot_exam) process_website_attached_files (&time_sent, is_web_zipfile, hotpot_exam, &done, &conf); else done = 1; /* no need for more info from user */ /* save the text message to a unique filename */ process_message_file (msg_fname, &time_sent, width); /* copy pasted files from clipboard */ process_pasted_attached_files (pasted_files, pasted_website, &time_sent, &user); /* free the data structures used in shared_multipart.c */ /* send the message */ complete_send(grp,msg_fname, &user, to, time_sent, website, hotpot_exam, pasted_website, reply_to, topic,subject, hide, &offset, info_fname); /* more cleanup */ free_file_list (&pasted_file_head); if (done) /* if we don't need to ask the user anything about an attached website */ { free_html_filelist (); free_file_list (&file_head); if(hotpot_exam) { hotpot_exam_check(course, key, user.username, info_fname, offset); cs_cgi_display("hotpot_exam_check_opener", 0); } else back_up (from); /* redirect to 'from' page */ } else { ask_which_fname (course, key, grp, from, &conf); /* redirect to another CGI to ask a question */ cs_cgi_display("select_home_html_file", 1); } cs_cgi_destroy(); return 0; }