2 * Copyright (c) 1987-2012 by the citadel.org team
4 * This program is open source software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3.
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
14 #if TIME_WITH_SYS_TIME
15 # include <sys/time.h>
19 # include <sys/time.h>
26 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <arpa/inet.h>
35 #include <netinet/in.h>
39 #ifdef THREADED_CLIENT
42 #include <libcitadel.h>
43 ///#include "citadel.h"
44 #include "citadel_ipc.h"
45 //#include "citadel_decls.h"
46 //#include "citadel_dirs.h"
47 #ifdef THREADED_CLIENT
48 pthread_mutex_t rwlock;
52 static SSL_CTX *ssl_ctx;
55 #ifdef THREADED_CLIENT
56 pthread_mutex_t **Critters; /* Things that need locking */
57 #endif /* THREADED_CLIENT */
59 #endif /* HAVE_OPENSSL */
62 #define INADDR_NONE 0xffffffff
65 static void (*status_hook)(char *s) = NULL;
66 char ctdl_autoetc_dir[PATH_MAX]="";
67 char file_citadel_rc[PATH_MAX]="";
68 char ctdl_run_dir[PATH_MAX]="";
69 char ctdl_etc_dir[PATH_MAX]="";
70 char ctdl_home_directory[PATH_MAX] = "";
71 char file_citadel_socket[PATH_MAX]="";
72 char file_citadel_config[PATH_MAX]="";
95 void CtdlIPC_lock(CtdlIPC *ipc)
97 if (ipc->network_status_cb) ipc->network_status_cb(1);
98 #ifdef THREADED_CLIENT
99 pthread_mutex_lock(&(ipc->mutex));
104 void CtdlIPC_unlock(CtdlIPC *ipc)
106 #ifdef THREADED_CLIENT
107 pthread_mutex_unlock(&(ipc->mutex));
109 if (ipc->network_status_cb) ipc->network_status_cb(0);
117 char *libcitadelclient_version_string(void) {
118 return "libcitadelclient(unnumbered)";
124 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
125 snprintf(SUBDIR,sizeof SUBDIR, "%s%s%s%s%s%s%s", \
126 (home&!relh)?ctdl_home_directory:basedir, \
127 ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
128 ((basedir!=ctdldir)&(home&!relh))?"/":"", \
130 (relhome[0]!='\0')?"/":"",\
132 (dirbuffer[0]!='\0')?"/":"");
134 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
137 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg)
139 const char* basedir = "";
140 char dirbuffer[PATH_MAX] = "";
142 StripSlashes(ctdldir, 1);
149 COMPUTE_DIRECTORY(ctdl_run_dir);
150 StripSlashes(ctdl_run_dir, 1);
153 #ifndef HAVE_AUTO_ETC_DIR
156 basedir=AUTO_ETC_DIR;
158 COMPUTE_DIRECTORY(ctdl_autoetc_dir);
159 StripSlashes(ctdl_autoetc_dir, 1);
167 COMPUTE_DIRECTORY(ctdl_etc_dir);
168 StripSlashes(ctdl_etc_dir, 1);
172 snprintf(file_citadel_rc,
173 sizeof file_citadel_rc,
176 StripSlashes(file_citadel_rc, 0);
178 snprintf(file_citadel_socket,
179 sizeof file_citadel_socket,
182 StripSlashes(file_citadel_socket, 0);
184 snprintf(file_citadel_config,
185 sizeof file_citadel_config,
188 StripSlashes(file_citadel_config, 0);
190 DBG_PRINT(ctdl_run_dir);
191 DBG_PRINT(file_citadel_socket);
192 DBG_PRINT(ctdl_etc_dir);
193 DBG_PRINT(file_citadel_rc);
196 void setCryptoStatusHook(void (*hook)(char *s)) {
200 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC *ipc, void (*hook)(int state)) {
201 ipc->network_status_cb = hook;
205 char instant_msgs = 0;
208 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes);
209 static void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
211 static void serv_read_ssl(CtdlIPC *ipc, char *buf, unsigned int bytes);
212 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
213 static void endtls(SSL *ssl);
214 #ifdef THREADED_CLIENT
215 static unsigned long id_callback(void);
216 #endif /* THREADED_CLIENT */
217 #endif /* HAVE_OPENSSL */
218 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf);
219 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf);
223 const char *svn_revision(void);
226 * Does nothing. The server should always return 200.
228 int CtdlIPCNoop(CtdlIPC *ipc)
232 return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
237 * Does nothing interesting. The server should always return 200
238 * along with your string.
240 int CtdlIPCEcho(CtdlIPC *ipc, const char *arg, char *cret)
246 if (!cret) return -2;
248 aaa = (char *)malloc((size_t)(strlen(arg) + 6));
251 sprintf(aaa, "ECHO %s", arg);
252 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
259 * Asks the server to close the connecction.
260 * Should always return 200.
262 int CtdlIPCQuit(CtdlIPC *ipc)
264 register int ret = 221; /* Default to successful quit */
268 if (ipc->sock > -1) {
269 CtdlIPC_putline(ipc, "QUIT");
270 CtdlIPC_getline(ipc, aaa);
275 SSL_shutdown(ipc->ssl);
279 shutdown(ipc->sock, 2); /* Close connection; we're dead */
287 * Asks the server to log out. Should always return 200, even if no user
288 * was logged in. The user will not be logged in after this!
290 int CtdlIPCLogout(CtdlIPC *ipc)
296 CtdlIPC_putline(ipc, "LOUT");
297 CtdlIPC_getline(ipc, aaa);
305 * First stage of authentication - pass the username. Returns 300 if the
306 * username is able to log in, with the username correctly spelled in cret.
307 * Returns various 500 error codes if the user doesn't exist, etc.
309 int CtdlIPCTryLogin(CtdlIPC *ipc, const char *username, char *cret)
314 if (!username) return -2;
315 if (!cret) return -2;
317 aaa = (char *)malloc((size_t)(strlen(username) + 6));
320 sprintf(aaa, "USER %s", username);
321 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
328 * Second stage of authentication - provide password. The server returns
329 * 200 and several arguments in cret relating to the user's account.
331 int CtdlIPCTryPassword(CtdlIPC *ipc, const char *passwd, char *cret)
336 if (!passwd) return -2;
337 if (!cret) return -2;
339 aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
342 sprintf(aaa, "PASS %s", passwd);
343 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
350 * Second stage of authentication - provide password. The server returns
351 * 200 and several arguments in cret relating to the user's account.
353 int CtdlIPCTryApopPassword(CtdlIPC *ipc, const char *response, char *cret)
358 if (!response) return -2;
359 if (!cret) return -2;
361 aaa = (char *)malloc((size_t)(strlen(response) + 6));
364 sprintf(aaa, "PAS2 %s", response);
365 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
372 * Create a new user. This returns 200 plus the same arguments as TryPassword
373 * if selfservice is nonzero, unless there was a problem creating the account.
374 * If selfservice is zero, creates a new user but does not log out the existing
375 * user - intended for use by system administrators to create accounts on
376 * behalf of other users.
378 int CtdlIPCCreateUser(CtdlIPC *ipc, const char *username, int selfservice, char *cret)
383 if (!username) return -2;
384 if (!cret) return -2;
386 aaa = (char *)malloc((size_t)(strlen(username) + 6));
389 sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
390 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
397 * Changes the user's password. Returns 200 if changed, errors otherwise.
399 int CtdlIPCChangePassword(CtdlIPC *ipc, const char *passwd, char *cret)
404 if (!passwd) return -2;
405 if (!cret) return -2;
407 aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
410 sprintf(aaa, "SETP %s", passwd);
411 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
418 /* Caller must free the march list */
419 /* Room types are defined in enum RoomList; keep these in sync! */
420 /* floor is -1 for all, or floornum */
421 int CtdlIPCKnownRooms(CtdlIPC *ipc, enum RoomList which, int floor, struct march **listing, char *cret)
424 struct march *march = NULL;
425 static char *proto[] =
426 {"LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
431 if (!listing) return -2;
432 if (*listing) return -2; /* Free the listing first */
433 if (!cret) return -2;
434 /* if (which < 0 || which > 4) return -2; */
435 if (floor < -1) return -2; /* Can't validate upper bound, sorry */
437 sprintf(aaa, "%s %d", proto[which], floor);
438 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
439 if (ret / 100 == 1) {
442 while (bbb && strlen(bbb)) {
445 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
447 memmove(bbb, bbb + a + 1, strlen(bbb) - a);
448 mptr = (struct march *) malloc(sizeof (struct march));
451 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
452 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
453 mptr->march_floor = (char) extract_int(aaa, 2);
454 mptr->march_order = (char) extract_int(aaa, 3);
455 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
456 mptr->march_access = (char) extract_int(aaa, 5);
463 while (mptr2->next != NULL)
477 /* Caller must free the struct ctdluser; caller may pass an existing one */
478 int CtdlIPCGetConfig(CtdlIPC *ipc, struct ctdluser **uret, char *cret)
482 if (!cret) return -2;
483 if (!uret) return -2;
484 if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof (struct ctdluser));
485 if (!*uret) return -1;
487 ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
488 if (ret / 100 == 2) {
489 uret[0]->flags = extract_int(cret, 2);
496 int CtdlIPCSetConfig(CtdlIPC *ipc, struct ctdluser *uret, char *cret)
500 if (!uret) return -2;
501 if (!cret) return -2;
507 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
512 int CtdlIPCRenameUser(CtdlIPC *ipc, char *oldname, char *newname, char *cret)
517 if (!oldname) return -2;
518 if (!newname) return -2;
519 if (!cret) return -2;
521 snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
522 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
528 int CtdlIPCGotoRoom(CtdlIPC *ipc, const char *room, const char *passwd,
529 struct ctdlipcroom **rret, char *cret)
534 if (!cret) return -2;
535 if (!rret) return -2;
536 if (!*rret) *rret = (struct ctdlipcroom *)calloc(1, sizeof (struct ctdlipcroom));
537 if (!*rret) return -1;
540 aaa = (char *)malloc(strlen(room) + strlen(passwd) + 7);
545 sprintf(aaa, "GOTO %s|%s", room, passwd);
547 aaa = (char *)malloc(strlen(room) + 6);
552 sprintf(aaa, "GOTO %s", room);
554 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
555 if (ret / 100 == 2) {
556 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
557 rret[0]->RRunread = extract_long(cret, 1);
558 rret[0]->RRtotal = extract_long(cret, 2);
559 rret[0]->RRinfoupdated = extract_int(cret, 3);
560 rret[0]->RRflags = extract_int(cret, 4);
561 rret[0]->RRhighest = extract_long(cret, 5);
562 rret[0]->RRlastread = extract_long(cret, 6);
563 rret[0]->RRismailbox = extract_int(cret, 7);
564 rret[0]->RRaide = extract_int(cret, 8);
565 rret[0]->RRnewmail = extract_long(cret, 9);
566 rret[0]->RRfloor = extract_int(cret, 10);
567 rret[0]->RRcurrentview = extract_int(cret, 11);
568 rret[0]->RRdefaultview = extract_int(cret, 12);
569 /* position 13 is a trash folder flag ... irrelevant in this client */
570 rret[0]->RRflags2 = extract_int(cret, 14);
581 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
582 /* whicharg is number of messages, applies to last, first, gt, lt */
583 int CtdlIPCGetMessages(CtdlIPC *ipc, enum MessageList which, int whicharg,
584 const char *mtemplate, unsigned long **mret, char *cret)
587 register unsigned long count = 0;
588 static char *proto[] =
589 { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
594 if (!cret) return -2;
595 if (!mret) return -2;
596 if (*mret) return -2;
597 if (which < 0 || which > 6) return -2;
600 sprintf(aaa, "MSGS %s||%d", proto[which],
601 (mtemplate) ? 1 : 0);
603 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg,
604 (mtemplate) ? 1 : 0);
605 if (mtemplate) count = strlen(mtemplate);
606 ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
610 *mret = (unsigned long *)calloc(1, sizeof(unsigned long));
613 while (bbb && strlen(bbb)) {
614 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
615 remove_token(bbb, 0, '\n');
616 *mret = (unsigned long *)realloc(*mret, (size_t)((count + 2) *
617 sizeof (unsigned long)));
619 (*mret)[count++] = atol(aaa);
631 int CtdlIPCGetSingleMessage(CtdlIPC *ipc, long msgnum, int headers, int as_mime,
632 struct ctdlipcmessage **mret, char *cret)
638 int multipart_hunting = 0;
639 char multipart_prefix[128];
642 if (!cret) return -1;
643 if (!mret) return -1;
644 if (!*mret) *mret = (struct ctdlipcmessage *)calloc(1, sizeof (struct ctdlipcmessage));
645 if (!*mret) return -1;
646 if (!msgnum) return -1;
648 strcpy(encoding, "");
649 strcpy(mret[0]->content_type, "");
650 sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
651 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
652 if (ret / 100 == 1) {
654 strcpy(mret[0]->mime_chosen, "1"); /* Default chosen-part is "1" */
655 while (strlen(bbb) > 4 && bbb[4] == '=') {
656 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
657 remove_token(bbb, 0, '\n');
659 if (!strncasecmp(aaa, "nhdr=yes", 8))
661 else if (!strncasecmp(aaa, "from=", 5))
662 safestrncpy(mret[0]->author, &aaa[5], SIZ);
663 else if (!strncasecmp(aaa, "type=", 5))
664 mret[0]->type = atoi(&aaa[5]);
665 else if (!strncasecmp(aaa, "msgn=", 5))
666 safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
667 else if (!strncasecmp(aaa, "subj=", 5))
668 safestrncpy(mret[0]->subject, &aaa[5], SIZ);
669 else if (!strncasecmp(aaa, "rfca=", 5))
670 safestrncpy(mret[0]->email, &aaa[5], SIZ);
671 else if (!strncasecmp(aaa, "hnod=", 5))
672 safestrncpy(mret[0]->hnod, &aaa[5], SIZ);
673 else if (!strncasecmp(aaa, "room=", 5))
674 safestrncpy(mret[0]->room, &aaa[5], SIZ);
675 else if (!strncasecmp(aaa, "node=", 5))
676 safestrncpy(mret[0]->node, &aaa[5], SIZ);
677 else if (!strncasecmp(aaa, "rcpt=", 5))
678 safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
679 else if (!strncasecmp(aaa, "wefw=", 5))
680 safestrncpy(mret[0]->references, &aaa[5], SIZ);
681 else if (!strncasecmp(aaa, "time=", 5))
682 mret[0]->time = atol(&aaa[5]);
684 /* Multipart/alternative prefix & suffix strings help
685 * us to determine which part we want to download.
687 else if (!strncasecmp(aaa, "pref=", 5)) {
688 extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
689 if (!strcasecmp(multipart_prefix,
690 "multipart/alternative")) {
694 else if (!strncasecmp(aaa, "suff=", 5)) {
695 extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
696 if (!strcasecmp(multipart_prefix,
697 "multipart/alternative")) {
702 else if (!strncasecmp(aaa, "part=", 5)) {
703 struct parts *ptr, *chain;
705 ptr = (struct parts *)calloc(1, sizeof (struct parts));
708 /* Fill the buffers for the caller */
709 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
710 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
711 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
712 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
713 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
714 ptr->length = extract_long(&aaa[5], 5);
715 if (!mret[0]->attachments)
716 mret[0]->attachments = ptr;
718 chain = mret[0]->attachments;
724 /* Now handle multipart/alternative */
725 if (multipart_hunting > 0) {
726 if ( (!strcasecmp(ptr->mimetype,
728 || (!strcasecmp(ptr->mimetype,
730 strcpy(mret[0]->mime_chosen,
738 /* Eliminate "text\n" */
739 remove_token(bbb, 0, '\n');
741 /* If doing a MIME thing, pull out the extra headers */
744 if (!strncasecmp(bbb, "Content-type:", 13)) {
745 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
746 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
747 striplt(mret[0]->content_type);
749 /* strip out ";charset=" portion. FIXME do something with
750 * the charset (like... convert it) instead of just throwing
753 if (strstr(mret[0]->content_type, ";") != NULL) {
754 strcpy(strstr(mret[0]->content_type, ";"), "");
758 if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
759 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
760 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
761 striplt(mret[0]->mime_chosen);
763 if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
764 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
765 strcpy(encoding, &encoding[26]);
768 remove_token(bbb, 0, '\n');
769 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
770 remove_token(bbb, 0, '\n');
777 if ( (!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable")) ) {
779 int bytes_decoded = 0;
780 ccc = malloc(strlen(bbb) + 32768);
781 if (!strcasecmp(encoding, "base64")) {
782 bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
784 else if (!strcasecmp(encoding, "quoted-printable")) {
785 bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
787 ccc[bytes_decoded] = 0;
792 /* FIXME: Strip trailing whitespace */
793 bbb = (char *)realloc(bbb, (size_t)(strlen(bbb) + 1));
796 bbb = (char *)realloc(bbb, 1);
806 int CtdlIPCWhoKnowsRoom(CtdlIPC *ipc, char **listing, char *cret)
811 if (!cret) return -2;
812 if (!listing) return -2;
813 if (*listing) return -2;
815 ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
821 int CtdlIPCServerInfo(CtdlIPC *ipc, char *cret)
825 char *listing = NULL;
828 if (!cret) return -2;
830 ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
831 if (ret / 100 == 1) {
834 while (*listing && strlen(listing)) {
835 extract_token(buf, listing, 0, '\n', sizeof buf);
836 remove_token(listing, 0, '\n');
838 case 0: ipc->ServInfo.pid = atoi(buf);
840 case 1: strcpy(ipc->ServInfo.nodename,buf);
842 case 2: strcpy(ipc->ServInfo.humannode,buf);
844 case 3: strcpy(ipc->ServInfo.fqdn,buf);
846 case 4: strcpy(ipc->ServInfo.software,buf);
848 case 5: ipc->ServInfo.rev_level = atoi(buf);
850 case 6: strcpy(ipc->ServInfo.site_location,buf);
852 case 7: strcpy(ipc->ServInfo.sysadm,buf);
854 case 9: strcpy(ipc->ServInfo.moreprompt,buf);
856 case 10: ipc->ServInfo.ok_floors = atoi(buf);
858 case 11: ipc->ServInfo.paging_level = atoi(buf);
860 case 13: ipc->ServInfo.supports_qnop = atoi(buf);
862 case 14: ipc->ServInfo.supports_ldap = atoi(buf);
864 case 15: ipc->ServInfo.newuser_disabled = atoi(buf);
866 case 16: strcpy(ipc->ServInfo.default_cal_zone, buf);
868 case 17: ipc->ServInfo.load_avg = atof(buf);
870 case 18: ipc->ServInfo.worker_avg = atof(buf);
872 case 19: ipc->ServInfo.thread_count = atoi(buf);
874 case 20: ipc->ServInfo.has_sieve = atoi(buf);
876 case 21: ipc->ServInfo.fulltext_enabled = atoi(buf);
878 case 22: strcpy(ipc->ServInfo.svn_revision, buf);
880 case 24: ipc->ServInfo.guest_logins = atoi(buf);
886 if (listing) free(listing);
892 int CtdlIPCReadDirectory(CtdlIPC *ipc, char **listing, char *cret)
897 if (!cret) return -2;
898 if (!listing) return -2;
899 if (*listing) return -2;
901 ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
907 * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
909 int CtdlIPCSetLastRead(CtdlIPC *ipc, long msgnum, char *cret)
914 if (!cret) return -2;
917 sprintf(aaa, "SLRP %ld", msgnum);
920 sprintf(aaa, "SLRP HIGHEST");
922 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
928 int CtdlIPCInviteUserToRoom(CtdlIPC *ipc, const char *username, char *cret)
933 if (!cret) return -2;
934 if (!username) return -2;
936 aaa = (char *)malloc(strlen(username) + 6);
939 sprintf(aaa, "INVT %s", username);
940 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
947 int CtdlIPCKickoutUserFromRoom(CtdlIPC *ipc, const char *username, char *cret)
952 if (!cret) return -1;
953 if (!username) return -1;
955 aaa = (char *)malloc(strlen(username) + 6);
957 sprintf(aaa, "KICK %s", username);
958 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
965 int CtdlIPCGetRoomAttributes(CtdlIPC *ipc, struct ctdlroom **qret, char *cret)
969 if (!cret) return -2;
970 if (!qret) return -2;
971 if (!*qret) *qret = (struct ctdlroom *)calloc(1, sizeof (struct ctdlroom));
972 if (!*qret) return -1;
974 ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
975 if (ret / 100 == 2) {
976 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
977 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
978 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
979 qret[0]->QRflags = extract_int(cret, 3);
980 qret[0]->QRfloor = extract_int(cret, 4);
981 qret[0]->QRorder = extract_int(cret, 5);
982 qret[0]->QRdefaultview = extract_int(cret, 6);
983 qret[0]->QRflags2 = extract_int(cret, 7);
990 /* set forget to kick all users out of room */
991 int CtdlIPCSetRoomAttributes(CtdlIPC *ipc, int forget, struct ctdlroom *qret, char *cret)
996 if (!cret) return -2;
997 if (!qret) return -2;
999 aaa = (char *)malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) +
1000 strlen(qret->QRdirname) + 64);
1001 if (!aaa) return -1;
1003 sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
1004 qret->QRname, qret->QRpasswd, qret->QRdirname,
1005 qret->QRflags, forget, qret->QRfloor, qret->QRorder,
1006 qret->QRdefaultview, qret->QRflags2);
1007 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1014 int CtdlIPCGetRoomAide(CtdlIPC *ipc, char *cret)
1016 if (!cret) return -1;
1018 return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1023 int CtdlIPCSetRoomAide(CtdlIPC *ipc, const char *username, char *cret)
1028 if (!cret) return -2;
1029 if (!username) return -2;
1031 aaa = (char *)malloc(strlen(username) + 6);
1032 if (!aaa) return -1;
1034 sprintf(aaa, "SETA %s", username);
1035 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1042 int CtdlIPCPostMessage(CtdlIPC *ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1048 if (!cret) return -2;
1051 if (mr->references) {
1052 for (ptr=mr->references; *ptr != 0; ++ptr) {
1053 if (*ptr == '|') *ptr = '!';
1057 snprintf(cmd, sizeof cmd,
1058 "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1059 mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1060 ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL,
1062 if ((flag == 0) && (subject_required != NULL)) {
1063 /* Is the server strongly recommending that the user enter a message subject? */
1064 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1065 *subject_required = extract_int(&cret[4], 1);
1075 int CtdlIPCRoomInfo(CtdlIPC *ipc, char **iret, char *cret)
1079 if (!cret) return -2;
1080 if (!iret) return -2;
1081 if (*iret) return -2;
1083 return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1088 int CtdlIPCDeleteMessage(CtdlIPC *ipc, long msgnum, char *cret)
1092 if (!cret) return -2;
1093 if (!msgnum) return -2;
1095 sprintf(aaa, "DELE %ld", msgnum);
1096 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1101 int CtdlIPCMoveMessage(CtdlIPC *ipc, int copy, long msgnum, const char *destroom, char *cret)
1106 if (!cret) return -2;
1107 if (!destroom) return -2;
1108 if (!msgnum) return -2;
1110 aaa = (char *)malloc(strlen(destroom) + 28);
1111 if (!aaa) return -1;
1113 sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1114 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1121 int CtdlIPCDeleteRoom(CtdlIPC *ipc, int for_real, char *cret)
1125 if (!cret) return -2;
1127 sprintf(aaa, "KILL %d", for_real);
1128 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1133 int CtdlIPCCreateRoom(CtdlIPC *ipc, int for_real, const char *roomname, int type,
1134 const char *password, int floor, char *cret)
1139 if (!cret) return -2;
1140 if (!roomname) return -2;
1143 aaa = (char *)malloc(strlen(roomname) + strlen(password) + 40);
1144 if (!aaa) return -1;
1145 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type,
1148 aaa = (char *)malloc(strlen(roomname) + 40);
1149 if (!aaa) return -1;
1150 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type,
1153 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1160 int CtdlIPCForgetRoom(CtdlIPC *ipc, char *cret)
1162 if (!cret) return -2;
1164 return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1169 int CtdlIPCSystemMessage(CtdlIPC *ipc, const char *message, char **mret, char *cret)
1175 if (!cret) return -2;
1176 if (!mret) return -2;
1177 if (*mret) return -2;
1178 if (!message) return -2;
1180 aaa = (char *)malloc(strlen(message) + 6);
1181 if (!aaa) return -1;
1183 sprintf(aaa, "MESG %s", message);
1184 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1191 int CtdlIPCNextUnvalidatedUser(CtdlIPC *ipc, char *cret)
1193 if (!cret) return -2;
1195 return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1200 int CtdlIPCGetUserRegistration(CtdlIPC *ipc, const char *username, char **rret, char *cret)
1206 if (!cret) return -2;
1207 if (!rret) return -2;
1208 if (*rret) return -2;
1211 aaa = (char *)malloc(strlen(username) + 6);
1213 aaa = (char *)malloc(12);
1214 if (!aaa) return -1;
1217 sprintf(aaa, "GREG %s", username);
1219 sprintf(aaa, "GREG _SELF_");
1220 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1227 int CtdlIPCValidateUser(CtdlIPC *ipc, const char *username, int axlevel, char *cret)
1232 if (!cret) return -2;
1233 if (!username) return -2;
1234 if (axlevel < AxDeleted || axlevel > AxAideU) return -2;
1236 aaa = (char *)malloc(strlen(username) + 17);
1237 if (!aaa) return -1;
1239 sprintf(aaa, "VALI %s|%d", username, axlevel);
1240 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1247 int CtdlIPCSetRoomInfo(CtdlIPC *ipc, int for_real, const char *info, char *cret)
1251 if (!cret) return -1;
1252 if (!info) return -1;
1254 sprintf(aaa, "EINF %d", for_real);
1255 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1260 int CtdlIPCUserListing(CtdlIPC *ipc, char *searchstring, char **listing, char *cret)
1266 if (!cret) return -1;
1267 if (!listing) return -1;
1268 if (*listing) return -1;
1269 if (!searchstring) return -1;
1271 cmd = malloc(strlen(searchstring) + 10);
1272 sprintf(cmd, "LIST %s", searchstring);
1274 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1281 int CtdlIPCSetRegistration(CtdlIPC *ipc, const char *info, char *cret)
1283 if (!cret) return -1;
1284 if (!info) return -1;
1286 return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info),
1292 int CtdlIPCMiscCheck(CtdlIPC *ipc, struct ctdlipcmisc *chek, char *cret)
1296 if (!cret) return -1;
1297 if (!chek) return -1;
1299 ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1300 if (ret / 100 == 2) {
1301 chek->newmail = extract_long(cret, 0);
1302 chek->needregis = extract_int(cret, 1);
1303 chek->needvalid = extract_int(cret, 2);
1310 int CtdlIPCDeleteFile(CtdlIPC *ipc, const char *filename, char *cret)
1315 if (!cret) return -2;
1316 if (!filename) return -2;
1318 aaa = (char *)malloc(strlen(filename) + 6);
1319 if (!aaa) return -1;
1321 sprintf(aaa, "DELF %s", filename);
1322 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1329 int CtdlIPCMoveFile(CtdlIPC *ipc, const char *filename, const char *destroom, char *cret)
1334 if (!cret) return -2;
1335 if (!filename) return -2;
1336 if (!destroom) return -2;
1338 aaa = (char *)malloc(strlen(filename) + strlen(destroom) + 7);
1339 if (!aaa) return -1;
1341 sprintf(aaa, "MOVF %s|%s", filename, destroom);
1342 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1349 int CtdlIPCOnlineUsers(CtdlIPC *ipc, char **listing, time_t *stamp, char *cret)
1354 if (!cret) return -1;
1355 if (!listing) return -1;
1356 if (*listing) return -1;
1358 *stamp = CtdlIPCServerTime(ipc, cret);
1360 *stamp = time(NULL);
1361 ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1367 int CtdlIPCFileDownload(CtdlIPC *ipc, const char *filename, void **buf,
1369 void (*progress_gauge_callback)
1370 (CtdlIPC*, unsigned long, unsigned long),
1379 if (!cret) return -2;
1380 if (!filename) return -2;
1381 if (!buf) return -2;
1382 if (*buf) return -2;
1383 if (ipc->downloading) return -2;
1385 aaa = (char *)malloc(strlen(filename) + 6);
1386 if (!aaa) return -1;
1388 sprintf(aaa, "OPEN %s", filename);
1389 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1391 if (ret / 100 == 2) {
1392 ipc->downloading = 1;
1393 bytes = extract_long(cret, 0);
1394 last_mod = extract_int(cret, 1);
1395 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1397 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume,
1398 progress_gauge_callback, cret);
1400 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1401 progress_gauge_callback, cret);
1404 ret = CtdlIPCEndDownload(ipc, cret);
1406 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1407 filename, mimetype);
1414 int CtdlIPCAttachmentDownload(CtdlIPC *ipc, long msgnum, const char *part,
1416 void (*progress_gauge_callback)
1417 (CtdlIPC*, unsigned long, unsigned long),
1427 if (!cret) return -2;
1428 if (!buf) return -2;
1429 if (*buf) return -2;
1430 if (!part) return -2;
1431 if (!msgnum) return -2;
1432 if (ipc->downloading) return -2;
1434 sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1435 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1436 if (ret / 100 == 2) {
1437 ipc->downloading = 1;
1438 bytes = extract_long(cret, 0);
1439 last_mod = extract_int(cret, 1);
1440 extract_token(filename, cret, 2, '|', sizeof filename);
1441 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1442 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1443 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1444 ret = CtdlIPCEndDownload(ipc, cret);
1446 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1447 filename, mimetype);
1454 int CtdlIPCImageDownload(CtdlIPC *ipc, const char *filename, void **buf,
1455 void (*progress_gauge_callback)
1456 (CtdlIPC*, unsigned long, unsigned long),
1465 if (!cret) return -1;
1466 if (!buf) return -1;
1467 if (*buf) return -1;
1468 if (!filename) return -1;
1469 if (ipc->downloading) return -1;
1471 aaa = (char *)malloc(strlen(filename) + 6);
1472 if (!aaa) return -1;
1474 sprintf(aaa, "OIMG %s", filename);
1475 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1477 if (ret / 100 == 2) {
1478 ipc->downloading = 1;
1479 bytes = extract_long(cret, 0);
1480 last_mod = extract_int(cret, 1);
1481 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1482 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1483 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1484 ret = CtdlIPCEndDownload(ipc, cret);
1486 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1487 filename, mimetype);
1494 int CtdlIPCFileUpload(CtdlIPC *ipc, const char *save_as, const char *comment,
1496 void (*progress_gauge_callback)
1497 (CtdlIPC*, unsigned long, unsigned long),
1503 char MimeTestBuf[64];
1504 const char *MimeType;
1507 if (!cret) return -1;
1508 if (!save_as) return -1;
1509 if (!comment) return -1;
1510 if (!path) return -1;
1511 if (!*path) return -1;
1512 if (ipc->uploading) return -1;
1514 uploadFP = fopen(path, "r");
1515 if (!uploadFP) return -2;
1517 len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1522 MimeType = GuessMimeType(&MimeTestBuf[0], len);
1523 aaa = (char *)malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1524 if (!aaa) return -1;
1526 sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1527 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1529 if (ret / 100 == 2) {
1531 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1532 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1540 int CtdlIPCImageUpload(CtdlIPC *ipc, int for_real, const char *path,
1541 const char *save_as,
1542 void (*progress_gauge_callback)
1543 (CtdlIPC*, unsigned long, unsigned long),
1549 char MimeTestBuf[64];
1550 const char *MimeType;
1553 if (!cret) return -1;
1554 if (!save_as) return -1;
1555 if (!path && for_real) return -1;
1556 if (!*path && for_real) return -1;
1557 if (ipc->uploading) return -1;
1559 aaa = (char *)malloc(strlen(save_as) + 17);
1560 if (!aaa) return -1;
1562 uploadFP = fopen(path, "r");
1563 if (!uploadFP) return -2;
1565 len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1569 MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1571 sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1572 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1574 if (ret / 100 == 2 && for_real) {
1576 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1577 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1585 int CtdlIPCQueryUsername(CtdlIPC *ipc, const char *username, char *cret)
1590 if (!cret) return -2;
1591 if (!username) return -2;
1593 aaa = (char *)malloc(strlen(username) + 6);
1594 if (!aaa) return -1;
1596 sprintf(aaa, "QUSR %s", username);
1597 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1604 int CtdlIPCFloorListing(CtdlIPC *ipc, char **listing, char *cret)
1608 if (!cret) return -2;
1609 if (!listing) return -2;
1610 if (*listing) return -2;
1612 return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1617 int CtdlIPCCreateFloor(CtdlIPC *ipc, int for_real, const char *name, char *cret)
1622 if (!cret) return -2;
1623 if (!name) return -2;
1625 sprintf(aaa, "CFLR %s|%d", name, for_real);
1626 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1632 int CtdlIPCDeleteFloor(CtdlIPC *ipc, int for_real, int floornum, char *cret)
1636 if (!cret) return -1;
1637 if (floornum < 0) return -1;
1639 sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1640 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1645 int CtdlIPCEditFloor(CtdlIPC *ipc, int floornum, const char *floorname, char *cret)
1650 if (!cret) return -2;
1651 if (!floorname) return -2;
1652 if (floornum < 0) return -2;
1654 sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1655 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1663 * You only need to fill out hostname, the defaults will be used if any of the
1664 * other fields are not set properly.
1666 int CtdlIPCIdentifySoftware(CtdlIPC *ipc, int developerid, int clientid,
1667 int revision, const char *software_name, const char *hostname,
1673 if (developerid < 0 || clientid < 0 || revision < 0 ||
1677 revision = CLIENT_VERSION - 600;
1678 software_name = "Citadel (libcitadel)";
1680 if (!hostname) return -2;
1682 aaa = (char *)malloc(strlen(software_name) + strlen(hostname) + 29);
1683 if (!aaa) return -1;
1685 sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid,
1686 revision, software_name, hostname);
1687 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1694 int CtdlIPCSendInstantMessage(CtdlIPC *ipc, const char *username, const char *text,
1700 if (!cret) return -2;
1701 if (!username) return -2;
1703 aaa = (char *)malloc(strlen(username) + 8);
1704 if (!aaa) return -1;
1707 sprintf(aaa, "SEXP %s|-", username);
1708 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text),
1711 sprintf(aaa, "SEXP %s||", username);
1712 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1720 int CtdlIPCGetInstantMessage(CtdlIPC *ipc, char **listing, char *cret)
1724 if (!cret) return -2;
1725 if (!listing) return -2;
1726 if (*listing) return -2;
1728 return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1733 /* mode is 0 = enable, 1 = disable, 2 = status */
1734 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC *ipc, int mode, char *cret)
1738 if (!cret) return -2;
1740 sprintf(aaa, "DEXP %d", mode);
1741 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1746 int CtdlIPCSetBio(CtdlIPC *ipc, char *bio, char *cret)
1748 if (!cret) return -2;
1749 if (!bio) return -2;
1751 return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio),
1757 int CtdlIPCGetBio(CtdlIPC *ipc, const char *username, char **listing, char *cret)
1763 if (!cret) return -2;
1764 if (!username) return -2;
1765 if (!listing) return -2;
1766 if (*listing) return -2;
1768 aaa = (char *)malloc(strlen(username) + 6);
1769 if (!aaa) return -1;
1771 sprintf(aaa, "RBIO %s", username);
1772 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1779 int CtdlIPCListUsersWithBios(CtdlIPC *ipc, char **listing, char *cret)
1783 if (!cret) return -2;
1784 if (!listing) return -2;
1785 if (*listing) return -2;
1787 return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1792 int CtdlIPCStealthMode(CtdlIPC *ipc, int mode, char *cret)
1796 if (!cret) return -1;
1798 sprintf(aaa, "STEL %d", mode ? 1 : 0);
1799 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1804 int CtdlIPCTerminateSession(CtdlIPC *ipc, int sid, char *cret)
1808 if (!cret) return -1;
1810 sprintf(aaa, "TERM %d", sid);
1811 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1816 int CtdlIPCTerminateServerNow(CtdlIPC *ipc, char *cret)
1818 if (!cret) return -1;
1820 return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1825 int CtdlIPCTerminateServerScheduled(CtdlIPC *ipc, int mode, char *cret)
1829 if (!cret) return -1;
1831 sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1832 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1837 int CtdlIPCEnterSystemMessage(CtdlIPC *ipc, const char *filename, const char *text,
1843 if (!cret) return -2;
1844 if (!text) return -2;
1845 if (!filename) return -2;
1847 aaa = (char *)malloc(strlen(filename) + 6);
1848 if (!aaa) return -1;
1850 sprintf(aaa, "EMSG %s", filename);
1851 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1858 int CtdlIPCChangeHostname(CtdlIPC *ipc, const char *hostname, char *cret)
1863 if (!cret) return -2;
1864 if (!hostname) return -2;
1866 aaa = (char *)malloc(strlen(hostname) + 6);
1867 if (!aaa) return -1;
1869 sprintf(aaa, "HCHG %s", hostname);
1870 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1877 int CtdlIPCChangeRoomname(CtdlIPC *ipc, const char *roomname, char *cret)
1882 if (!cret) return -2;
1883 if (!roomname) return -2;
1885 aaa = (char *)malloc(strlen(roomname) + 6);
1886 if (!aaa) return -1;
1888 sprintf(aaa, "RCHG %s", roomname);
1889 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1896 int CtdlIPCChangeUsername(CtdlIPC *ipc, const char *username, char *cret)
1901 if (!cret) return -2;
1902 if (!username) return -2;
1904 aaa = (char *)malloc(strlen(username) + 6);
1905 if (!aaa) return -1;
1907 sprintf(aaa, "UCHG %s", username);
1908 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1915 /* This function returns the actual server time reported, or 0 if error */
1916 time_t CtdlIPCServerTime(CtdlIPC *ipc, char *cret)
1918 register time_t tret;
1921 ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1922 if (ret / 100 == 2) {
1923 tret = extract_long(cret, 0);
1932 int CtdlIPCAideGetUserParameters(CtdlIPC *ipc, const char *who,
1933 struct ctdluser **uret, char *cret)
1938 if (!cret) return -2;
1939 if (!uret) return -2;
1940 if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof(struct ctdluser));
1941 if (!*uret) return -1;
1943 sprintf(aaa, "AGUP %s", who);
1944 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1946 if (ret / 100 == 2) {
1947 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1948 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1949 uret[0]->flags = extract_int(cret, 2);
1950 uret[0]->timescalled = extract_long(cret, 3);
1951 uret[0]->posted = extract_long(cret, 4);
1952 uret[0]->axlevel = extract_int(cret, 5);
1953 uret[0]->usernum = extract_long(cret, 6);
1954 uret[0]->lastcall = extract_long(cret, 7);
1955 uret[0]->USuserpurge = extract_int(cret, 8);
1962 int CtdlIPCAideSetUserParameters(CtdlIPC *ipc, const struct ctdluser *uret, char *cret)
1967 if (!cret) return -2;
1968 if (!uret) return -2;
1970 aaa = (char *)malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1971 if (!aaa) return -1;
1973 sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
1974 uret->fullname, uret->password, uret->flags,
1975 uret->timescalled, uret->posted, uret->axlevel,
1976 uret->usernum, uret->lastcall, uret->USuserpurge);
1977 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1984 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1985 /* caller must free the struct ExpirePolicy */
1986 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC *ipc, GPEXWhichPolicy which,
1987 struct ExpirePolicy **policy, char *cret)
1989 static char *proto[] = {
1993 strof(mailboxespolicy)
1998 if (!cret) return -2;
1999 if (!policy) return -2;
2000 if (!*policy) *policy = (struct ExpirePolicy *)calloc(1, sizeof(struct ExpirePolicy));
2001 if (!*policy) return -1;
2002 if (which < 0 || which > 3) return -2;
2004 sprintf(cmd, "GPEX %s", proto[which]);
2005 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2006 if (ret / 100 == 2) {
2007 policy[0]->expire_mode = extract_int(cret, 0);
2008 policy[0]->expire_value = extract_int(cret, 1);
2015 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2016 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2017 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC *ipc, int which,
2018 struct ExpirePolicy *policy, char *cret)
2021 char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2023 if (!cret) return -2;
2024 if (which < 0 || which > 3) return -2;
2025 if (!policy) return -2;
2026 if (policy->expire_mode < 0 || policy->expire_mode > 3) return -2;
2027 if (policy->expire_mode >= 2 && policy->expire_value < 1) return -2;
2029 sprintf(aaa, "SPEX %s|%d|%d", whichvals[which],
2030 policy->expire_mode, policy->expire_value);
2031 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2036 int CtdlIPCGetSystemConfig(CtdlIPC *ipc, char **listing, char *cret)
2040 if (!cret) return -2;
2041 if (!listing) return -2;
2042 if (*listing) return -2;
2044 return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0,
2045 listing, &bytes, cret);
2050 int CtdlIPCSetSystemConfig(CtdlIPC *ipc, const char *listing, char *cret)
2052 if (!cret) return -2;
2053 if (!listing) return -2;
2055 return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing),
2061 int CtdlIPCGetSystemConfigByType(CtdlIPC *ipc, const char *mimetype,
2062 char **listing, char *cret)
2068 if (!cret) return -2;
2069 if (!mimetype) return -2;
2070 if (!listing) return -2;
2071 if (*listing) return -2;
2073 aaa = malloc(strlen(mimetype) + 13);
2074 if (!aaa) return -1;
2075 sprintf(aaa, "CONF GETSYS|%s", mimetype);
2076 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0,
2077 listing, &bytes, cret);
2084 int CtdlIPCSetSystemConfigByType(CtdlIPC *ipc, const char *mimetype,
2085 const char *listing, char *cret)
2090 if (!cret) return -2;
2091 if (!mimetype) return -2;
2092 if (!listing) return -2;
2094 aaa = malloc(strlen(mimetype) + 13);
2095 if (!aaa) return -1;
2096 sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2097 ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing),
2105 int CtdlIPCGetRoomNetworkConfig(CtdlIPC *ipc, char **listing, char *cret)
2109 if (!cret) return -2;
2110 if (!listing) return -2;
2111 if (*listing) return -2;
2113 return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0,
2114 listing, &bytes, cret);
2119 int CtdlIPCSetRoomNetworkConfig(CtdlIPC *ipc, const char *listing, char *cret)
2121 if (!cret) return -2;
2122 if (!listing) return -2;
2124 return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing),
2130 int CtdlIPCRequestClientLogout(CtdlIPC *ipc, int session, char *cret)
2134 if (!cret) return -2;
2135 if (session < 0) return -2;
2137 sprintf(aaa, "REQT %d", session);
2138 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2143 int CtdlIPCSetMessageSeen(CtdlIPC *ipc, long msgnum, int seen, char *cret)
2147 if (!cret) return -2;
2148 if (msgnum < 0) return -2;
2150 sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2151 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2156 int CtdlIPCStartEncryption(CtdlIPC *ipc, char *cret)
2165 /* New SSL object */
2166 temp_ssl = SSL_new(ssl_ctx);
2168 error_printf("SSL_new failed: %s\n",
2169 ERR_reason_error_string(ERR_get_error()));
2172 /* Pointless flag waving */
2173 #if SSLEAY_VERSION_NUMBER >= 0x0922
2174 SSL_set_session_id_context(temp_ssl, (const unsigned char*) "Citadel SID", 14);
2177 if (!access(EGD_POOL, F_OK))
2180 if (!RAND_status()) {
2181 error_printf("PRNG not properly seeded\n");
2185 /* Associate network connection with SSL object */
2186 if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2187 error_printf("SSL_set_fd failed: %s\n",
2188 ERR_reason_error_string(ERR_get_error()));
2192 if (status_hook != NULL)
2193 status_hook("Requesting encryption...\r");
2195 /* Ready to start SSL/TLS */
2197 CtdlIPC_putline(ipc, "STLS");
2198 CtdlIPC_getline(ipc, buf);
2199 if (buf[0] != '2') {
2200 error_printf("Server can't start TLS: %s\n", buf);
2204 r = CtdlIPCGenericCommand(ipc,
2205 "STLS", NULL, 0, NULL, NULL, cret);
2207 error_printf("Server can't start TLS: %s\n", buf);
2212 /* Do SSL/TLS handshake */
2213 if ((a = SSL_connect(temp_ssl)) < 1) {
2214 error_printf("SSL_connect failed: %s\n",
2215 ERR_reason_error_string(ERR_get_error()));
2219 ipc->ssl = temp_ssl;
2221 if (BIO_set_close(ipc->ssl->rbio, BIO_NOCLOSE))
2225 bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ipc->ssl), &alg_bits);
2226 error_printf("Encrypting with %s cipher %s (%d of %d bits)\n",
2227 SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
2228 SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl)),
2234 #endif /* HAVE_OPENSSL */
2239 static void endtls(SSL *ssl)
2250 int CtdlIPCDirectoryLookup(CtdlIPC *ipc, const char *address, char *cret)
2255 if (!address) return -2;
2256 if (!cret) return -2;
2258 aaa = (char *)malloc(strlen(address) + 6);
2259 if (!aaa) return -1;
2261 sprintf(aaa, "QDIR %s", address);
2262 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2269 int CtdlIPCInternalProgram(CtdlIPC *ipc, int secret, char *cret)
2273 if (!cret) return -2;
2274 sprintf(aaa, "IPGM %d", secret);
2275 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2297 /* ************************************************************************** */
2298 /* Stuff below this line is not for public consumption */
2299 /* ************************************************************************** */
2302 /* Read a listing from the server up to 000. Append to dest if it exists */
2303 char *CtdlIPCReadListing(CtdlIPC *ipc, char *dest)
2312 length = strlen(ret);
2317 while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2318 linelength = strlen(aaa);
2319 ret = (char *)realloc(ret, (size_t)(length + linelength + 2));
2321 strcpy(&ret[length], aaa);
2322 length += linelength;
2323 strcpy(&ret[length++], "\n");
2331 /* Send a listing to the server; generate the ending 000. */
2332 int CtdlIPCSendListing(CtdlIPC *ipc, const char *listing)
2336 text = (char *)malloc(strlen(listing) + 6);
2338 strcpy(text, listing);
2339 while (text[strlen(text) - 1] == '\n')
2340 text[strlen(text) - 1] = '\0';
2341 strcat(text, "\n000");
2342 CtdlIPC_putline(ipc, text);
2346 /* Malloc failed but we are committed to send */
2347 /* This may result in extra blanks at the bottom */
2348 CtdlIPC_putline(ipc, text);
2349 CtdlIPC_putline(ipc, "000");
2355 /* Partial read of file from server */
2356 size_t CtdlIPCPartialRead(CtdlIPC *ipc, void **buf, size_t offset, size_t bytes, char *cret)
2358 register size_t len = 0;
2362 if (!cret) return 0;
2363 if (bytes < 1) return 0;
2366 sprintf(aaa, "READ %d|%d", (int)offset, (int)bytes);
2367 CtdlIPC_putline(ipc, aaa);
2368 CtdlIPC_getline(ipc, aaa);
2370 strcpy(cret, &aaa[4]);
2372 len = extract_long(&aaa[4], 0);
2373 *buf = (void *)realloc(*buf, (size_t)(offset + len));
2375 /* I know what I'm doing */
2376 serv_read(ipc, ((char *)(*buf) + offset), len);
2378 /* We have to read regardless */
2379 serv_read(ipc, aaa, len);
2383 CtdlIPC_unlock(ipc);
2389 int CtdlIPCEndDownload(CtdlIPC *ipc, char *cret)
2393 if (!cret) return -2;
2394 if (!ipc->downloading) return -2;
2396 ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2398 ipc->downloading = 0;
2404 int CtdlIPCSpecifyPreferredFormats(CtdlIPC *ipc, char *cret, char *formats) {
2408 snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2409 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2416 int CtdlIPCReadDownload(CtdlIPC *ipc, void **buf, size_t bytes, size_t resume,
2417 void (*progress_gauge_callback)
2418 (CtdlIPC*, unsigned long, unsigned long),
2421 register size_t len;
2423 if (!cret) return -1;
2424 if (!buf) return -1;
2425 if (*buf) return -1;
2426 if (!ipc->downloading) return -1;
2429 if (progress_gauge_callback)
2430 progress_gauge_callback(ipc, len, bytes);
2431 while (len < bytes) {
2432 register size_t block;
2434 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2440 if (progress_gauge_callback)
2441 progress_gauge_callback(ipc, len, bytes);
2446 /* READ - pipelined */
2447 int CtdlIPCHighSpeedReadDownload(CtdlIPC *ipc, void **buf, size_t bytes,
2449 void (*progress_gauge_callback)
2450 (CtdlIPC*, unsigned long, unsigned long),
2453 register size_t len;
2454 register int calls; /* How many calls in the pipeline */
2455 register int i; /* iterator */
2458 if (!cret) return -1;
2459 if (!buf) return -1;
2460 if (*buf) return -1;
2461 if (!ipc->downloading) return -1;
2463 *buf = (void *)realloc(*buf, bytes - resume);
2464 if (!*buf) return -1;
2468 if (progress_gauge_callback)
2469 progress_gauge_callback(ipc, len, bytes);
2471 /* How many calls will be in the pipeline? */
2472 calls = (bytes - resume) / 4096;
2473 if ((bytes - resume) % 4096) calls++;
2475 /* Send all requests at once */
2476 for (i = 0; i < calls; i++) {
2477 sprintf(aaa, "READ %d|4096", (int)(i * 4096 + resume) );
2478 CtdlIPC_putline(ipc, aaa);
2481 /* Receive all responses at once */
2482 for (i = 0; i < calls; i++) {
2483 CtdlIPC_getline(ipc, aaa);
2485 strcpy(cret, &aaa[4]);
2487 len = extract_long(&aaa[4], 0);
2488 /* I know what I'm doing */
2489 serv_read(ipc, ((char *)(*buf) + (i * 4096)), len);
2491 if (progress_gauge_callback)
2492 progress_gauge_callback(ipc, i * 4096 + len, bytes);
2494 CtdlIPC_unlock(ipc);
2500 int CtdlIPCEndUpload(CtdlIPC *ipc, int discard, char *cret)
2505 if (!cret) return -1;
2506 if (!ipc->uploading) return -1;
2508 sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2509 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2516 int CtdlIPCWriteUpload(CtdlIPC *ipc, FILE *uploadFP,
2517 void (*progress_gauge_callback)
2518 (CtdlIPC*, unsigned long, unsigned long),
2521 register int ret = -1;
2522 register size_t offset = 0;
2526 FILE *fd = uploadFP;
2529 if (!cret) return -1;
2531 fseek(fd, 0L, SEEK_END);
2535 if (progress_gauge_callback)
2536 progress_gauge_callback(ipc, 0, bytes);
2538 while (offset < bytes) {
2539 register size_t to_write;
2541 /* Read some data in */
2542 to_write = fread(buf, 1, 4096, fd);
2544 if (feof(fd) || ferror(fd)) break;
2546 sprintf(aaa, "WRIT %d", (int)to_write);
2547 CtdlIPC_putline(ipc, aaa);
2548 CtdlIPC_getline(ipc, aaa);
2549 strcpy(cret, &aaa[4]);
2551 if (aaa[0] == '7') {
2552 to_write = extract_long(&aaa[4], 0);
2554 serv_write(ipc, buf, to_write);
2556 if (progress_gauge_callback)
2557 progress_gauge_callback(ipc, offset, bytes);
2558 /* Detect short reads and back up if needed */
2559 /* offset will never be negative anyway */
2560 fseek(fd, (signed)offset, SEEK_SET);
2565 if (progress_gauge_callback)
2566 progress_gauge_callback(ipc, 1, 1);
2569 return (!ferr ? ret : -2);
2574 * Generic command method. This method should handle any server command
2575 * except for CHAT. It takes the following arguments:
2577 * ipc The server to speak with
2578 * command Preformatted command to send to server
2579 * to_send A text or binary file to send to server
2580 * (only sent if server requests it)
2581 * bytes_to_send The number of bytes in to_send (required if
2582 * sending binary, optional if sending listing)
2583 * to_receive Pointer to a NULL pointer, if the server
2584 * sends text or binary we will allocate memory
2585 * for the file and stuff it here
2586 * bytes_to_receive If a file is received, we will store its
2588 * proto_response The protocol response. Caller must provide
2589 * this buffer and ensure that it is at least
2590 * 128 bytes in length.
2592 * This function returns a number equal to the protocol response number,
2593 * -1 if an internal error occurred, -2 if caller provided bad values,
2594 * or 0 - the protocol response number if bad values were found during
2595 * the protocol exchange.
2596 * It stores the protocol response string (minus the number) in
2597 * protocol_response as described above. Some commands send additional
2598 * data in this string.
2600 int CtdlIPCGenericCommand(CtdlIPC *ipc,
2601 const char *command, const char *to_send,
2602 size_t bytes_to_send, char **to_receive,
2603 size_t *bytes_to_receive, char *proto_response)
2608 if (!command) return -2;
2609 if (!proto_response) return -2;
2612 CtdlIPC_putline(ipc, command);
2614 CtdlIPC_getline(ipc, proto_response);
2615 if (proto_response[3] == '*')
2617 ret = atoi(proto_response);
2618 strcpy(proto_response, &proto_response[4]);
2619 switch (ret / 100) {
2620 default: /* Unknown, punt */
2622 case 3: /* MORE_DATA */
2624 /* Don't need to do anything */
2626 case 1: /* LISTING_FOLLOWS */
2627 if (to_receive && !*to_receive && bytes_to_receive) {
2628 *to_receive = CtdlIPCReadListing(ipc, NULL);
2629 } else { /* Drain */
2630 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000")) ;
2634 case 4: /* SEND_LISTING */
2636 CtdlIPCSendListing(ipc, to_send);
2638 /* No listing given, fake it */
2639 CtdlIPC_putline(ipc, "000");
2643 case 6: /* BINARY_FOLLOWS */
2644 if (to_receive && !*to_receive && bytes_to_receive) {
2646 extract_long(proto_response, 0);
2647 *to_receive = (char *)
2648 malloc((size_t)*bytes_to_receive);
2652 serv_read(ipc, *to_receive,
2659 drain = extract_long(proto_response, 0);
2660 while (drain > SIZ) {
2661 serv_read(ipc, buf, SIZ);
2664 serv_read(ipc, buf, drain);
2668 case 7: /* SEND_BINARY */
2669 if (to_send && bytes_to_send) {
2670 serv_write(ipc, to_send, bytes_to_send);
2671 } else if (bytes_to_send) {
2672 /* Fake it, send nulls */
2675 fake = bytes_to_send;
2676 memset(buf, '\0', SIZ);
2677 while (fake > SIZ) {
2678 serv_write(ipc, buf, SIZ);
2681 serv_write(ipc, buf, fake);
2683 } /* else who knows? DANGER WILL ROBINSON */
2685 case 8: /* START_CHAT_MODE */
2686 if (!strncasecmp(command, "CHAT", 4)) {
2687 /* Don't call chatmode with generic! */
2688 CtdlIPC_putline(ipc, "/quit");
2691 /* In this mode we send then receive listing */
2693 CtdlIPCSendListing(ipc, to_send);
2695 /* No listing given, fake it */
2696 CtdlIPC_putline(ipc, "000");
2699 if (to_receive && !*to_receive
2700 && bytes_to_receive) {
2701 *to_receive = CtdlIPCReadListing(ipc, NULL);
2702 } else { /* Drain */
2703 while (CtdlIPC_getline(ipc, buf),
2704 strcmp(buf, "000")) ;
2709 case 9: /* ASYNC_MSG */
2710 /* CtdlIPCDoAsync(ret, proto_response); */
2711 free(CtdlIPCReadListing(ipc, NULL)); /* STUB FIXME */
2717 CtdlIPC_unlock(ipc);
2723 * Connect to a Citadel on a remote host using a TCP/IP socket
2725 static int tcp_connectsock(char *host, char *service)
2727 struct in6_addr serveraddr;
2728 struct addrinfo hints;
2729 struct addrinfo *res = NULL;
2730 struct addrinfo *ai = NULL;
2734 if ((host == NULL) || IsEmptyStr(host)) {
2735 service = DEFAULT_HOST ;
2737 if ((service == NULL) || IsEmptyStr(service)) {
2738 service = DEFAULT_PORT ;
2741 memset(&hints, 0x00, sizeof(hints));
2742 hints.ai_flags = AI_NUMERICSERV;
2743 hints.ai_family = AF_UNSPEC;
2744 hints.ai_socktype = SOCK_STREAM;
2747 * Handle numeric IPv4 and IPv6 addresses
2749 rc = inet_pton(AF_INET, host, &serveraddr);
2750 if (rc == 1) { /* dotted quad */
2751 hints.ai_family = AF_INET;
2752 hints.ai_flags |= AI_NUMERICHOST;
2755 rc = inet_pton(AF_INET6, host, &serveraddr);
2756 if (rc == 1) { /* IPv6 address */
2757 hints.ai_family = AF_INET6;
2758 hints.ai_flags |= AI_NUMERICHOST;
2762 /* Begin the connection process */
2764 rc = getaddrinfo(host, service, &hints, &res);
2770 * Try all available addresses until we connect to one or until we run out.
2772 for (ai = res; ai != NULL; ai = ai->ai_next) {
2773 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2774 if (sock < 0) return(-1);
2776 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2778 return(sock); /* Connected! */
2781 close(sock); /* Failed. Close the socket to avoid fd leak! */
2793 * Connect to a Citadel on the local host using a unix domain socket
2795 static int uds_connectsock(int *isLocal, char *sockpath)
2797 struct sockaddr_un addr;
2800 memset(&addr, 0, sizeof(addr));
2801 addr.sun_family = AF_UNIX;
2802 safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2804 s = socket(AF_UNIX, SOCK_STREAM, 0);
2809 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2820 * input binary data from socket
2822 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes)
2824 unsigned int len, rlen;
2826 #if defined(HAVE_OPENSSL)
2828 serv_read_ssl(ipc, buf, bytes);
2833 while (len < bytes) {
2834 rlen = read(ipc->sock, &buf[len], bytes - len);
2836 connection_died(ipc, 0);
2845 * send binary to server
2847 void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2849 unsigned int bytes_written = 0;
2852 #if defined(HAVE_OPENSSL)
2854 serv_write_ssl(ipc, buf, nbytes);
2858 while (bytes_written < nbytes) {
2859 retval = write(ipc->sock, &buf[bytes_written],
2860 nbytes - bytes_written);
2862 connection_died(ipc, 0);
2865 bytes_written += retval;
2872 * input binary data from encrypted connection
2874 static void serv_read_ssl(CtdlIPC* ipc, char *buf, unsigned int bytes)
2880 while (len < bytes) {
2881 if (SSL_want_read(ipc->ssl)) {
2882 if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2883 error_printf("SSL_write in serv_read:\n");
2884 ERR_print_errors_fp(stderr);
2887 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2891 errval = SSL_get_error(ipc->ssl, rlen);
2892 if (errval == SSL_ERROR_WANT_READ ||
2893 errval == SSL_ERROR_WANT_WRITE) {
2898 Not sure why we'd want to handle these error codes any differently,
2899 but this definitely isn't the way to handle them. Someone must have
2900 naively assumed that we could fall back to unencrypted communications,
2901 but all it does is just recursively blow the stack.
2902 if (errval == SSL_ERROR_ZERO_RETURN ||
2903 errval == SSL_ERROR_SSL) {
2904 serv_read(ipc, &buf[len], bytes - len);
2908 error_printf("SSL_read in serv_read: %s\n",
2909 ERR_reason_error_string(ERR_peek_error()));
2910 connection_died(ipc, 1);
2919 * send binary to server encrypted
2921 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2923 unsigned int bytes_written = 0;
2927 while (bytes_written < nbytes) {
2928 if (SSL_want_write(ipc->ssl)) {
2929 if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2930 error_printf("SSL_read in serv_write:\n");
2931 ERR_print_errors_fp(stderr);
2934 retval = SSL_write(ipc->ssl, &buf[bytes_written],
2935 nbytes - bytes_written);
2939 errval = SSL_get_error(ipc->ssl, retval);
2940 if (errval == SSL_ERROR_WANT_READ ||
2941 errval == SSL_ERROR_WANT_WRITE) {
2945 if (errval == SSL_ERROR_ZERO_RETURN ||
2946 errval == SSL_ERROR_SSL) {
2947 serv_write(ipc, &buf[bytes_written],
2948 nbytes - bytes_written);
2951 error_printf("SSL_write in serv_write: %s\n",
2952 ERR_reason_error_string(ERR_peek_error()));
2953 connection_died(ipc, 1);
2956 bytes_written += retval;
2961 #ifdef THREADED_CLIENT
2962 static void ssl_lock(int mode, int n, const char *file, int line)
2964 if (mode & CRYPTO_LOCK)
2965 pthread_mutex_lock(Critters[n]);
2967 pthread_mutex_unlock(Critters[n]);
2969 #endif /* THREADED_CLIENT */
2972 static void CtdlIPC_init_OpenSSL(void)
2975 const SSL_METHOD *ssl_method;
2978 /* already done init */
2987 SSL_load_error_strings();
2988 SSLeay_add_ssl_algorithms();
2990 /* Set up the SSL context in which we will oeprate */
2991 ssl_method = SSLv23_client_method();
2992 ssl_ctx = SSL_CTX_new(ssl_method);
2994 error_printf("SSL_CTX_new failed: %s\n",
2995 ERR_reason_error_string(ERR_get_error()));
2998 /* Any reasonable cipher we can get */
2999 if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
3000 error_printf("No ciphers available for encryption\n");
3003 SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
3005 /* Load DH parameters into the context */
3008 error_printf("Can't allocate a DH object: %s\n",
3009 ERR_reason_error_string(ERR_get_error()));
3012 if (!(BN_hex2bn(&(dh->p), DH_P))) {
3013 error_printf("Can't assign DH_P: %s\n",
3014 ERR_reason_error_string(ERR_get_error()));
3018 if (!(BN_hex2bn(&(dh->g), DH_G))) {
3019 error_printf("Can't assign DH_G: %s\n",
3020 ERR_reason_error_string(ERR_get_error()));
3025 SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3028 #ifdef THREADED_CLIENT
3029 /* OpenSSL requires callbacks for threaded clients */
3030 CRYPTO_set_locking_callback(ssl_lock);
3031 CRYPTO_set_id_callback(id_callback);
3033 /* OpenSSL requires us to do semaphores for threaded clients */
3034 Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
3036 perror("malloc failed");
3039 for (a = 0; a < CRYPTO_num_locks(); a++) {
3040 Critters[a] = malloc(sizeof (pthread_mutex_t));
3042 perror("malloc failed");
3045 pthread_mutex_init(Critters[a], NULL);
3048 #endif /* THREADED_CLIENT */
3053 #ifdef THREADED_CLIENT
3054 static unsigned long id_callback(void) {
3055 return (unsigned long)pthread_self();
3057 #endif /* THREADED_CLIENT */
3058 #endif /* HAVE_OPENSSL */
3062 ReadNetworkChunk(CtdlIPC* ipc)
3079 FD_SET(ipc->sock, &read_fd);
3080 ret = select(ipc->sock+1, &read_fd, NULL, NULL, &tv);
3082 // fprintf(stderr, "\nselect failed: %d %d %s\n", ret, err, strerror(err));
3086 *(ipc->BufPtr) = '\0';
3087 // n = read(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1);
3088 n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3090 ipc->BufPtr[n]='\0';
3098 if (!(errno == EINTR || errno == EAGAIN))
3099 error_printf( "\nselect failed: %d %s\n", err, strerror(err));
3105 n = read(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1);
3107 ipc->BufPtr[n]='\0';
3112 connection_died(ipc, 0);
3120 * input string from socket - implemented in terms of serv_read()
3124 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3127 char *aptr, *bptr, *aeptr, *beptr;
3129 // error_printf("---\n");
3132 #if defined(HAVE_OPENSSL)
3135 /* Read one character at a time. */
3137 serv_read(ipc, &buf[i], 1);
3138 if (buf[i] == '\n' || i == (SIZ-1))
3142 /* If we got a long line, discard characters until the newline. */
3144 while (buf[i] != '\n')
3145 serv_read(ipc, &buf[i], 1);
3147 /* Strip the trailing newline (and carriage return, if present) */
3148 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3149 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3154 if (ipc->Buf == NULL)
3157 ipc->Buf = (char*) malloc(ipc->BufSize + 10);
3159 ipc->BufPtr = ipc->Buf;
3163 // while ((ipc->BufUsed == 0)||(ntries++ > 10))
3164 if (ipc->BufUsed == 0)
3165 ReadNetworkChunk(ipc);
3167 //// if (ipc->BufUsed != 0) while (1)
3173 aeptr = ipc->Buf + ipc->BufSize;
3174 while ((aptr < aeptr) &&
3178 *(bptr++) = *(aptr++);
3179 if ((*aptr == '\n') && (aptr < aeptr))
3181 /* Terminate it right, remove the line breaks */
3182 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3184 while ((aptr < aeptr ) && (*(aptr + 1) == '\0') )
3187 // fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
3188 if ((bptr > buf + 1) && (*(bptr-1) == '\r'))
3191 /* is there more in the buffer we need to read later? */
3192 if (ipc->Buf + ipc->BufUsed > aptr)
3199 ipc->BufPtr = ipc->Buf;
3201 // error_printf("----bla6\n");
3204 }/* should we move our read stuf to the bufferstart so we have more space at the end? */
3205 else if ((ipc->BufPtr != ipc->Buf) &&
3206 (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4))))
3208 size_t NewBufSize = ipc->BufSize * 2;
3209 int delta = (ipc->BufPtr - ipc->Buf);
3212 /* if the line would end after our buffer, we should use a bigger buffer. */
3213 NewBuf = (char *)malloc (NewBufSize + 10);
3214 memcpy (NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3216 ipc->Buf = ipc->BufPtr = NewBuf;
3217 ipc->BufUsed -= delta;
3218 ipc->BufSize = NewBufSize;
3220 if (ReadNetworkChunk(ipc) <0)
3222 // error_printf("----bla\n");
3226 /// error_printf("----bl45761%s\nipc->BufUsed");
3228 // error_printf("----bla1\n");
3231 #else /* CHUNKED_READ */
3233 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3237 /* Read one character at a time. */
3239 serv_read(ipc, &buf[i], 1);
3240 if (buf[i] == '\n' || i == (SIZ-1))
3244 /* If we got a long line, discard characters until the newline. */
3246 while (buf[i] != '\n')
3247 serv_read(ipc, &buf[i], 1);
3249 /* Strip the trailing newline (and carriage return, if present) */
3250 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3251 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3255 #endif /* CHUNKED_READ */
3258 void CtdlIPC_chat_recv(CtdlIPC* ipc, char* buf)
3260 CtdlIPC_getline(ipc, buf);
3264 * send line to server - implemented in terms of serv_write()
3266 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf)
3272 cmd = malloc(len + 2);
3274 /* This requires no extra memory */
3275 serv_write(ipc, buf, len);
3276 serv_write(ipc, "\n", 1);
3278 /* This is network-optimized */
3279 strncpy(cmd, buf, len);
3280 strcpy(cmd + len, "\n");
3281 serv_write(ipc, cmd, len + 1);
3285 ipc->last_command_sent = time(NULL);
3288 void CtdlIPC_chat_send(CtdlIPC* ipc, const char* buf)
3290 CtdlIPC_putline(ipc, buf);
3297 CtdlIPC* CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3305 ipc = ialloc(CtdlIPC);
3309 #if defined(HAVE_OPENSSL)
3311 CtdlIPC_init_OpenSSL();
3313 #if defined(HAVE_PTHREAD_H)
3314 pthread_mutex_init(&(ipc->mutex), NULL); /* Default fast mutex */
3316 ipc->sock = -1; /* Not connected */
3317 ipc->isLocal = 0; /* Not local, of course! */
3318 ipc->downloading = 0;
3320 ipc->last_command_sent = 0L;
3321 ipc->network_status_cb = NULL;
3326 strcpy(cithost, DEFAULT_HOST); /* default host */
3327 strcpy(citport, DEFAULT_PORT); /* default port */
3329 /* Allow caller to supply our values (Windows) */
3330 if (hostbuf && strlen(hostbuf) > 0)
3331 strcpy(cithost, hostbuf);
3332 if (portbuf && strlen(portbuf) > 0)
3333 strcpy(citport, portbuf);
3335 /* Read host/port from command line if present */
3336 for (a = 0; a < argc; ++a) {
3339 } else if (a == 1) {
3340 strcpy(cithost, argv[a]);
3341 } else if (a == 2) {
3342 strcpy(citport, argv[a]);
3344 error_printf("%s: usage: ",argv[0]);
3345 error_printf("%s [host] [port] ",argv[0]);
3352 if ((!strcmp(cithost, "localhost"))
3353 || (!strcmp(cithost, "127.0.0.1"))) {
3357 /* If we're using a unix domain socket we can do a bunch of stuff */
3358 if (!strcmp(cithost, UDS)) {
3359 if (!strcasecmp(citport, DEFAULT_PORT)) {
3360 snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3363 snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3365 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3366 if (ipc->sock == -1) {
3370 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3371 if (portbuf != NULL) strcpy(portbuf, sockpath);
3372 strcpy(ipc->ip_hostname, "");
3373 strcpy(ipc->ip_address, "");
3377 ipc->sock = tcp_connectsock(cithost, citport);
3378 if (ipc->sock == -1) {
3384 /* Learn the actual network identity of the host to which we are connected */
3386 struct sockaddr_in6 clientaddr;
3387 unsigned int addrlen = sizeof(clientaddr);
3389 ipc->ip_hostname[0] = 0;
3390 ipc->ip_address[0] = 0;
3392 getpeername(ipc->sock, (struct sockaddr *)&clientaddr, &addrlen);
3393 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3394 ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0
3396 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3397 ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST
3400 /* stuff other things elsewhere */
3402 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3403 if (portbuf != NULL) strcpy(portbuf, citport);
3409 * Disconnect and delete the IPC class (destructor)
3411 void CtdlIPC_delete(CtdlIPC* ipc)
3415 SSL_shutdown(ipc->ssl);
3420 if (ipc->sock > -1) {
3421 shutdown(ipc->sock, 2); /* Close it up */
3424 if (ipc->Buf != NULL)
3433 * Disconnect and delete the IPC class (destructor)
3434 * Also NULLs out the pointer
3436 void CtdlIPC_delete_ptr(CtdlIPC** pipc)
3438 CtdlIPC_delete(*pipc);
3444 * return the file descriptor of the server socket so we can select() on it.
3446 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3449 int CtdlIPC_getsockfd(CtdlIPC* ipc)
3456 * return one character
3458 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3461 char CtdlIPC_get(CtdlIPC* ipc)
3466 serv_read(ipc, buf, 1);