2 * Copyright (c) 1987-2018 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, uret->timescalled,
1963 uret->posted, uret->axlevel, 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 int CtdlIPCAideSetEmailAddresses(CtdlIPC *ipc, const char *who, char *emailaddrs, char *cret)
2000 if (!who) return -2;
2001 if (!emailaddrs) return -2;
2002 if (!cret) return -2;
2004 sprintf(aaa, "ASEA %s", who);
2005 ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
2011 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2012 /* caller must free the struct ExpirePolicy */
2013 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC *ipc, GPEXWhichPolicy which,
2014 struct ExpirePolicy **policy, char *cret)
2016 static char *proto[] = {
2020 strof(mailboxespolicy)
2025 if (!cret) return -2;
2026 if (!policy) return -2;
2027 if (!*policy) *policy = (struct ExpirePolicy *)calloc(1, sizeof(struct ExpirePolicy));
2028 if (!*policy) return -1;
2029 if (which < 0 || which > 3) return -2;
2031 sprintf(cmd, "GPEX %s", proto[which]);
2032 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2033 if (ret / 100 == 2) {
2034 policy[0]->expire_mode = extract_int(cret, 0);
2035 policy[0]->expire_value = extract_int(cret, 1);
2042 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2043 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2044 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC *ipc, int which,
2045 struct ExpirePolicy *policy, char *cret)
2048 char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2050 if (!cret) return -2;
2051 if (which < 0 || which > 3) return -2;
2052 if (!policy) return -2;
2053 if (policy->expire_mode < 0 || policy->expire_mode > 3) return -2;
2054 if (policy->expire_mode >= 2 && policy->expire_value < 1) return -2;
2056 sprintf(aaa, "SPEX %s|%d|%d", whichvals[which],
2057 policy->expire_mode, policy->expire_value);
2058 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2063 int CtdlIPCGetSystemConfig(CtdlIPC *ipc, char **listing, char *cret)
2067 if (!cret) return -2;
2068 if (!listing) return -2;
2069 if (*listing) return -2;
2071 return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0,
2072 listing, &bytes, cret);
2077 int CtdlIPCSetSystemConfig(CtdlIPC *ipc, const char *listing, char *cret)
2079 if (!cret) return -2;
2080 if (!listing) return -2;
2082 return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing),
2088 int CtdlIPCGetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, char **listing, char *cret)
2094 if (!cret) return -2;
2095 if (!mimetype) return -2;
2096 if (!listing) return -2;
2097 if (*listing) return -2;
2099 aaa = malloc(strlen(mimetype) + 13);
2100 if (!aaa) return -1;
2101 sprintf(aaa, "CONF GETSYS|%s", mimetype);
2102 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2109 int CtdlIPCSetSystemConfigByType(CtdlIPC *ipc, const char *mimetype, const char *listing, char *cret)
2114 if (!cret) return -2;
2115 if (!mimetype) return -2;
2116 if (!listing) return -2;
2118 aaa = malloc(strlen(mimetype) + 13);
2119 if (!aaa) return -1;
2120 sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2121 ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2128 int CtdlIPCGetRoomNetworkConfig(CtdlIPC *ipc, char **listing, char *cret)
2132 if (!cret) return -2;
2133 if (!listing) return -2;
2134 if (*listing) return -2;
2136 return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2141 int CtdlIPCSetRoomNetworkConfig(CtdlIPC *ipc, const char *listing, char *cret)
2143 if (!cret) return -2;
2144 if (!listing) return -2;
2146 return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2151 int CtdlIPCRequestClientLogout(CtdlIPC *ipc, int session, char *cret)
2155 if (!cret) return -2;
2156 if (session < 0) return -2;
2158 sprintf(aaa, "REQT %d", session);
2159 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2164 int CtdlIPCSetMessageSeen(CtdlIPC *ipc, long msgnum, int seen, char *cret)
2168 if (!cret) return -2;
2169 if (msgnum < 0) return -2;
2171 sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2172 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2177 int CtdlIPCStartEncryption(CtdlIPC *ipc, char *cret)
2186 /* New SSL object */
2187 temp_ssl = SSL_new(ssl_ctx);
2189 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2192 /* Pointless flag waving */
2193 #if SSLEAY_VERSION_NUMBER >= 0x0922
2194 SSL_set_session_id_context(temp_ssl, (const unsigned char*) "Citadel SID", 14);
2197 /* Associate network connection with SSL object */
2198 if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2199 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2203 if (status_hook != NULL) {
2204 status_hook("Requesting encryption...\r");
2207 /* Ready to start SSL/TLS */
2208 r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2210 error_printf("Server can't start TLS: %s\n", buf);
2215 /* Do SSL/TLS handshake */
2216 if ((a = SSL_connect(temp_ssl)) < 1) {
2217 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2221 ipc->ssl = temp_ssl;
2223 error_printf("Encrypting with %s cipher %s\n",
2224 SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
2225 SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2230 #endif /* HAVE_OPENSSL */
2235 static void endtls(SSL *ssl)
2246 int CtdlIPCDirectoryLookup(CtdlIPC *ipc, const char *address, char *cret)
2251 if (!address) return -2;
2252 if (!cret) return -2;
2254 aaa = (char *)malloc(strlen(address) + 6);
2255 if (!aaa) return -1;
2257 sprintf(aaa, "QDIR %s", address);
2258 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2265 int CtdlIPCInternalProgram(CtdlIPC *ipc, int secret, char *cret)
2269 if (!cret) return -2;
2270 sprintf(aaa, "IPGM %d", secret);
2271 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2277 /* ************************************************************************** */
2278 /* Stuff below this line is not for public consumption */
2279 /* ************************************************************************** */
2282 /* Read a listing from the server up to 000. Append to dest if it exists */
2283 char *CtdlIPCReadListing(CtdlIPC *ipc, char *dest)
2292 length = strlen(ret);
2297 while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2298 linelength = strlen(aaa);
2299 ret = (char *)realloc(ret, (size_t)(length + linelength + 2));
2301 strcpy(&ret[length], aaa);
2302 length += linelength;
2303 strcpy(&ret[length++], "\n");
2311 /* Send a listing to the server; generate the ending 000. */
2312 int CtdlIPCSendListing(CtdlIPC *ipc, const char *listing)
2316 text = (char *)malloc(strlen(listing) + 6);
2318 strcpy(text, listing);
2319 while (text[strlen(text) - 1] == '\n')
2320 text[strlen(text) - 1] = '\0';
2321 strcat(text, "\n000");
2322 CtdlIPC_putline(ipc, text);
2326 /* Malloc failed but we are committed to send */
2327 /* This may result in extra blanks at the bottom */
2328 CtdlIPC_putline(ipc, text);
2329 CtdlIPC_putline(ipc, "000");
2335 /* Partial read of file from server */
2336 size_t CtdlIPCPartialRead(CtdlIPC *ipc, void **buf, size_t offset, size_t bytes, char *cret)
2342 if (!cret) return 0;
2343 if (bytes < 1) return 0;
2346 sprintf(aaa, "READ %d|%d", (int)offset, (int)bytes);
2347 CtdlIPC_putline(ipc, aaa);
2348 CtdlIPC_getline(ipc, aaa);
2350 strcpy(cret, &aaa[4]);
2352 len = extract_long(&aaa[4], 0);
2353 *buf = (void *)realloc(*buf, (size_t)(offset + len));
2355 /* I know what I'm doing */
2356 serv_read(ipc, ((char *)(*buf) + offset), len);
2358 /* We have to read regardless */
2359 serv_read(ipc, aaa, len);
2363 CtdlIPC_unlock(ipc);
2369 int CtdlIPCEndDownload(CtdlIPC *ipc, char *cret)
2373 if (!cret) return -2;
2374 if (!ipc->downloading) return -2;
2376 ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2378 ipc->downloading = 0;
2384 int CtdlIPCSpecifyPreferredFormats(CtdlIPC *ipc, char *cret, char *formats) {
2388 snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2389 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2396 int CtdlIPCReadDownload(CtdlIPC *ipc, void **buf, size_t bytes, size_t resume,
2397 void (*progress_gauge_callback)
2398 (CtdlIPC*, unsigned long, unsigned long),
2403 if (!cret) return -1;
2404 if (!buf) return -1;
2405 if (*buf) return -1;
2406 if (!ipc->downloading) return -1;
2409 if (progress_gauge_callback)
2410 progress_gauge_callback(ipc, len, bytes);
2411 while (len < bytes) {
2414 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2420 if (progress_gauge_callback)
2421 progress_gauge_callback(ipc, len, bytes);
2426 /* READ - pipelined */
2427 int CtdlIPCHighSpeedReadDownload(CtdlIPC *ipc, void **buf, size_t bytes,
2429 void (*progress_gauge_callback)
2430 (CtdlIPC*, unsigned long, unsigned long),
2434 int calls; /* How many calls in the pipeline */
2435 int i; /* iterator */
2438 if (!cret) return -1;
2439 if (!buf) return -1;
2440 if (*buf) return -1;
2441 if (!ipc->downloading) return -1;
2443 *buf = (void *)realloc(*buf, bytes - resume);
2444 if (!*buf) return -1;
2448 if (progress_gauge_callback)
2449 progress_gauge_callback(ipc, len, bytes);
2451 /* How many calls will be in the pipeline? */
2452 calls = (bytes - resume) / 4096;
2453 if ((bytes - resume) % 4096) calls++;
2455 /* Send all requests at once */
2456 for (i = 0; i < calls; i++) {
2457 sprintf(aaa, "READ %d|4096", (int)(i * 4096 + resume) );
2458 CtdlIPC_putline(ipc, aaa);
2461 /* Receive all responses at once */
2462 for (i = 0; i < calls; i++) {
2463 CtdlIPC_getline(ipc, aaa);
2465 strcpy(cret, &aaa[4]);
2467 len = extract_long(&aaa[4], 0);
2468 /* I know what I'm doing */
2469 serv_read(ipc, ((char *)(*buf) + (i * 4096)), len);
2471 if (progress_gauge_callback)
2472 progress_gauge_callback(ipc, i * 4096 + len, bytes);
2474 CtdlIPC_unlock(ipc);
2480 int CtdlIPCEndUpload(CtdlIPC *ipc, int discard, char *cret)
2485 if (!cret) return -1;
2486 if (!ipc->uploading) return -1;
2488 sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2489 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2496 int CtdlIPCWriteUpload(CtdlIPC *ipc, FILE *uploadFP,
2497 void (*progress_gauge_callback)
2498 (CtdlIPC*, unsigned long, unsigned long),
2506 FILE *fd = uploadFP;
2509 if (!cret) return -1;
2511 fseek(fd, 0L, SEEK_END);
2515 if (progress_gauge_callback)
2516 progress_gauge_callback(ipc, 0, bytes);
2518 while (offset < bytes) {
2521 /* Read some data in */
2522 to_write = fread(buf, 1, 4096, fd);
2524 if (feof(fd) || ferror(fd)) break;
2526 sprintf(aaa, "WRIT %d", (int)to_write);
2527 CtdlIPC_putline(ipc, aaa);
2528 CtdlIPC_getline(ipc, aaa);
2529 strcpy(cret, &aaa[4]);
2531 if (aaa[0] == '7') {
2532 to_write = extract_long(&aaa[4], 0);
2534 serv_write(ipc, buf, to_write);
2536 if (progress_gauge_callback)
2537 progress_gauge_callback(ipc, offset, bytes);
2538 /* Detect short reads and back up if needed */
2539 /* offset will never be negative anyway */
2540 fseek(fd, (signed)offset, SEEK_SET);
2545 if (progress_gauge_callback)
2546 progress_gauge_callback(ipc, 1, 1);
2549 return (!ferr ? ret : -2);
2554 * Generic command method. This method should handle any server command
2555 * except for CHAT. It takes the following arguments:
2557 * ipc The server to speak with
2558 * command Preformatted command to send to server
2559 * to_send A text or binary file to send to server
2560 * (only sent if server requests it)
2561 * bytes_to_send The number of bytes in to_send (required if
2562 * sending binary, optional if sending listing)
2563 * to_receive Pointer to a NULL pointer, if the server
2564 * sends text or binary we will allocate memory
2565 * for the file and stuff it here
2566 * bytes_to_receive If a file is received, we will store its
2568 * proto_response The protocol response. Caller must provide
2569 * this buffer and ensure that it is at least
2570 * 128 bytes in length.
2572 * This function returns a number equal to the protocol response number,
2573 * -1 if an internal error occurred, -2 if caller provided bad values,
2574 * or 0 - the protocol response number if bad values were found during
2575 * the protocol exchange.
2576 * It stores the protocol response string (minus the number) in
2577 * protocol_response as described above. Some commands send additional
2578 * data in this string.
2580 int CtdlIPCGenericCommand(CtdlIPC *ipc,
2581 const char *command, const char *to_send,
2582 size_t bytes_to_send, char **to_receive,
2583 size_t *bytes_to_receive, char *proto_response)
2588 if (!command) return -2;
2589 if (!proto_response) return -2;
2592 CtdlIPC_putline(ipc, command);
2594 CtdlIPC_getline(ipc, proto_response);
2595 if (proto_response[3] == '*')
2597 ret = atoi(proto_response);
2598 strcpy(proto_response, &proto_response[4]);
2599 switch (ret / 100) {
2600 default: /* Unknown, punt */
2602 case 3: /* MORE_DATA */
2604 /* Don't need to do anything */
2606 case 1: /* LISTING_FOLLOWS */
2607 if (to_receive && !*to_receive && bytes_to_receive) {
2608 *to_receive = CtdlIPCReadListing(ipc, NULL);
2609 } else { /* Drain */
2610 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000")) ;
2614 case 4: /* SEND_LISTING */
2616 CtdlIPCSendListing(ipc, to_send);
2618 /* No listing given, fake it */
2619 CtdlIPC_putline(ipc, "000");
2623 case 6: /* BINARY_FOLLOWS */
2624 if (to_receive && !*to_receive && bytes_to_receive) {
2626 extract_long(proto_response, 0);
2627 *to_receive = (char *)
2628 malloc((size_t)*bytes_to_receive);
2632 serv_read(ipc, *to_receive,
2639 drain = extract_long(proto_response, 0);
2640 while (drain > SIZ) {
2641 serv_read(ipc, buf, SIZ);
2644 serv_read(ipc, buf, drain);
2648 case 7: /* SEND_BINARY */
2649 if (to_send && bytes_to_send) {
2650 serv_write(ipc, to_send, bytes_to_send);
2651 } else if (bytes_to_send) {
2652 /* Fake it, send nulls */
2655 fake = bytes_to_send;
2656 memset(buf, '\0', SIZ);
2657 while (fake > SIZ) {
2658 serv_write(ipc, buf, SIZ);
2661 serv_write(ipc, buf, fake);
2663 } /* else who knows? DANGER WILL ROBINSON */
2665 case 8: /* START_CHAT_MODE */
2666 if (!strncasecmp(command, "CHAT", 4)) {
2667 /* Don't call chatmode with generic! */
2668 CtdlIPC_putline(ipc, "/quit");
2671 /* In this mode we send then receive listing */
2673 CtdlIPCSendListing(ipc, to_send);
2675 /* No listing given, fake it */
2676 CtdlIPC_putline(ipc, "000");
2679 if (to_receive && !*to_receive
2680 && bytes_to_receive) {
2681 *to_receive = CtdlIPCReadListing(ipc, NULL);
2682 } else { /* Drain */
2683 while (CtdlIPC_getline(ipc, buf),
2684 strcmp(buf, "000")) ;
2689 case 9: /* ASYNC_MSG */
2690 /* CtdlIPCDoAsync(ret, proto_response); */
2691 free(CtdlIPCReadListing(ipc, NULL)); /* STUB FIXME */
2697 CtdlIPC_unlock(ipc);
2703 * Connect to a Citadel on a remote host using a TCP/IP socket
2705 static int tcp_connectsock(char *host, char *service)
2707 struct in6_addr serveraddr;
2708 struct addrinfo hints;
2709 struct addrinfo *res = NULL;
2710 struct addrinfo *ai = NULL;
2714 if ((host == NULL) || IsEmptyStr(host)) {
2715 service = DEFAULT_HOST ;
2717 if ((service == NULL) || IsEmptyStr(service)) {
2718 service = DEFAULT_PORT ;
2721 memset(&hints, 0x00, sizeof(hints));
2722 hints.ai_flags = AI_NUMERICSERV;
2723 hints.ai_family = AF_UNSPEC;
2724 hints.ai_socktype = SOCK_STREAM;
2727 * Handle numeric IPv4 and IPv6 addresses
2729 rc = inet_pton(AF_INET, host, &serveraddr);
2730 if (rc == 1) { /* dotted quad */
2731 hints.ai_family = AF_INET;
2732 hints.ai_flags |= AI_NUMERICHOST;
2735 rc = inet_pton(AF_INET6, host, &serveraddr);
2736 if (rc == 1) { /* IPv6 address */
2737 hints.ai_family = AF_INET6;
2738 hints.ai_flags |= AI_NUMERICHOST;
2742 /* Begin the connection process */
2744 rc = getaddrinfo(host, service, &hints, &res);
2750 * Try all available addresses until we connect to one or until we run out.
2752 for (ai = res; ai != NULL; ai = ai->ai_next) {
2753 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2754 if (sock < 0) return(-1);
2756 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2758 return(sock); /* Connected! */
2761 close(sock); /* Failed. Close the socket to avoid fd leak! */
2773 * Connect to a Citadel on the local host using a unix domain socket
2775 static int uds_connectsock(int *isLocal, char *sockpath)
2777 struct sockaddr_un addr;
2780 memset(&addr, 0, sizeof(addr));
2781 addr.sun_family = AF_UNIX;
2782 safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2784 s = socket(AF_UNIX, SOCK_STREAM, 0);
2789 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2800 * input binary data from socket
2802 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes)
2804 unsigned int len, rlen;
2806 #if defined(HAVE_OPENSSL)
2808 serv_read_ssl(ipc, buf, bytes);
2813 while (len < bytes) {
2814 rlen = read(ipc->sock, &buf[len], bytes - len);
2816 connection_died(ipc, 0);
2825 * send binary to server
2827 void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2829 unsigned int bytes_written = 0;
2832 #if defined(HAVE_OPENSSL)
2834 serv_write_ssl(ipc, buf, nbytes);
2838 while (bytes_written < nbytes) {
2839 retval = write(ipc->sock, &buf[bytes_written],
2840 nbytes - bytes_written);
2842 connection_died(ipc, 0);
2845 bytes_written += retval;
2852 * input binary data from encrypted connection
2854 static void serv_read_ssl(CtdlIPC* ipc, char *buf, unsigned int bytes)
2860 while (len < bytes) {
2861 if (SSL_want_read(ipc->ssl)) {
2862 if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2863 error_printf("SSL_write in serv_read:\n");
2864 ERR_print_errors_fp(stderr);
2867 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2871 errval = SSL_get_error(ipc->ssl, rlen);
2872 if (errval == SSL_ERROR_WANT_READ ||
2873 errval == SSL_ERROR_WANT_WRITE) {
2878 Not sure why we'd want to handle these error codes any differently,
2879 but this definitely isn't the way to handle them. Someone must have
2880 naively assumed that we could fall back to unencrypted communications,
2881 but all it does is just recursively blow the stack.
2882 if (errval == SSL_ERROR_ZERO_RETURN ||
2883 errval == SSL_ERROR_SSL) {
2884 serv_read(ipc, &buf[len], bytes - len);
2888 error_printf("SSL_read in serv_read: %s\n",
2889 ERR_reason_error_string(ERR_peek_error()));
2890 connection_died(ipc, 1);
2899 * send binary to server encrypted
2901 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2903 unsigned int bytes_written = 0;
2907 while (bytes_written < nbytes) {
2908 if (SSL_want_write(ipc->ssl)) {
2909 if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2910 error_printf("SSL_read in serv_write:\n");
2911 ERR_print_errors_fp(stderr);
2914 retval = SSL_write(ipc->ssl, &buf[bytes_written],
2915 nbytes - bytes_written);
2919 errval = SSL_get_error(ipc->ssl, retval);
2920 if (errval == SSL_ERROR_WANT_READ ||
2921 errval == SSL_ERROR_WANT_WRITE) {
2925 if (errval == SSL_ERROR_ZERO_RETURN ||
2926 errval == SSL_ERROR_SSL) {
2927 serv_write(ipc, &buf[bytes_written],
2928 nbytes - bytes_written);
2931 error_printf("SSL_write in serv_write: %s\n",
2932 ERR_reason_error_string(ERR_peek_error()));
2933 connection_died(ipc, 1);
2936 bytes_written += retval;
2941 #ifdef THREADED_CLIENT
2942 static void ssl_lock(int mode, int n, const char *file, int line)
2944 if (mode & CRYPTO_LOCK)
2945 pthread_mutex_lock(Critters[n]);
2947 pthread_mutex_unlock(Critters[n]);
2949 #endif /* THREADED_CLIENT */
2952 static void CtdlIPC_init_OpenSSL(void)
2955 const SSL_METHOD *ssl_method;
2958 /* already done init */
2967 SSL_load_error_strings();
2968 SSLeay_add_ssl_algorithms();
2970 /* Set up the SSL context in which we will oeprate */
2971 ssl_method = SSLv23_client_method();
2972 ssl_ctx = SSL_CTX_new(ssl_method);
2974 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2977 /* Any reasonable cipher we can get */
2978 if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
2979 error_printf("No ciphers available for encryption\n");
2982 SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
2984 /* Load DH parameters into the context */
2987 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
2991 if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
2992 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
2997 SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3000 #ifdef THREADED_CLIENT
3001 /* OpenSSL requires callbacks for threaded clients */
3002 CRYPTO_set_locking_callback(ssl_lock);
3003 CRYPTO_set_id_callback(id_callback);
3005 /* OpenSSL requires us to do semaphores for threaded clients */
3006 Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
3008 perror("malloc failed");
3011 for (a = 0; a < CRYPTO_num_locks(); a++) {
3012 Critters[a] = malloc(sizeof (pthread_mutex_t));
3014 perror("malloc failed");
3017 pthread_mutex_init(Critters[a], NULL);
3020 #endif /* THREADED_CLIENT */
3025 #ifdef THREADED_CLIENT
3026 static unsigned long id_callback(void) {
3027 return (unsigned long)pthread_self();
3029 #endif /* THREADED_CLIENT */
3030 #endif /* HAVE_OPENSSL */
3034 ReadNetworkChunk(CtdlIPC* ipc)
3050 FD_SET(ipc->sock, &read_fd);
3051 ret = select(ipc->sock+1, &read_fd, NULL, NULL, &tv);
3055 *(ipc->BufPtr) = '\0';
3056 n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3058 ipc->BufPtr[n]='\0';
3066 if (!(errno == EINTR || errno == EAGAIN))
3067 error_printf( "\nselect failed: %d %s\n", err, strerror(err));
3073 n = read(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1);
3075 ipc->BufPtr[n]='\0';
3080 connection_died(ipc, 0);
3088 * input string from socket - implemented in terms of serv_read()
3092 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3095 char *aptr, *bptr, *aeptr, *beptr;
3097 // error_printf("---\n");
3100 #if defined(HAVE_OPENSSL)
3103 /* Read one character at a time. */
3105 serv_read(ipc, &buf[i], 1);
3106 if (buf[i] == '\n' || i == (SIZ-1))
3110 /* If we got a long line, discard characters until the newline. */
3112 while (buf[i] != '\n')
3113 serv_read(ipc, &buf[i], 1);
3115 /* Strip the trailing newline (and carriage return, if present) */
3116 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3117 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3122 if (ipc->Buf == NULL)
3125 ipc->Buf = (char*) malloc(ipc->BufSize + 10);
3127 ipc->BufPtr = ipc->Buf;
3131 // while ((ipc->BufUsed == 0)||(ntries++ > 10))
3132 if (ipc->BufUsed == 0)
3133 ReadNetworkChunk(ipc);
3135 //// if (ipc->BufUsed != 0) while (1)
3141 aeptr = ipc->Buf + ipc->BufSize;
3142 while ((aptr < aeptr) &&
3146 *(bptr++) = *(aptr++);
3147 if ((*aptr == '\n') && (aptr < aeptr))
3149 /* Terminate it right, remove the line breaks */
3150 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3152 while ((aptr < aeptr ) && (*(aptr + 1) == '\0') )
3155 // 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);
3156 if ((bptr > buf + 1) && (*(bptr-1) == '\r'))
3159 /* is there more in the buffer we need to read later? */
3160 if (ipc->Buf + ipc->BufUsed > aptr)
3167 ipc->BufPtr = ipc->Buf;
3169 // error_printf("----bla6\n");
3172 }/* should we move our read stuf to the bufferstart so we have more space at the end? */
3173 else if ((ipc->BufPtr != ipc->Buf) &&
3174 (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4))))
3176 size_t NewBufSize = ipc->BufSize * 2;
3177 int delta = (ipc->BufPtr - ipc->Buf);
3180 /* if the line would end after our buffer, we should use a bigger buffer. */
3181 NewBuf = (char *)malloc (NewBufSize + 10);
3182 memcpy (NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3184 ipc->Buf = ipc->BufPtr = NewBuf;
3185 ipc->BufUsed -= delta;
3186 ipc->BufSize = NewBufSize;
3188 if (ReadNetworkChunk(ipc) <0)
3190 // error_printf("----bla\n");
3194 /// error_printf("----bl45761%s\nipc->BufUsed");
3196 // error_printf("----bla1\n");
3199 #else /* CHUNKED_READ */
3201 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3205 /* Read one character at a time. */
3207 serv_read(ipc, &buf[i], 1);
3208 if (buf[i] == '\n' || i == (SIZ-1))
3212 /* If we got a long line, discard characters until the newline. */
3214 while (buf[i] != '\n')
3215 serv_read(ipc, &buf[i], 1);
3217 /* Strip the trailing newline (and carriage return, if present) */
3218 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3219 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3223 #endif /* CHUNKED_READ */
3226 void CtdlIPC_chat_recv(CtdlIPC* ipc, char* buf)
3228 CtdlIPC_getline(ipc, buf);
3232 * send line to server - implemented in terms of serv_write()
3234 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf)
3240 cmd = malloc(len + 2);
3242 /* This requires no extra memory */
3243 serv_write(ipc, buf, len);
3244 serv_write(ipc, "\n", 1);
3246 /* This is network-optimized */
3247 strncpy(cmd, buf, len);
3248 strcpy(cmd + len, "\n");
3249 serv_write(ipc, cmd, len + 1);
3253 ipc->last_command_sent = time(NULL);
3256 void CtdlIPC_chat_send(CtdlIPC* ipc, const char* buf)
3258 CtdlIPC_putline(ipc, buf);
3265 CtdlIPC* CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3273 ipc = ialloc(CtdlIPC);
3277 #if defined(HAVE_OPENSSL)
3279 CtdlIPC_init_OpenSSL();
3281 #if defined(HAVE_PTHREAD_H)
3282 pthread_mutex_init(&(ipc->mutex), NULL); /* Default fast mutex */
3284 ipc->sock = -1; /* Not connected */
3285 ipc->isLocal = 0; /* Not local, of course! */
3286 ipc->downloading = 0;
3288 ipc->last_command_sent = 0L;
3289 ipc->network_status_cb = NULL;
3294 strcpy(cithost, DEFAULT_HOST); /* default host */
3295 strcpy(citport, DEFAULT_PORT); /* default port */
3297 /* Allow caller to supply our values */
3298 if (hostbuf && strlen(hostbuf) > 0) {
3299 strcpy(cithost, hostbuf);
3301 if (portbuf && strlen(portbuf) > 0) {
3302 strcpy(citport, portbuf);
3305 /* Read host/port from command line if present */
3306 for (a = 0; a < argc; ++a) {
3309 } else if (a == 1) {
3310 strcpy(cithost, argv[a]);
3311 } else if (a == 2) {
3312 strcpy(citport, argv[a]);
3314 error_printf("%s: usage: ",argv[0]);
3315 error_printf("%s [host] [port] ",argv[0]);
3322 if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3326 /* If we're using a unix domain socket we can do a bunch of stuff */
3327 if (!strcmp(cithost, UDS)) {
3328 if (!strcasecmp(citport, DEFAULT_PORT)) {
3329 snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3332 snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3334 printf("[%s]\n", sockpath);
3335 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3336 if (ipc->sock == -1) {
3340 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3341 if (portbuf != NULL) strcpy(portbuf, sockpath);
3342 strcpy(ipc->ip_hostname, "");
3343 strcpy(ipc->ip_address, "");
3347 printf("[%s:%s]\n", cithost, citport);
3348 ipc->sock = tcp_connectsock(cithost, citport);
3349 if (ipc->sock == -1) {
3355 /* Learn the actual network identity of the host to which we are connected */
3357 struct sockaddr_in6 clientaddr;
3358 unsigned int addrlen = sizeof(clientaddr);
3360 ipc->ip_hostname[0] = 0;
3361 ipc->ip_address[0] = 0;
3363 getpeername(ipc->sock, (struct sockaddr *)&clientaddr, &addrlen);
3364 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3365 ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0
3367 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3368 ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST
3371 /* stuff other things elsewhere */
3373 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3374 if (portbuf != NULL) strcpy(portbuf, citport);
3380 * Disconnect and delete the IPC class (destructor)
3382 void CtdlIPC_delete(CtdlIPC* ipc)
3386 SSL_shutdown(ipc->ssl);
3391 if (ipc->sock > -1) {
3392 shutdown(ipc->sock, 2); /* Close it up */
3395 if (ipc->Buf != NULL)
3404 * Disconnect and delete the IPC class (destructor)
3405 * Also NULLs out the pointer
3407 void CtdlIPC_delete_ptr(CtdlIPC** pipc)
3409 CtdlIPC_delete(*pipc);
3415 * return the file descriptor of the server socket so we can select() on it.
3417 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3420 int CtdlIPC_getsockfd(CtdlIPC* ipc)
3427 * return one character
3429 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3432 char CtdlIPC_get(CtdlIPC* ipc)
3437 serv_read(ipc, buf, 1);