2 * Copyright (c) 1987-2017 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_ipc.h"
44 #ifdef THREADED_CLIENT
45 pthread_mutex_t rwlock;
49 static SSL_CTX *ssl_ctx;
52 #ifdef THREADED_CLIENT
53 pthread_mutex_t **Critters; /* Things that need locking */
54 #endif /* THREADED_CLIENT */
56 #endif /* HAVE_OPENSSL */
59 #define INADDR_NONE 0xffffffff
62 static void (*status_hook)(char *s) = NULL;
63 char ctdl_autoetc_dir[PATH_MAX]="";
64 char file_citadel_rc[PATH_MAX]="";
65 char ctdl_run_dir[PATH_MAX]="";
66 char ctdl_etc_dir[PATH_MAX]="";
67 char ctdl_home_directory[PATH_MAX] = "";
68 char file_citadel_socket[PATH_MAX]="";
90 void CtdlIPC_lock(CtdlIPC *ipc)
92 if (ipc->network_status_cb) ipc->network_status_cb(1);
93 #ifdef THREADED_CLIENT
94 pthread_mutex_lock(&(ipc->mutex));
99 void CtdlIPC_unlock(CtdlIPC *ipc)
101 #ifdef THREADED_CLIENT
102 pthread_mutex_unlock(&(ipc->mutex));
104 if (ipc->network_status_cb) ipc->network_status_cb(0);
112 char *libcitadelclient_version_string(void) {
113 return "libcitadelclient(unnumbered)";
119 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
120 snprintf(SUBDIR,sizeof SUBDIR, "%s%s%s%s%s%s%s", \
121 (home&!relh)?ctdl_home_directory:basedir, \
122 ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
123 ((basedir!=ctdldir)&(home&!relh))?"/":"", \
125 (relhome[0]!='\0')?"/":"",\
127 (dirbuffer[0]!='\0')?"/":"");
129 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
132 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg)
134 const char* basedir = "";
135 char dirbuffer[PATH_MAX] = "";
137 StripSlashes(ctdldir, 1);
144 COMPUTE_DIRECTORY(ctdl_run_dir);
145 StripSlashes(ctdl_run_dir, 1);
148 #ifndef HAVE_AUTO_ETC_DIR
151 basedir=AUTO_ETC_DIR;
153 COMPUTE_DIRECTORY(ctdl_autoetc_dir);
154 StripSlashes(ctdl_autoetc_dir, 1);
162 COMPUTE_DIRECTORY(ctdl_etc_dir);
163 StripSlashes(ctdl_etc_dir, 1);
167 snprintf(file_citadel_rc,
168 sizeof file_citadel_rc,
171 StripSlashes(file_citadel_rc, 0);
173 snprintf(file_citadel_socket,
174 sizeof file_citadel_socket,
177 StripSlashes(file_citadel_socket, 0);
179 DBG_PRINT(ctdl_run_dir);
180 DBG_PRINT(file_citadel_socket);
181 DBG_PRINT(ctdl_etc_dir);
182 DBG_PRINT(file_citadel_rc);
185 void setCryptoStatusHook(void (*hook)(char *s)) {
189 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC *ipc, void (*hook)(int state)) {
190 ipc->network_status_cb = hook;
194 char instant_msgs = 0;
197 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes);
198 static void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
200 static void serv_read_ssl(CtdlIPC *ipc, char *buf, unsigned int bytes);
201 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
202 static void endtls(SSL *ssl);
203 #ifdef THREADED_CLIENT
204 static unsigned long id_callback(void);
205 #endif /* THREADED_CLIENT */
206 #endif /* HAVE_OPENSSL */
207 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf);
208 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf);
212 const char *svn_revision(void);
215 * Does nothing. The server should always return 200.
217 int CtdlIPCNoop(CtdlIPC *ipc)
221 return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
226 * Does nothing interesting. The server should always return 200
227 * along with your string.
229 int CtdlIPCEcho(CtdlIPC *ipc, const char *arg, char *cret)
235 if (!cret) return -2;
237 aaa = (char *)malloc((size_t)(strlen(arg) + 6));
240 sprintf(aaa, "ECHO %s", arg);
241 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
248 * Asks the server to close the connecction.
249 * Should always return 200.
251 int CtdlIPCQuit(CtdlIPC *ipc)
253 int ret = 221; /* Default to successful quit */
257 if (ipc->sock > -1) {
258 CtdlIPC_putline(ipc, "QUIT");
259 CtdlIPC_getline(ipc, aaa);
264 SSL_shutdown(ipc->ssl);
268 shutdown(ipc->sock, 2); /* Close connection; we're dead */
276 * Asks the server to log out. Should always return 200, even if no user
277 * was logged in. The user will not be logged in after this!
279 int CtdlIPCLogout(CtdlIPC *ipc)
285 CtdlIPC_putline(ipc, "LOUT");
286 CtdlIPC_getline(ipc, aaa);
294 * First stage of authentication - pass the username. Returns 300 if the
295 * username is able to log in, with the username correctly spelled in cret.
296 * Returns various 500 error codes if the user doesn't exist, etc.
298 int CtdlIPCTryLogin(CtdlIPC *ipc, const char *username, char *cret)
303 if (!username) return -2;
304 if (!cret) return -2;
306 aaa = (char *)malloc((size_t)(strlen(username) + 6));
309 sprintf(aaa, "USER %s", username);
310 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
317 * Second stage of authentication - provide password. The server returns
318 * 200 and several arguments in cret relating to the user's account.
320 int CtdlIPCTryPassword(CtdlIPC *ipc, const char *passwd, char *cret)
325 if (!passwd) return -2;
326 if (!cret) return -2;
328 aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
331 sprintf(aaa, "PASS %s", passwd);
332 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
339 * Second stage of authentication - provide password. The server returns
340 * 200 and several arguments in cret relating to the user's account.
342 int CtdlIPCTryApopPassword(CtdlIPC *ipc, const char *response, char *cret)
347 if (!response) return -2;
348 if (!cret) return -2;
350 aaa = (char *)malloc((size_t)(strlen(response) + 6));
353 sprintf(aaa, "PAS2 %s", response);
354 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
361 * Create a new user. This returns 200 plus the same arguments as TryPassword
362 * if selfservice is nonzero, unless there was a problem creating the account.
363 * If selfservice is zero, creates a new user but does not log out the existing
364 * user - intended for use by system administrators to create accounts on
365 * behalf of other users.
367 int CtdlIPCCreateUser(CtdlIPC *ipc, const char *username, int selfservice, char *cret)
372 if (!username) return -2;
373 if (!cret) return -2;
375 aaa = (char *)malloc((size_t)(strlen(username) + 6));
378 sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
379 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
386 * Changes the user's password. Returns 200 if changed, errors otherwise.
388 int CtdlIPCChangePassword(CtdlIPC *ipc, const char *passwd, char *cret)
393 if (!passwd) return -2;
394 if (!cret) return -2;
396 aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
399 sprintf(aaa, "SETP %s", passwd);
400 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
407 /* Caller must free the march list */
408 /* Room types are defined in enum RoomList; keep these in sync! */
409 /* floor is -1 for all, or floornum */
410 int CtdlIPCKnownRooms(CtdlIPC *ipc, enum RoomList which, int floor, struct march **listing, char *cret)
413 struct march *march = NULL;
414 static char *proto[] =
415 {"LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
420 if (!listing) return -2;
421 if (*listing) return -2; /* Free the listing first */
422 if (!cret) return -2;
423 /* if (which < 0 || which > 4) return -2; */
424 if (floor < -1) return -2; /* Can't validate upper bound, sorry */
426 sprintf(aaa, "%s %d", proto[which], floor);
427 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
428 if (ret / 100 == 1) {
431 while (bbb && strlen(bbb)) {
434 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
436 memmove(bbb, bbb + a + 1, strlen(bbb) - a);
437 mptr = (struct march *) malloc(sizeof (struct march));
440 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
441 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
442 mptr->march_floor = (char) extract_int(aaa, 2);
443 mptr->march_order = (char) extract_int(aaa, 3);
444 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
445 mptr->march_access = (char) extract_int(aaa, 5);
452 while (mptr2->next != NULL)
466 /* Caller must free the struct ctdluser; caller may pass an existing one */
467 int CtdlIPCGetConfig(CtdlIPC *ipc, struct ctdluser **uret, char *cret)
471 if (!cret) return -2;
472 if (!uret) return -2;
473 if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof (struct ctdluser));
474 if (!*uret) return -1;
476 ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
477 if (ret / 100 == 2) {
478 uret[0]->flags = extract_int(cret, 2);
485 int CtdlIPCSetConfig(CtdlIPC *ipc, struct ctdluser *uret, char *cret)
489 if (!uret) return -2;
490 if (!cret) return -2;
496 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
501 int CtdlIPCRenameUser(CtdlIPC *ipc, char *oldname, char *newname, char *cret)
506 if (!oldname) return -2;
507 if (!newname) return -2;
508 if (!cret) return -2;
510 snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
511 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
517 int CtdlIPCGotoRoom(CtdlIPC *ipc, const char *room, const char *passwd,
518 struct ctdlipcroom **rret, char *cret)
523 if (!cret) return -2;
524 if (!rret) return -2;
525 if (!*rret) *rret = (struct ctdlipcroom *)calloc(1, sizeof (struct ctdlipcroom));
526 if (!*rret) return -1;
529 aaa = (char *)malloc(strlen(room) + strlen(passwd) + 7);
534 sprintf(aaa, "GOTO %s|%s", room, passwd);
536 aaa = (char *)malloc(strlen(room) + 6);
541 sprintf(aaa, "GOTO %s", room);
543 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
544 if (ret / 100 == 2) {
545 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
546 rret[0]->RRunread = extract_long(cret, 1);
547 rret[0]->RRtotal = extract_long(cret, 2);
548 rret[0]->RRinfoupdated = extract_int(cret, 3);
549 rret[0]->RRflags = extract_int(cret, 4);
550 rret[0]->RRhighest = extract_long(cret, 5);
551 rret[0]->RRlastread = extract_long(cret, 6);
552 rret[0]->RRismailbox = extract_int(cret, 7);
553 rret[0]->RRaide = extract_int(cret, 8);
554 rret[0]->RRnewmail = extract_long(cret, 9);
555 rret[0]->RRfloor = extract_int(cret, 10);
556 rret[0]->RRcurrentview = extract_int(cret, 11);
557 rret[0]->RRdefaultview = extract_int(cret, 12);
558 /* position 13 is a trash folder flag ... irrelevant in this client */
559 rret[0]->RRflags2 = extract_int(cret, 14);
570 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
571 /* whicharg is number of messages, applies to last, first, gt, lt */
572 int CtdlIPCGetMessages(CtdlIPC *ipc, enum MessageList which, int whicharg,
573 const char *mtemplate, unsigned long **mret, char *cret)
576 unsigned long count = 0;
577 static char *proto[] =
578 { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
583 if (!cret) return -2;
584 if (!mret) return -2;
585 if (*mret) return -2;
586 if (which < 0 || which > 6) return -2;
589 sprintf(aaa, "MSGS %s||%d", proto[which],
590 (mtemplate) ? 1 : 0);
592 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg,
593 (mtemplate) ? 1 : 0);
594 if (mtemplate) count = strlen(mtemplate);
595 ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
599 *mret = (unsigned long *)calloc(1, sizeof(unsigned long));
602 while (bbb && strlen(bbb)) {
603 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
604 remove_token(bbb, 0, '\n');
605 *mret = (unsigned long *)realloc(*mret, (size_t)((count + 2) *
606 sizeof (unsigned long)));
608 (*mret)[count++] = atol(aaa);
620 int CtdlIPCGetSingleMessage(CtdlIPC *ipc, long msgnum, int headers, int as_mime,
621 struct ctdlipcmessage **mret, char *cret)
627 int multipart_hunting = 0;
628 char multipart_prefix[128];
631 if (!cret) return -1;
632 if (!mret) return -1;
633 if (!*mret) *mret = (struct ctdlipcmessage *)calloc(1, sizeof (struct ctdlipcmessage));
634 if (!*mret) return -1;
635 if (!msgnum) return -1;
637 strcpy(encoding, "");
638 strcpy(mret[0]->content_type, "");
639 sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
640 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
641 if (ret / 100 == 1) {
643 strcpy(mret[0]->mime_chosen, "1"); /* Default chosen-part is "1" */
644 while (strlen(bbb) > 4 && bbb[4] == '=') {
645 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
646 remove_token(bbb, 0, '\n');
648 if (!strncasecmp(aaa, "nhdr=yes", 8))
650 else if (!strncasecmp(aaa, "from=", 5))
651 safestrncpy(mret[0]->author, &aaa[5], SIZ);
652 else if (!strncasecmp(aaa, "type=", 5))
653 mret[0]->type = atoi(&aaa[5]);
654 else if (!strncasecmp(aaa, "msgn=", 5))
655 safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
656 else if (!strncasecmp(aaa, "subj=", 5))
657 safestrncpy(mret[0]->subject, &aaa[5], SIZ);
658 else if (!strncasecmp(aaa, "rfca=", 5))
659 safestrncpy(mret[0]->email, &aaa[5], SIZ);
660 else if (!strncasecmp(aaa, "hnod=", 5))
661 safestrncpy(mret[0]->hnod, &aaa[5], SIZ);
662 else if (!strncasecmp(aaa, "room=", 5))
663 safestrncpy(mret[0]->room, &aaa[5], SIZ);
664 else if (!strncasecmp(aaa, "node=", 5))
665 safestrncpy(mret[0]->node, &aaa[5], SIZ);
666 else if (!strncasecmp(aaa, "rcpt=", 5))
667 safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
668 else if (!strncasecmp(aaa, "wefw=", 5))
669 safestrncpy(mret[0]->references, &aaa[5], SIZ);
670 else if (!strncasecmp(aaa, "time=", 5))
671 mret[0]->time = atol(&aaa[5]);
673 /* Multipart/alternative prefix & suffix strings help
674 * us to determine which part we want to download.
676 else if (!strncasecmp(aaa, "pref=", 5)) {
677 extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
678 if (!strcasecmp(multipart_prefix,
679 "multipart/alternative")) {
683 else if (!strncasecmp(aaa, "suff=", 5)) {
684 extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
685 if (!strcasecmp(multipart_prefix,
686 "multipart/alternative")) {
691 else if (!strncasecmp(aaa, "part=", 5)) {
692 struct parts *ptr, *chain;
694 ptr = (struct parts *)calloc(1, sizeof (struct parts));
697 /* Fill the buffers for the caller */
698 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
699 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
700 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
701 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
702 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
703 ptr->length = extract_long(&aaa[5], 5);
704 if (!mret[0]->attachments)
705 mret[0]->attachments = ptr;
707 chain = mret[0]->attachments;
713 /* Now handle multipart/alternative */
714 if (multipart_hunting > 0) {
715 if ( (!strcasecmp(ptr->mimetype,
717 || (!strcasecmp(ptr->mimetype,
719 strcpy(mret[0]->mime_chosen,
727 /* Eliminate "text\n" */
728 remove_token(bbb, 0, '\n');
730 /* If doing a MIME thing, pull out the extra headers */
733 if (!strncasecmp(bbb, "Content-type:", 13)) {
734 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
735 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
736 striplt(mret[0]->content_type);
738 /* strip out ";charset=" portion. FIXME do something with
739 * the charset (like... convert it) instead of just throwing
742 if (strstr(mret[0]->content_type, ";") != NULL) {
743 strcpy(strstr(mret[0]->content_type, ";"), "");
747 if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
748 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
749 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
750 striplt(mret[0]->mime_chosen);
752 if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
753 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
754 strcpy(encoding, &encoding[26]);
757 remove_token(bbb, 0, '\n');
758 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
759 remove_token(bbb, 0, '\n');
766 if ( (!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable")) ) {
768 int bytes_decoded = 0;
769 ccc = malloc(strlen(bbb) + 32768);
770 if (!strcasecmp(encoding, "base64")) {
771 bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
773 else if (!strcasecmp(encoding, "quoted-printable")) {
774 bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
776 ccc[bytes_decoded] = 0;
781 /* FIXME: Strip trailing whitespace */
782 bbb = (char *)realloc(bbb, (size_t)(strlen(bbb) + 1));
785 bbb = (char *)realloc(bbb, 1);
795 int CtdlIPCWhoKnowsRoom(CtdlIPC *ipc, char **listing, char *cret)
800 if (!cret) return -2;
801 if (!listing) return -2;
802 if (*listing) return -2;
804 ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
810 int CtdlIPCServerInfo(CtdlIPC *ipc, char *cret)
814 char *listing = NULL;
817 if (!cret) return -2;
819 ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
820 if (ret / 100 == 1) {
823 while (*listing && strlen(listing)) {
824 extract_token(buf, listing, 0, '\n', sizeof buf);
825 remove_token(listing, 0, '\n');
827 case 0: ipc->ServInfo.pid = atoi(buf);
829 case 1: strcpy(ipc->ServInfo.nodename,buf);
831 case 2: strcpy(ipc->ServInfo.humannode,buf);
833 case 3: strcpy(ipc->ServInfo.fqdn,buf);
835 case 4: strcpy(ipc->ServInfo.software,buf);
837 case 5: ipc->ServInfo.rev_level = atoi(buf);
839 case 6: strcpy(ipc->ServInfo.site_location,buf);
841 case 7: strcpy(ipc->ServInfo.sysadm,buf);
843 case 9: strcpy(ipc->ServInfo.moreprompt,buf);
845 case 10: ipc->ServInfo.ok_floors = atoi(buf);
847 case 11: ipc->ServInfo.paging_level = atoi(buf);
849 case 13: ipc->ServInfo.supports_qnop = atoi(buf);
851 case 14: ipc->ServInfo.supports_ldap = atoi(buf);
853 case 15: ipc->ServInfo.newuser_disabled = atoi(buf);
855 case 16: strcpy(ipc->ServInfo.default_cal_zone, buf);
857 case 17: ipc->ServInfo.load_avg = atof(buf);
859 case 18: ipc->ServInfo.worker_avg = atof(buf);
861 case 19: ipc->ServInfo.thread_count = atoi(buf);
863 case 20: ipc->ServInfo.has_sieve = atoi(buf);
865 case 21: ipc->ServInfo.fulltext_enabled = atoi(buf);
867 case 22: strcpy(ipc->ServInfo.svn_revision, buf);
869 case 24: ipc->ServInfo.guest_logins = atoi(buf);
875 if (listing) free(listing);
881 int CtdlIPCReadDirectory(CtdlIPC *ipc, char **listing, char *cret)
886 if (!cret) return -2;
887 if (!listing) return -2;
888 if (*listing) return -2;
890 ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
896 * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
898 int CtdlIPCSetLastRead(CtdlIPC *ipc, long msgnum, char *cret)
903 if (!cret) return -2;
906 sprintf(aaa, "SLRP %ld", msgnum);
909 sprintf(aaa, "SLRP HIGHEST");
911 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
917 int CtdlIPCInviteUserToRoom(CtdlIPC *ipc, const char *username, char *cret)
922 if (!cret) return -2;
923 if (!username) return -2;
925 aaa = (char *)malloc(strlen(username) + 6);
928 sprintf(aaa, "INVT %s", username);
929 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
936 int CtdlIPCKickoutUserFromRoom(CtdlIPC *ipc, const char *username, char *cret)
941 if (!cret) return -1;
942 if (!username) return -1;
944 aaa = (char *)malloc(strlen(username) + 6);
946 sprintf(aaa, "KICK %s", username);
947 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
954 int CtdlIPCGetRoomAttributes(CtdlIPC *ipc, struct ctdlroom **qret, char *cret)
958 if (!cret) return -2;
959 if (!qret) return -2;
960 if (!*qret) *qret = (struct ctdlroom *)calloc(1, sizeof (struct ctdlroom));
961 if (!*qret) return -1;
963 ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
964 if (ret / 100 == 2) {
965 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
966 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
967 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
968 qret[0]->QRflags = extract_int(cret, 3);
969 qret[0]->QRfloor = extract_int(cret, 4);
970 qret[0]->QRorder = extract_int(cret, 5);
971 qret[0]->QRdefaultview = extract_int(cret, 6);
972 qret[0]->QRflags2 = extract_int(cret, 7);
979 /* set forget to kick all users out of room */
980 int CtdlIPCSetRoomAttributes(CtdlIPC *ipc, int forget, struct ctdlroom *qret, char *cret)
985 if (!cret) return -2;
986 if (!qret) return -2;
988 aaa = (char *)malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) +
989 strlen(qret->QRdirname) + 64);
992 sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
993 qret->QRname, qret->QRpasswd, qret->QRdirname,
994 qret->QRflags, forget, qret->QRfloor, qret->QRorder,
995 qret->QRdefaultview, qret->QRflags2);
996 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1003 int CtdlIPCGetRoomAide(CtdlIPC *ipc, char *cret)
1005 if (!cret) return -1;
1007 return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1012 int CtdlIPCSetRoomAide(CtdlIPC *ipc, const char *username, char *cret)
1017 if (!cret) return -2;
1018 if (!username) return -2;
1020 aaa = (char *)malloc(strlen(username) + 6);
1021 if (!aaa) return -1;
1023 sprintf(aaa, "SETA %s", username);
1024 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1031 int CtdlIPCPostMessage(CtdlIPC *ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1037 if (!cret) return -2;
1040 if (mr->references) {
1041 for (ptr=mr->references; *ptr != 0; ++ptr) {
1042 if (*ptr == '|') *ptr = '!';
1046 snprintf(cmd, sizeof cmd,
1047 "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1048 mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1049 ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL,
1051 if ((flag == 0) && (subject_required != NULL)) {
1052 /* Is the server strongly recommending that the user enter a message subject? */
1053 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1054 *subject_required = extract_int(&cret[4], 1);
1064 int CtdlIPCRoomInfo(CtdlIPC *ipc, char **iret, char *cret)
1068 if (!cret) return -2;
1069 if (!iret) return -2;
1070 if (*iret) return -2;
1072 return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1077 int CtdlIPCDeleteMessage(CtdlIPC *ipc, long msgnum, char *cret)
1081 if (!cret) return -2;
1082 if (!msgnum) return -2;
1084 sprintf(aaa, "DELE %ld", msgnum);
1085 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1090 int CtdlIPCMoveMessage(CtdlIPC *ipc, int copy, long msgnum, const char *destroom, char *cret)
1095 if (!cret) return -2;
1096 if (!destroom) return -2;
1097 if (!msgnum) return -2;
1099 aaa = (char *)malloc(strlen(destroom) + 28);
1100 if (!aaa) return -1;
1102 sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1103 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1110 int CtdlIPCDeleteRoom(CtdlIPC *ipc, int for_real, char *cret)
1114 if (!cret) return -2;
1116 sprintf(aaa, "KILL %d", for_real);
1117 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1122 int CtdlIPCCreateRoom(CtdlIPC *ipc, int for_real, const char *roomname, int type,
1123 const char *password, int floor, char *cret)
1128 if (!cret) return -2;
1129 if (!roomname) return -2;
1132 aaa = (char *)malloc(strlen(roomname) + strlen(password) + 40);
1133 if (!aaa) return -1;
1134 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type,
1137 aaa = (char *)malloc(strlen(roomname) + 40);
1138 if (!aaa) return -1;
1139 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type,
1142 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1149 int CtdlIPCForgetRoom(CtdlIPC *ipc, char *cret)
1151 if (!cret) return -2;
1153 return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1158 int CtdlIPCSystemMessage(CtdlIPC *ipc, const char *message, char **mret, char *cret)
1164 if (!cret) return -2;
1165 if (!mret) return -2;
1166 if (*mret) return -2;
1167 if (!message) return -2;
1169 aaa = (char *)malloc(strlen(message) + 6);
1170 if (!aaa) return -1;
1172 sprintf(aaa, "MESG %s", message);
1173 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1180 int CtdlIPCNextUnvalidatedUser(CtdlIPC *ipc, char *cret)
1182 if (!cret) return -2;
1184 return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1189 int CtdlIPCGetUserRegistration(CtdlIPC *ipc, const char *username, char **rret, char *cret)
1195 if (!cret) return -2;
1196 if (!rret) return -2;
1197 if (*rret) return -2;
1200 aaa = (char *)malloc(strlen(username) + 6);
1202 aaa = (char *)malloc(12);
1203 if (!aaa) return -1;
1206 sprintf(aaa, "GREG %s", username);
1208 sprintf(aaa, "GREG _SELF_");
1209 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1216 int CtdlIPCValidateUser(CtdlIPC *ipc, const char *username, int axlevel, char *cret)
1221 if (!cret) return -2;
1222 if (!username) return -2;
1223 if (axlevel < AxDeleted || axlevel > AxAideU) return -2;
1225 aaa = (char *)malloc(strlen(username) + 17);
1226 if (!aaa) return -1;
1228 sprintf(aaa, "VALI %s|%d", username, axlevel);
1229 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1236 int CtdlIPCSetRoomInfo(CtdlIPC *ipc, int for_real, const char *info, char *cret)
1240 if (!cret) return -1;
1241 if (!info) return -1;
1243 sprintf(aaa, "EINF %d", for_real);
1244 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1249 int CtdlIPCUserListing(CtdlIPC *ipc, char *searchstring, char **listing, char *cret)
1255 if (!cret) return -1;
1256 if (!listing) return -1;
1257 if (*listing) return -1;
1258 if (!searchstring) return -1;
1260 cmd = malloc(strlen(searchstring) + 10);
1261 sprintf(cmd, "LIST %s", searchstring);
1263 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1270 int CtdlIPCSetRegistration(CtdlIPC *ipc, const char *info, char *cret)
1272 if (!cret) return -1;
1273 if (!info) return -1;
1275 return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info),
1281 int CtdlIPCMiscCheck(CtdlIPC *ipc, struct ctdlipcmisc *chek, char *cret)
1285 if (!cret) return -1;
1286 if (!chek) return -1;
1288 ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1289 if (ret / 100 == 2) {
1290 chek->newmail = extract_long(cret, 0);
1291 chek->needregis = extract_int(cret, 1);
1292 chek->needvalid = extract_int(cret, 2);
1299 int CtdlIPCDeleteFile(CtdlIPC *ipc, const char *filename, char *cret)
1304 if (!cret) return -2;
1305 if (!filename) return -2;
1307 aaa = (char *)malloc(strlen(filename) + 6);
1308 if (!aaa) return -1;
1310 sprintf(aaa, "DELF %s", filename);
1311 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1318 int CtdlIPCMoveFile(CtdlIPC *ipc, const char *filename, const char *destroom, char *cret)
1323 if (!cret) return -2;
1324 if (!filename) return -2;
1325 if (!destroom) return -2;
1327 aaa = (char *)malloc(strlen(filename) + strlen(destroom) + 7);
1328 if (!aaa) return -1;
1330 sprintf(aaa, "MOVF %s|%s", filename, destroom);
1331 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1338 int CtdlIPCOnlineUsers(CtdlIPC *ipc, char **listing, time_t *stamp, char *cret)
1343 if (!cret) return -1;
1344 if (!listing) return -1;
1345 if (*listing) return -1;
1347 *stamp = CtdlIPCServerTime(ipc, cret);
1349 *stamp = time(NULL);
1350 ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1356 int CtdlIPCFileDownload(CtdlIPC *ipc, const char *filename, void **buf,
1358 void (*progress_gauge_callback)
1359 (CtdlIPC*, unsigned long, unsigned long),
1368 if (!cret) return -2;
1369 if (!filename) return -2;
1370 if (!buf) return -2;
1371 if (*buf) return -2;
1372 if (ipc->downloading) return -2;
1374 aaa = (char *)malloc(strlen(filename) + 6);
1375 if (!aaa) return -1;
1377 sprintf(aaa, "OPEN %s", filename);
1378 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1380 if (ret / 100 == 2) {
1381 ipc->downloading = 1;
1382 bytes = extract_long(cret, 0);
1383 last_mod = extract_int(cret, 1);
1384 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1386 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume,
1387 progress_gauge_callback, cret);
1389 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1390 progress_gauge_callback, cret);
1393 ret = CtdlIPCEndDownload(ipc, cret);
1395 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1396 filename, mimetype);
1403 int CtdlIPCAttachmentDownload(CtdlIPC *ipc, long msgnum, const char *part,
1405 void (*progress_gauge_callback)
1406 (CtdlIPC*, unsigned long, unsigned long),
1416 if (!cret) return -2;
1417 if (!buf) return -2;
1418 if (*buf) return -2;
1419 if (!part) return -2;
1420 if (!msgnum) return -2;
1421 if (ipc->downloading) return -2;
1423 sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1424 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1425 if (ret / 100 == 2) {
1426 ipc->downloading = 1;
1427 bytes = extract_long(cret, 0);
1428 last_mod = extract_int(cret, 1);
1429 extract_token(filename, cret, 2, '|', sizeof filename);
1430 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1431 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1432 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1433 ret = CtdlIPCEndDownload(ipc, cret);
1435 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1436 filename, mimetype);
1443 int CtdlIPCImageDownload(CtdlIPC *ipc, const char *filename, void **buf,
1444 void (*progress_gauge_callback)
1445 (CtdlIPC*, unsigned long, unsigned long),
1454 if (!cret) return -1;
1455 if (!buf) return -1;
1456 if (*buf) return -1;
1457 if (!filename) return -1;
1458 if (ipc->downloading) return -1;
1460 aaa = (char *)malloc(strlen(filename) + 6);
1461 if (!aaa) return -1;
1463 sprintf(aaa, "OIMG %s", filename);
1464 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1466 if (ret / 100 == 2) {
1467 ipc->downloading = 1;
1468 bytes = extract_long(cret, 0);
1469 last_mod = extract_int(cret, 1);
1470 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1471 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1472 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1473 ret = CtdlIPCEndDownload(ipc, cret);
1475 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1476 filename, mimetype);
1483 int CtdlIPCFileUpload(CtdlIPC *ipc, const char *save_as, const char *comment,
1485 void (*progress_gauge_callback)
1486 (CtdlIPC*, unsigned long, unsigned long),
1492 char MimeTestBuf[64];
1493 const char *MimeType;
1496 if (!cret) return -1;
1497 if (!save_as) return -1;
1498 if (!comment) return -1;
1499 if (!path) return -1;
1500 if (!*path) return -1;
1501 if (ipc->uploading) return -1;
1503 uploadFP = fopen(path, "r");
1504 if (!uploadFP) return -2;
1506 len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1511 MimeType = GuessMimeType(&MimeTestBuf[0], len);
1512 aaa = (char *)malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1513 if (!aaa) return -1;
1515 sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1516 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1518 if (ret / 100 == 2) {
1520 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1521 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1529 int CtdlIPCImageUpload(CtdlIPC *ipc, int for_real, const char *path,
1530 const char *save_as,
1531 void (*progress_gauge_callback)
1532 (CtdlIPC*, unsigned long, unsigned long),
1538 char MimeTestBuf[64];
1539 const char *MimeType;
1542 if (!cret) return -1;
1543 if (!save_as) return -1;
1544 if (!path && for_real) return -1;
1545 if (!*path && for_real) return -1;
1546 if (ipc->uploading) return -1;
1548 aaa = (char *)malloc(strlen(save_as) + 17);
1549 if (!aaa) return -1;
1551 uploadFP = fopen(path, "r");
1552 if (!uploadFP) return -2;
1554 len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1558 MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1560 sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1561 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1563 if (ret / 100 == 2 && for_real) {
1565 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1566 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1574 int CtdlIPCQueryUsername(CtdlIPC *ipc, const char *username, char *cret)
1579 if (!cret) return -2;
1580 if (!username) return -2;
1582 aaa = (char *)malloc(strlen(username) + 6);
1583 if (!aaa) return -1;
1585 sprintf(aaa, "QUSR %s", username);
1586 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1593 int CtdlIPCFloorListing(CtdlIPC *ipc, char **listing, char *cret)
1597 if (!cret) return -2;
1598 if (!listing) return -2;
1599 if (*listing) return -2;
1601 return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1606 int CtdlIPCCreateFloor(CtdlIPC *ipc, int for_real, const char *name, char *cret)
1611 if (!cret) return -2;
1612 if (!name) return -2;
1614 sprintf(aaa, "CFLR %s|%d", name, for_real);
1615 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1621 int CtdlIPCDeleteFloor(CtdlIPC *ipc, int for_real, int floornum, char *cret)
1625 if (!cret) return -1;
1626 if (floornum < 0) return -1;
1628 sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1629 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1634 int CtdlIPCEditFloor(CtdlIPC *ipc, int floornum, const char *floorname, char *cret)
1639 if (!cret) return -2;
1640 if (!floorname) return -2;
1641 if (floornum < 0) return -2;
1643 sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1644 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1652 * You only need to fill out hostname, the defaults will be used if any of the
1653 * other fields are not set properly.
1655 int CtdlIPCIdentifySoftware(CtdlIPC *ipc, int developerid, int clientid,
1656 int revision, const char *software_name, const char *hostname,
1662 if (developerid < 0 || clientid < 0 || revision < 0 ||
1666 revision = CLIENT_VERSION - 600;
1667 software_name = "Citadel (libcitadel)";
1669 if (!hostname) return -2;
1671 aaa = (char *)malloc(strlen(software_name) + strlen(hostname) + 29);
1672 if (!aaa) return -1;
1674 sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid,
1675 revision, software_name, hostname);
1676 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1683 int CtdlIPCSendInstantMessage(CtdlIPC *ipc, const char *username, const char *text,
1689 if (!cret) return -2;
1690 if (!username) return -2;
1692 aaa = (char *)malloc(strlen(username) + 8);
1693 if (!aaa) return -1;
1696 sprintf(aaa, "SEXP %s|-", username);
1697 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text),
1700 sprintf(aaa, "SEXP %s||", username);
1701 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1709 int CtdlIPCGetInstantMessage(CtdlIPC *ipc, char **listing, char *cret)
1713 if (!cret) return -2;
1714 if (!listing) return -2;
1715 if (*listing) return -2;
1717 return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1722 /* mode is 0 = enable, 1 = disable, 2 = status */
1723 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC *ipc, int mode, char *cret)
1727 if (!cret) return -2;
1729 sprintf(aaa, "DEXP %d", mode);
1730 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1735 int CtdlIPCSetBio(CtdlIPC *ipc, char *bio, char *cret)
1737 if (!cret) return -2;
1738 if (!bio) return -2;
1740 return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio),
1746 int CtdlIPCGetBio(CtdlIPC *ipc, const char *username, char **listing, char *cret)
1752 if (!cret) return -2;
1753 if (!username) return -2;
1754 if (!listing) return -2;
1755 if (*listing) return -2;
1757 aaa = (char *)malloc(strlen(username) + 6);
1758 if (!aaa) return -1;
1760 sprintf(aaa, "RBIO %s", username);
1761 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1768 int CtdlIPCListUsersWithBios(CtdlIPC *ipc, char **listing, char *cret)
1772 if (!cret) return -2;
1773 if (!listing) return -2;
1774 if (*listing) return -2;
1776 return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1781 int CtdlIPCStealthMode(CtdlIPC *ipc, int mode, char *cret)
1785 if (!cret) return -1;
1787 sprintf(aaa, "STEL %d", mode ? 1 : 0);
1788 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1793 int CtdlIPCTerminateSession(CtdlIPC *ipc, int sid, char *cret)
1797 if (!cret) return -1;
1799 sprintf(aaa, "TERM %d", sid);
1800 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1805 int CtdlIPCTerminateServerNow(CtdlIPC *ipc, char *cret)
1807 if (!cret) return -1;
1809 return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1814 int CtdlIPCTerminateServerScheduled(CtdlIPC *ipc, int mode, char *cret)
1818 if (!cret) return -1;
1820 sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1821 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1826 int CtdlIPCEnterSystemMessage(CtdlIPC *ipc, const char *filename, const char *text,
1832 if (!cret) return -2;
1833 if (!text) return -2;
1834 if (!filename) return -2;
1836 aaa = (char *)malloc(strlen(filename) + 6);
1837 if (!aaa) return -1;
1839 sprintf(aaa, "EMSG %s", filename);
1840 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1847 int CtdlIPCChangeHostname(CtdlIPC *ipc, const char *hostname, char *cret)
1852 if (!cret) return -2;
1853 if (!hostname) return -2;
1855 aaa = (char *)malloc(strlen(hostname) + 6);
1856 if (!aaa) return -1;
1858 sprintf(aaa, "HCHG %s", hostname);
1859 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1866 int CtdlIPCChangeRoomname(CtdlIPC *ipc, const char *roomname, char *cret)
1871 if (!cret) return -2;
1872 if (!roomname) return -2;
1874 aaa = (char *)malloc(strlen(roomname) + 6);
1875 if (!aaa) return -1;
1877 sprintf(aaa, "RCHG %s", roomname);
1878 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1885 int CtdlIPCChangeUsername(CtdlIPC *ipc, const char *username, char *cret)
1890 if (!cret) return -2;
1891 if (!username) return -2;
1893 aaa = (char *)malloc(strlen(username) + 6);
1894 if (!aaa) return -1;
1896 sprintf(aaa, "UCHG %s", username);
1897 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1904 /* This function returns the actual server time reported, or 0 if error */
1905 time_t CtdlIPCServerTime(CtdlIPC *ipc, char *cret)
1910 ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1911 if (ret / 100 == 2) {
1912 tret = extract_long(cret, 0);
1921 int CtdlIPCAideGetUserParameters(CtdlIPC *ipc, const char *who, struct ctdluser **uret, char *cret)
1926 if (!cret) return -2;
1927 if (!uret) return -2;
1928 if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof(struct ctdluser));
1929 if (!*uret) return -1;
1931 sprintf(aaa, "AGUP %s", who);
1932 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1934 if (ret / 100 == 2) {
1935 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1936 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1937 uret[0]->flags = extract_int(cret, 2);
1938 uret[0]->timescalled = extract_long(cret, 3);
1939 uret[0]->posted = extract_long(cret, 4);
1940 uret[0]->axlevel = extract_int(cret, 5);
1941 uret[0]->usernum = extract_long(cret, 6);
1942 uret[0]->lastcall = extract_long(cret, 7);
1943 uret[0]->USuserpurge = extract_int(cret, 8);
1950 int CtdlIPCAideSetUserParameters(CtdlIPC *ipc, const struct ctdluser *uret, char *cret)
1955 if (!cret) return -2;
1956 if (!uret) return -2;
1958 aaa = (char *)malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1959 if (!aaa) return -1;
1961 sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
1962 uret->fullname, uret->password, uret->flags,
1963 uret->timescalled, uret->posted, uret->axlevel,
1964 uret->usernum, uret->lastcall, uret->USuserpurge);
1965 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1972 int CtdlIPCAideGetEmailAddresses(CtdlIPC *ipc, const char *who, char *target_buf, char *cret)
1976 char *emailaddrs = NULL;
1977 size_t emailaddrs_len = 0;
1979 sprintf(aaa, "AGEA %s", who);
1980 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
1982 if (ret / 100 == 1) {
1983 strcpy(target_buf, emailaddrs);
1986 if (emailaddrs != NULL) {
1995 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1996 /* caller must free the struct ExpirePolicy */
1997 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC *ipc, GPEXWhichPolicy which,
1998 struct ExpirePolicy **policy, char *cret)
2000 static char *proto[] = {
2004 strof(mailboxespolicy)
2009 if (!cret) return -2;
2010 if (!policy) return -2;
2011 if (!*policy) *policy = (struct ExpirePolicy *)calloc(1, sizeof(struct ExpirePolicy));
2012 if (!*policy) return -1;
2013 if (which < 0 || which > 3) return -2;
2015 sprintf(cmd, "GPEX %s", proto[which]);
2016 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2017 if (ret / 100 == 2) {
2018 policy[0]->expire_mode = extract_int(cret, 0);
2019 policy[0]->expire_value = extract_int(cret, 1);
2026 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2027 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2028 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC *ipc, int which,
2029 struct ExpirePolicy *policy, char *cret)
2032 char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2034 if (!cret) return -2;
2035 if (which < 0 || which > 3) return -2;
2036 if (!policy) return -2;
2037 if (policy->expire_mode < 0 || policy->expire_mode > 3) return -2;
2038 if (policy->expire_mode >= 2 && policy->expire_value < 1) return -2;
2040 sprintf(aaa, "SPEX %s|%d|%d", whichvals[which],
2041 policy->expire_mode, policy->expire_value);
2042 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2047 int CtdlIPCGetSystemConfig(CtdlIPC *ipc, char **listing, char *cret)
2051 if (!cret) return -2;
2052 if (!listing) return -2;
2053 if (*listing) return -2;
2055 return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0,
2056 listing, &bytes, cret);
2061 int CtdlIPCSetSystemConfig(CtdlIPC *ipc, const char *listing, char *cret)
2063 if (!cret) return -2;
2064 if (!listing) return -2;
2066 return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing),
2072 int CtdlIPCGetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, char **listing, char *cret)
2078 if (!cret) return -2;
2079 if (!mimetype) return -2;
2080 if (!listing) return -2;
2081 if (*listing) return -2;
2083 aaa = malloc(strlen(mimetype) + 13);
2084 if (!aaa) return -1;
2085 sprintf(aaa, "CONF GETSYS|%s", mimetype);
2086 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2093 int CtdlIPCSetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, const char *listing, char *cret)
2098 if (!cret) return -2;
2099 if (!mimetype) return -2;
2100 if (!listing) return -2;
2102 aaa = malloc(strlen(mimetype) + 13);
2103 if (!aaa) return -1;
2104 sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2105 ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2112 int CtdlIPCGetRoomNetworkConfig(CtdlIPC *ipc, char **listing, char *cret)
2116 if (!cret) return -2;
2117 if (!listing) return -2;
2118 if (*listing) return -2;
2120 return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2125 int CtdlIPCSetRoomNetworkConfig(CtdlIPC *ipc, const char *listing, char *cret)
2127 if (!cret) return -2;
2128 if (!listing) return -2;
2130 return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2135 int CtdlIPCRequestClientLogout(CtdlIPC *ipc, int session, char *cret)
2139 if (!cret) return -2;
2140 if (session < 0) return -2;
2142 sprintf(aaa, "REQT %d", session);
2143 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2148 int CtdlIPCSetMessageSeen(CtdlIPC *ipc, long msgnum, int seen, char *cret)
2152 if (!cret) return -2;
2153 if (msgnum < 0) return -2;
2155 sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2156 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2161 int CtdlIPCStartEncryption(CtdlIPC *ipc, char *cret)
2170 /* New SSL object */
2171 temp_ssl = SSL_new(ssl_ctx);
2173 error_printf("SSL_new failed: %s\n",
2174 ERR_reason_error_string(ERR_get_error()));
2177 /* Pointless flag waving */
2178 #if SSLEAY_VERSION_NUMBER >= 0x0922
2179 SSL_set_session_id_context(temp_ssl, (const unsigned char*) "Citadel SID", 14);
2182 if (!access(EGD_POOL, F_OK))
2185 if (!RAND_status()) {
2186 error_printf("PRNG not properly seeded\n");
2190 /* Associate network connection with SSL object */
2191 if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2192 error_printf("SSL_set_fd failed: %s\n",
2193 ERR_reason_error_string(ERR_get_error()));
2197 if (status_hook != NULL)
2198 status_hook("Requesting encryption...\r");
2200 /* Ready to start SSL/TLS */
2202 CtdlIPC_putline(ipc, "STLS");
2203 CtdlIPC_getline(ipc, buf);
2204 if (buf[0] != '2') {
2205 error_printf("Server can't start TLS: %s\n", buf);
2209 r = CtdlIPCGenericCommand(ipc,
2210 "STLS", NULL, 0, NULL, NULL, cret);
2212 error_printf("Server can't start TLS: %s\n", buf);
2217 /* Do SSL/TLS handshake */
2218 if ((a = SSL_connect(temp_ssl)) < 1) {
2219 error_printf("SSL_connect failed: %s\n",
2220 ERR_reason_error_string(ERR_get_error()));
2224 ipc->ssl = temp_ssl;
2226 if (BIO_set_close(ipc->ssl->rbio, BIO_NOCLOSE))
2230 bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ipc->ssl), &alg_bits);
2231 error_printf("Encrypting with %s cipher %s (%d of %d bits)\n",
2232 SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
2233 SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl)),
2239 #endif /* HAVE_OPENSSL */
2244 static void endtls(SSL *ssl)
2255 int CtdlIPCDirectoryLookup(CtdlIPC *ipc, const char *address, char *cret)
2260 if (!address) return -2;
2261 if (!cret) return -2;
2263 aaa = (char *)malloc(strlen(address) + 6);
2264 if (!aaa) return -1;
2266 sprintf(aaa, "QDIR %s", address);
2267 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2274 int CtdlIPCInternalProgram(CtdlIPC *ipc, int secret, char *cret)
2278 if (!cret) return -2;
2279 sprintf(aaa, "IPGM %d", secret);
2280 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2286 /* ************************************************************************** */
2287 /* Stuff below this line is not for public consumption */
2288 /* ************************************************************************** */
2291 /* Read a listing from the server up to 000. Append to dest if it exists */
2292 char *CtdlIPCReadListing(CtdlIPC *ipc, char *dest)
2301 length = strlen(ret);
2306 while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2307 linelength = strlen(aaa);
2308 ret = (char *)realloc(ret, (size_t)(length + linelength + 2));
2310 strcpy(&ret[length], aaa);
2311 length += linelength;
2312 strcpy(&ret[length++], "\n");
2320 /* Send a listing to the server; generate the ending 000. */
2321 int CtdlIPCSendListing(CtdlIPC *ipc, const char *listing)
2325 text = (char *)malloc(strlen(listing) + 6);
2327 strcpy(text, listing);
2328 while (text[strlen(text) - 1] == '\n')
2329 text[strlen(text) - 1] = '\0';
2330 strcat(text, "\n000");
2331 CtdlIPC_putline(ipc, text);
2335 /* Malloc failed but we are committed to send */
2336 /* This may result in extra blanks at the bottom */
2337 CtdlIPC_putline(ipc, text);
2338 CtdlIPC_putline(ipc, "000");
2344 /* Partial read of file from server */
2345 size_t CtdlIPCPartialRead(CtdlIPC *ipc, void **buf, size_t offset, size_t bytes, char *cret)
2351 if (!cret) return 0;
2352 if (bytes < 1) return 0;
2355 sprintf(aaa, "READ %d|%d", (int)offset, (int)bytes);
2356 CtdlIPC_putline(ipc, aaa);
2357 CtdlIPC_getline(ipc, aaa);
2359 strcpy(cret, &aaa[4]);
2361 len = extract_long(&aaa[4], 0);
2362 *buf = (void *)realloc(*buf, (size_t)(offset + len));
2364 /* I know what I'm doing */
2365 serv_read(ipc, ((char *)(*buf) + offset), len);
2367 /* We have to read regardless */
2368 serv_read(ipc, aaa, len);
2372 CtdlIPC_unlock(ipc);
2378 int CtdlIPCEndDownload(CtdlIPC *ipc, char *cret)
2382 if (!cret) return -2;
2383 if (!ipc->downloading) return -2;
2385 ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2387 ipc->downloading = 0;
2393 int CtdlIPCSpecifyPreferredFormats(CtdlIPC *ipc, char *cret, char *formats) {
2397 snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2398 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2405 int CtdlIPCReadDownload(CtdlIPC *ipc, void **buf, size_t bytes, size_t resume,
2406 void (*progress_gauge_callback)
2407 (CtdlIPC*, unsigned long, unsigned long),
2412 if (!cret) return -1;
2413 if (!buf) return -1;
2414 if (*buf) return -1;
2415 if (!ipc->downloading) return -1;
2418 if (progress_gauge_callback)
2419 progress_gauge_callback(ipc, len, bytes);
2420 while (len < bytes) {
2423 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2429 if (progress_gauge_callback)
2430 progress_gauge_callback(ipc, len, bytes);
2435 /* READ - pipelined */
2436 int CtdlIPCHighSpeedReadDownload(CtdlIPC *ipc, void **buf, size_t bytes,
2438 void (*progress_gauge_callback)
2439 (CtdlIPC*, unsigned long, unsigned long),
2443 int calls; /* How many calls in the pipeline */
2444 int i; /* iterator */
2447 if (!cret) return -1;
2448 if (!buf) return -1;
2449 if (*buf) return -1;
2450 if (!ipc->downloading) return -1;
2452 *buf = (void *)realloc(*buf, bytes - resume);
2453 if (!*buf) return -1;
2457 if (progress_gauge_callback)
2458 progress_gauge_callback(ipc, len, bytes);
2460 /* How many calls will be in the pipeline? */
2461 calls = (bytes - resume) / 4096;
2462 if ((bytes - resume) % 4096) calls++;
2464 /* Send all requests at once */
2465 for (i = 0; i < calls; i++) {
2466 sprintf(aaa, "READ %d|4096", (int)(i * 4096 + resume) );
2467 CtdlIPC_putline(ipc, aaa);
2470 /* Receive all responses at once */
2471 for (i = 0; i < calls; i++) {
2472 CtdlIPC_getline(ipc, aaa);
2474 strcpy(cret, &aaa[4]);
2476 len = extract_long(&aaa[4], 0);
2477 /* I know what I'm doing */
2478 serv_read(ipc, ((char *)(*buf) + (i * 4096)), len);
2480 if (progress_gauge_callback)
2481 progress_gauge_callback(ipc, i * 4096 + len, bytes);
2483 CtdlIPC_unlock(ipc);
2489 int CtdlIPCEndUpload(CtdlIPC *ipc, int discard, char *cret)
2494 if (!cret) return -1;
2495 if (!ipc->uploading) return -1;
2497 sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2498 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2505 int CtdlIPCWriteUpload(CtdlIPC *ipc, FILE *uploadFP,
2506 void (*progress_gauge_callback)
2507 (CtdlIPC*, unsigned long, unsigned long),
2515 FILE *fd = uploadFP;
2518 if (!cret) return -1;
2520 fseek(fd, 0L, SEEK_END);
2524 if (progress_gauge_callback)
2525 progress_gauge_callback(ipc, 0, bytes);
2527 while (offset < bytes) {
2530 /* Read some data in */
2531 to_write = fread(buf, 1, 4096, fd);
2533 if (feof(fd) || ferror(fd)) break;
2535 sprintf(aaa, "WRIT %d", (int)to_write);
2536 CtdlIPC_putline(ipc, aaa);
2537 CtdlIPC_getline(ipc, aaa);
2538 strcpy(cret, &aaa[4]);
2540 if (aaa[0] == '7') {
2541 to_write = extract_long(&aaa[4], 0);
2543 serv_write(ipc, buf, to_write);
2545 if (progress_gauge_callback)
2546 progress_gauge_callback(ipc, offset, bytes);
2547 /* Detect short reads and back up if needed */
2548 /* offset will never be negative anyway */
2549 fseek(fd, (signed)offset, SEEK_SET);
2554 if (progress_gauge_callback)
2555 progress_gauge_callback(ipc, 1, 1);
2558 return (!ferr ? ret : -2);
2563 * Generic command method. This method should handle any server command
2564 * except for CHAT. It takes the following arguments:
2566 * ipc The server to speak with
2567 * command Preformatted command to send to server
2568 * to_send A text or binary file to send to server
2569 * (only sent if server requests it)
2570 * bytes_to_send The number of bytes in to_send (required if
2571 * sending binary, optional if sending listing)
2572 * to_receive Pointer to a NULL pointer, if the server
2573 * sends text or binary we will allocate memory
2574 * for the file and stuff it here
2575 * bytes_to_receive If a file is received, we will store its
2577 * proto_response The protocol response. Caller must provide
2578 * this buffer and ensure that it is at least
2579 * 128 bytes in length.
2581 * This function returns a number equal to the protocol response number,
2582 * -1 if an internal error occurred, -2 if caller provided bad values,
2583 * or 0 - the protocol response number if bad values were found during
2584 * the protocol exchange.
2585 * It stores the protocol response string (minus the number) in
2586 * protocol_response as described above. Some commands send additional
2587 * data in this string.
2589 int CtdlIPCGenericCommand(CtdlIPC *ipc,
2590 const char *command, const char *to_send,
2591 size_t bytes_to_send, char **to_receive,
2592 size_t *bytes_to_receive, char *proto_response)
2597 if (!command) return -2;
2598 if (!proto_response) return -2;
2601 CtdlIPC_putline(ipc, command);
2603 CtdlIPC_getline(ipc, proto_response);
2604 if (proto_response[3] == '*')
2606 ret = atoi(proto_response);
2607 strcpy(proto_response, &proto_response[4]);
2608 switch (ret / 100) {
2609 default: /* Unknown, punt */
2611 case 3: /* MORE_DATA */
2613 /* Don't need to do anything */
2615 case 1: /* LISTING_FOLLOWS */
2616 if (to_receive && !*to_receive && bytes_to_receive) {
2617 *to_receive = CtdlIPCReadListing(ipc, NULL);
2618 } else { /* Drain */
2619 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000")) ;
2623 case 4: /* SEND_LISTING */
2625 CtdlIPCSendListing(ipc, to_send);
2627 /* No listing given, fake it */
2628 CtdlIPC_putline(ipc, "000");
2632 case 6: /* BINARY_FOLLOWS */
2633 if (to_receive && !*to_receive && bytes_to_receive) {
2635 extract_long(proto_response, 0);
2636 *to_receive = (char *)
2637 malloc((size_t)*bytes_to_receive);
2641 serv_read(ipc, *to_receive,
2648 drain = extract_long(proto_response, 0);
2649 while (drain > SIZ) {
2650 serv_read(ipc, buf, SIZ);
2653 serv_read(ipc, buf, drain);
2657 case 7: /* SEND_BINARY */
2658 if (to_send && bytes_to_send) {
2659 serv_write(ipc, to_send, bytes_to_send);
2660 } else if (bytes_to_send) {
2661 /* Fake it, send nulls */
2664 fake = bytes_to_send;
2665 memset(buf, '\0', SIZ);
2666 while (fake > SIZ) {
2667 serv_write(ipc, buf, SIZ);
2670 serv_write(ipc, buf, fake);
2672 } /* else who knows? DANGER WILL ROBINSON */
2674 case 8: /* START_CHAT_MODE */
2675 if (!strncasecmp(command, "CHAT", 4)) {
2676 /* Don't call chatmode with generic! */
2677 CtdlIPC_putline(ipc, "/quit");
2680 /* In this mode we send then receive listing */
2682 CtdlIPCSendListing(ipc, to_send);
2684 /* No listing given, fake it */
2685 CtdlIPC_putline(ipc, "000");
2688 if (to_receive && !*to_receive
2689 && bytes_to_receive) {
2690 *to_receive = CtdlIPCReadListing(ipc, NULL);
2691 } else { /* Drain */
2692 while (CtdlIPC_getline(ipc, buf),
2693 strcmp(buf, "000")) ;
2698 case 9: /* ASYNC_MSG */
2699 /* CtdlIPCDoAsync(ret, proto_response); */
2700 free(CtdlIPCReadListing(ipc, NULL)); /* STUB FIXME */
2706 CtdlIPC_unlock(ipc);
2712 * Connect to a Citadel on a remote host using a TCP/IP socket
2714 static int tcp_connectsock(char *host, char *service)
2716 struct in6_addr serveraddr;
2717 struct addrinfo hints;
2718 struct addrinfo *res = NULL;
2719 struct addrinfo *ai = NULL;
2723 if ((host == NULL) || IsEmptyStr(host)) {
2724 service = DEFAULT_HOST ;
2726 if ((service == NULL) || IsEmptyStr(service)) {
2727 service = DEFAULT_PORT ;
2730 memset(&hints, 0x00, sizeof(hints));
2731 hints.ai_flags = AI_NUMERICSERV;
2732 hints.ai_family = AF_UNSPEC;
2733 hints.ai_socktype = SOCK_STREAM;
2736 * Handle numeric IPv4 and IPv6 addresses
2738 rc = inet_pton(AF_INET, host, &serveraddr);
2739 if (rc == 1) { /* dotted quad */
2740 hints.ai_family = AF_INET;
2741 hints.ai_flags |= AI_NUMERICHOST;
2744 rc = inet_pton(AF_INET6, host, &serveraddr);
2745 if (rc == 1) { /* IPv6 address */
2746 hints.ai_family = AF_INET6;
2747 hints.ai_flags |= AI_NUMERICHOST;
2751 /* Begin the connection process */
2753 rc = getaddrinfo(host, service, &hints, &res);
2759 * Try all available addresses until we connect to one or until we run out.
2761 for (ai = res; ai != NULL; ai = ai->ai_next) {
2762 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2763 if (sock < 0) return(-1);
2765 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2767 return(sock); /* Connected! */
2770 close(sock); /* Failed. Close the socket to avoid fd leak! */
2782 * Connect to a Citadel on the local host using a unix domain socket
2784 static int uds_connectsock(int *isLocal, char *sockpath)
2786 struct sockaddr_un addr;
2789 memset(&addr, 0, sizeof(addr));
2790 addr.sun_family = AF_UNIX;
2791 safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2793 s = socket(AF_UNIX, SOCK_STREAM, 0);
2798 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2809 * input binary data from socket
2811 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes)
2813 unsigned int len, rlen;
2815 #if defined(HAVE_OPENSSL)
2817 serv_read_ssl(ipc, buf, bytes);
2822 while (len < bytes) {
2823 rlen = read(ipc->sock, &buf[len], bytes - len);
2825 connection_died(ipc, 0);
2834 * send binary to server
2836 void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2838 unsigned int bytes_written = 0;
2841 #if defined(HAVE_OPENSSL)
2843 serv_write_ssl(ipc, buf, nbytes);
2847 while (bytes_written < nbytes) {
2848 retval = write(ipc->sock, &buf[bytes_written],
2849 nbytes - bytes_written);
2851 connection_died(ipc, 0);
2854 bytes_written += retval;
2861 * input binary data from encrypted connection
2863 static void serv_read_ssl(CtdlIPC* ipc, char *buf, unsigned int bytes)
2869 while (len < bytes) {
2870 if (SSL_want_read(ipc->ssl)) {
2871 if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2872 error_printf("SSL_write in serv_read:\n");
2873 ERR_print_errors_fp(stderr);
2876 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2880 errval = SSL_get_error(ipc->ssl, rlen);
2881 if (errval == SSL_ERROR_WANT_READ ||
2882 errval == SSL_ERROR_WANT_WRITE) {
2887 Not sure why we'd want to handle these error codes any differently,
2888 but this definitely isn't the way to handle them. Someone must have
2889 naively assumed that we could fall back to unencrypted communications,
2890 but all it does is just recursively blow the stack.
2891 if (errval == SSL_ERROR_ZERO_RETURN ||
2892 errval == SSL_ERROR_SSL) {
2893 serv_read(ipc, &buf[len], bytes - len);
2897 error_printf("SSL_read in serv_read: %s\n",
2898 ERR_reason_error_string(ERR_peek_error()));
2899 connection_died(ipc, 1);
2908 * send binary to server encrypted
2910 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2912 unsigned int bytes_written = 0;
2916 while (bytes_written < nbytes) {
2917 if (SSL_want_write(ipc->ssl)) {
2918 if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2919 error_printf("SSL_read in serv_write:\n");
2920 ERR_print_errors_fp(stderr);
2923 retval = SSL_write(ipc->ssl, &buf[bytes_written],
2924 nbytes - bytes_written);
2928 errval = SSL_get_error(ipc->ssl, retval);
2929 if (errval == SSL_ERROR_WANT_READ ||
2930 errval == SSL_ERROR_WANT_WRITE) {
2934 if (errval == SSL_ERROR_ZERO_RETURN ||
2935 errval == SSL_ERROR_SSL) {
2936 serv_write(ipc, &buf[bytes_written],
2937 nbytes - bytes_written);
2940 error_printf("SSL_write in serv_write: %s\n",
2941 ERR_reason_error_string(ERR_peek_error()));
2942 connection_died(ipc, 1);
2945 bytes_written += retval;
2950 #ifdef THREADED_CLIENT
2951 static void ssl_lock(int mode, int n, const char *file, int line)
2953 if (mode & CRYPTO_LOCK)
2954 pthread_mutex_lock(Critters[n]);
2956 pthread_mutex_unlock(Critters[n]);
2958 #endif /* THREADED_CLIENT */
2961 static void CtdlIPC_init_OpenSSL(void)
2964 const SSL_METHOD *ssl_method;
2967 /* already done init */
2976 SSL_load_error_strings();
2977 SSLeay_add_ssl_algorithms();
2979 /* Set up the SSL context in which we will oeprate */
2980 ssl_method = SSLv23_client_method();
2981 ssl_ctx = SSL_CTX_new(ssl_method);
2983 error_printf("SSL_CTX_new failed: %s\n",
2984 ERR_reason_error_string(ERR_get_error()));
2987 /* Any reasonable cipher we can get */
2988 if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
2989 error_printf("No ciphers available for encryption\n");
2992 SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
2994 /* Load DH parameters into the context */
2997 error_printf("Can't allocate a DH object: %s\n",
2998 ERR_reason_error_string(ERR_get_error()));
3001 if (!(BN_hex2bn(&(dh->p), DH_P))) {
3002 error_printf("Can't assign DH_P: %s\n",
3003 ERR_reason_error_string(ERR_get_error()));
3007 if (!(BN_hex2bn(&(dh->g), DH_G))) {
3008 error_printf("Can't assign DH_G: %s\n",
3009 ERR_reason_error_string(ERR_get_error()));
3014 SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3017 #ifdef THREADED_CLIENT
3018 /* OpenSSL requires callbacks for threaded clients */
3019 CRYPTO_set_locking_callback(ssl_lock);
3020 CRYPTO_set_id_callback(id_callback);
3022 /* OpenSSL requires us to do semaphores for threaded clients */
3023 Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
3025 perror("malloc failed");
3028 for (a = 0; a < CRYPTO_num_locks(); a++) {
3029 Critters[a] = malloc(sizeof (pthread_mutex_t));
3031 perror("malloc failed");
3034 pthread_mutex_init(Critters[a], NULL);
3037 #endif /* THREADED_CLIENT */
3042 #ifdef THREADED_CLIENT
3043 static unsigned long id_callback(void) {
3044 return (unsigned long)pthread_self();
3046 #endif /* THREADED_CLIENT */
3047 #endif /* HAVE_OPENSSL */
3051 ReadNetworkChunk(CtdlIPC* ipc)
3068 FD_SET(ipc->sock, &read_fd);
3069 ret = select(ipc->sock+1, &read_fd, NULL, NULL, &tv);
3071 // fprintf(stderr, "\nselect failed: %d %d %s\n", ret, err, strerror(err));
3075 *(ipc->BufPtr) = '\0';
3076 // n = read(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1);
3077 n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3079 ipc->BufPtr[n]='\0';
3087 if (!(errno == EINTR || errno == EAGAIN))
3088 error_printf( "\nselect failed: %d %s\n", err, strerror(err));
3094 n = read(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1);
3096 ipc->BufPtr[n]='\0';
3101 connection_died(ipc, 0);
3109 * input string from socket - implemented in terms of serv_read()
3113 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3116 char *aptr, *bptr, *aeptr, *beptr;
3118 // error_printf("---\n");
3121 #if defined(HAVE_OPENSSL)
3124 /* Read one character at a time. */
3126 serv_read(ipc, &buf[i], 1);
3127 if (buf[i] == '\n' || i == (SIZ-1))
3131 /* If we got a long line, discard characters until the newline. */
3133 while (buf[i] != '\n')
3134 serv_read(ipc, &buf[i], 1);
3136 /* Strip the trailing newline (and carriage return, if present) */
3137 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3138 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3143 if (ipc->Buf == NULL)
3146 ipc->Buf = (char*) malloc(ipc->BufSize + 10);
3148 ipc->BufPtr = ipc->Buf;
3152 // while ((ipc->BufUsed == 0)||(ntries++ > 10))
3153 if (ipc->BufUsed == 0)
3154 ReadNetworkChunk(ipc);
3156 //// if (ipc->BufUsed != 0) while (1)
3162 aeptr = ipc->Buf + ipc->BufSize;
3163 while ((aptr < aeptr) &&
3167 *(bptr++) = *(aptr++);
3168 if ((*aptr == '\n') && (aptr < aeptr))
3170 /* Terminate it right, remove the line breaks */
3171 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3173 while ((aptr < aeptr ) && (*(aptr + 1) == '\0') )
3176 // 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);
3177 if ((bptr > buf + 1) && (*(bptr-1) == '\r'))
3180 /* is there more in the buffer we need to read later? */
3181 if (ipc->Buf + ipc->BufUsed > aptr)
3188 ipc->BufPtr = ipc->Buf;
3190 // error_printf("----bla6\n");
3193 }/* should we move our read stuf to the bufferstart so we have more space at the end? */
3194 else if ((ipc->BufPtr != ipc->Buf) &&
3195 (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4))))
3197 size_t NewBufSize = ipc->BufSize * 2;
3198 int delta = (ipc->BufPtr - ipc->Buf);
3201 /* if the line would end after our buffer, we should use a bigger buffer. */
3202 NewBuf = (char *)malloc (NewBufSize + 10);
3203 memcpy (NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3205 ipc->Buf = ipc->BufPtr = NewBuf;
3206 ipc->BufUsed -= delta;
3207 ipc->BufSize = NewBufSize;
3209 if (ReadNetworkChunk(ipc) <0)
3211 // error_printf("----bla\n");
3215 /// error_printf("----bl45761%s\nipc->BufUsed");
3217 // error_printf("----bla1\n");
3220 #else /* CHUNKED_READ */
3222 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3226 /* Read one character at a time. */
3228 serv_read(ipc, &buf[i], 1);
3229 if (buf[i] == '\n' || i == (SIZ-1))
3233 /* If we got a long line, discard characters until the newline. */
3235 while (buf[i] != '\n')
3236 serv_read(ipc, &buf[i], 1);
3238 /* Strip the trailing newline (and carriage return, if present) */
3239 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3240 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3244 #endif /* CHUNKED_READ */
3247 void CtdlIPC_chat_recv(CtdlIPC* ipc, char* buf)
3249 CtdlIPC_getline(ipc, buf);
3253 * send line to server - implemented in terms of serv_write()
3255 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf)
3261 cmd = malloc(len + 2);
3263 /* This requires no extra memory */
3264 serv_write(ipc, buf, len);
3265 serv_write(ipc, "\n", 1);
3267 /* This is network-optimized */
3268 strncpy(cmd, buf, len);
3269 strcpy(cmd + len, "\n");
3270 serv_write(ipc, cmd, len + 1);
3274 ipc->last_command_sent = time(NULL);
3277 void CtdlIPC_chat_send(CtdlIPC* ipc, const char* buf)
3279 CtdlIPC_putline(ipc, buf);
3286 CtdlIPC* CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3294 ipc = ialloc(CtdlIPC);
3298 #if defined(HAVE_OPENSSL)
3300 CtdlIPC_init_OpenSSL();
3302 #if defined(HAVE_PTHREAD_H)
3303 pthread_mutex_init(&(ipc->mutex), NULL); /* Default fast mutex */
3305 ipc->sock = -1; /* Not connected */
3306 ipc->isLocal = 0; /* Not local, of course! */
3307 ipc->downloading = 0;
3309 ipc->last_command_sent = 0L;
3310 ipc->network_status_cb = NULL;
3315 strcpy(cithost, DEFAULT_HOST); /* default host */
3316 strcpy(citport, DEFAULT_PORT); /* default port */
3318 /* Allow caller to supply our values (Windows) */
3319 if (hostbuf && strlen(hostbuf) > 0)
3320 strcpy(cithost, hostbuf);
3321 if (portbuf && strlen(portbuf) > 0)
3322 strcpy(citport, portbuf);
3324 /* Read host/port from command line if present */
3325 for (a = 0; a < argc; ++a) {
3328 } else if (a == 1) {
3329 strcpy(cithost, argv[a]);
3330 } else if (a == 2) {
3331 strcpy(citport, argv[a]);
3333 error_printf("%s: usage: ",argv[0]);
3334 error_printf("%s [host] [port] ",argv[0]);
3341 if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3345 /* If we're using a unix domain socket we can do a bunch of stuff */
3346 if (!strcmp(cithost, UDS)) {
3347 if (!strcasecmp(citport, DEFAULT_PORT)) {
3348 snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3351 snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3353 printf("[%s]\n", sockpath);
3354 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3355 if (ipc->sock == -1) {
3359 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3360 if (portbuf != NULL) strcpy(portbuf, sockpath);
3361 strcpy(ipc->ip_hostname, "");
3362 strcpy(ipc->ip_address, "");
3366 printf("[%s:%s]\n", cithost, citport);
3367 ipc->sock = tcp_connectsock(cithost, citport);
3368 if (ipc->sock == -1) {
3374 /* Learn the actual network identity of the host to which we are connected */
3376 struct sockaddr_in6 clientaddr;
3377 unsigned int addrlen = sizeof(clientaddr);
3379 ipc->ip_hostname[0] = 0;
3380 ipc->ip_address[0] = 0;
3382 getpeername(ipc->sock, (struct sockaddr *)&clientaddr, &addrlen);
3383 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3384 ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0
3386 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3387 ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST
3390 /* stuff other things elsewhere */
3392 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3393 if (portbuf != NULL) strcpy(portbuf, citport);
3399 * Disconnect and delete the IPC class (destructor)
3401 void CtdlIPC_delete(CtdlIPC* ipc)
3405 SSL_shutdown(ipc->ssl);
3410 if (ipc->sock > -1) {
3411 shutdown(ipc->sock, 2); /* Close it up */
3414 if (ipc->Buf != NULL)
3423 * Disconnect and delete the IPC class (destructor)
3424 * Also NULLs out the pointer
3426 void CtdlIPC_delete_ptr(CtdlIPC** pipc)
3428 CtdlIPC_delete(*pipc);
3434 * return the file descriptor of the server socket so we can select() on it.
3436 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3439 int CtdlIPC_getsockfd(CtdlIPC* ipc)
3446 * return one character
3448 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3451 char CtdlIPC_get(CtdlIPC* ipc)
3456 serv_read(ipc, buf, 1);