2 * Copyright (c) 1987-2016 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]="";
69 char file_citadel_config[PATH_MAX]="";
92 void CtdlIPC_lock(CtdlIPC *ipc)
94 if (ipc->network_status_cb) ipc->network_status_cb(1);
95 #ifdef THREADED_CLIENT
96 pthread_mutex_lock(&(ipc->mutex));
101 void CtdlIPC_unlock(CtdlIPC *ipc)
103 #ifdef THREADED_CLIENT
104 pthread_mutex_unlock(&(ipc->mutex));
106 if (ipc->network_status_cb) ipc->network_status_cb(0);
114 char *libcitadelclient_version_string(void) {
115 return "libcitadelclient(unnumbered)";
121 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
122 snprintf(SUBDIR,sizeof SUBDIR, "%s%s%s%s%s%s%s", \
123 (home&!relh)?ctdl_home_directory:basedir, \
124 ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
125 ((basedir!=ctdldir)&(home&!relh))?"/":"", \
127 (relhome[0]!='\0')?"/":"",\
129 (dirbuffer[0]!='\0')?"/":"");
131 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
134 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg)
136 const char* basedir = "";
137 char dirbuffer[PATH_MAX] = "";
139 StripSlashes(ctdldir, 1);
146 COMPUTE_DIRECTORY(ctdl_run_dir);
147 StripSlashes(ctdl_run_dir, 1);
150 #ifndef HAVE_AUTO_ETC_DIR
153 basedir=AUTO_ETC_DIR;
155 COMPUTE_DIRECTORY(ctdl_autoetc_dir);
156 StripSlashes(ctdl_autoetc_dir, 1);
164 COMPUTE_DIRECTORY(ctdl_etc_dir);
165 StripSlashes(ctdl_etc_dir, 1);
169 snprintf(file_citadel_rc,
170 sizeof file_citadel_rc,
173 StripSlashes(file_citadel_rc, 0);
175 snprintf(file_citadel_socket,
176 sizeof file_citadel_socket,
179 StripSlashes(file_citadel_socket, 0);
181 snprintf(file_citadel_config,
182 sizeof file_citadel_config,
185 StripSlashes(file_citadel_config, 0);
187 DBG_PRINT(ctdl_run_dir);
188 DBG_PRINT(file_citadel_socket);
189 DBG_PRINT(ctdl_etc_dir);
190 DBG_PRINT(file_citadel_rc);
193 void setCryptoStatusHook(void (*hook)(char *s)) {
197 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC *ipc, void (*hook)(int state)) {
198 ipc->network_status_cb = hook;
202 char instant_msgs = 0;
205 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes);
206 static void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
208 static void serv_read_ssl(CtdlIPC *ipc, char *buf, unsigned int bytes);
209 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes);
210 static void endtls(SSL *ssl);
211 #ifdef THREADED_CLIENT
212 static unsigned long id_callback(void);
213 #endif /* THREADED_CLIENT */
214 #endif /* HAVE_OPENSSL */
215 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf);
216 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf);
220 const char *svn_revision(void);
223 * Does nothing. The server should always return 200.
225 int CtdlIPCNoop(CtdlIPC *ipc)
229 return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
234 * Does nothing interesting. The server should always return 200
235 * along with your string.
237 int CtdlIPCEcho(CtdlIPC *ipc, const char *arg, char *cret)
243 if (!cret) return -2;
245 aaa = (char *)malloc((size_t)(strlen(arg) + 6));
248 sprintf(aaa, "ECHO %s", arg);
249 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
256 * Asks the server to close the connecction.
257 * Should always return 200.
259 int CtdlIPCQuit(CtdlIPC *ipc)
261 register int ret = 221; /* Default to successful quit */
265 if (ipc->sock > -1) {
266 CtdlIPC_putline(ipc, "QUIT");
267 CtdlIPC_getline(ipc, aaa);
272 SSL_shutdown(ipc->ssl);
276 shutdown(ipc->sock, 2); /* Close connection; we're dead */
284 * Asks the server to log out. Should always return 200, even if no user
285 * was logged in. The user will not be logged in after this!
287 int CtdlIPCLogout(CtdlIPC *ipc)
293 CtdlIPC_putline(ipc, "LOUT");
294 CtdlIPC_getline(ipc, aaa);
302 * First stage of authentication - pass the username. Returns 300 if the
303 * username is able to log in, with the username correctly spelled in cret.
304 * Returns various 500 error codes if the user doesn't exist, etc.
306 int CtdlIPCTryLogin(CtdlIPC *ipc, const char *username, char *cret)
311 if (!username) return -2;
312 if (!cret) return -2;
314 aaa = (char *)malloc((size_t)(strlen(username) + 6));
317 sprintf(aaa, "USER %s", username);
318 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
325 * Second stage of authentication - provide password. The server returns
326 * 200 and several arguments in cret relating to the user's account.
328 int CtdlIPCTryPassword(CtdlIPC *ipc, const char *passwd, char *cret)
333 if (!passwd) return -2;
334 if (!cret) return -2;
336 aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
339 sprintf(aaa, "PASS %s", passwd);
340 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
347 * Second stage of authentication - provide password. The server returns
348 * 200 and several arguments in cret relating to the user's account.
350 int CtdlIPCTryApopPassword(CtdlIPC *ipc, const char *response, char *cret)
355 if (!response) return -2;
356 if (!cret) return -2;
358 aaa = (char *)malloc((size_t)(strlen(response) + 6));
361 sprintf(aaa, "PAS2 %s", response);
362 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
369 * Create a new user. This returns 200 plus the same arguments as TryPassword
370 * if selfservice is nonzero, unless there was a problem creating the account.
371 * If selfservice is zero, creates a new user but does not log out the existing
372 * user - intended for use by system administrators to create accounts on
373 * behalf of other users.
375 int CtdlIPCCreateUser(CtdlIPC *ipc, const char *username, int selfservice, char *cret)
380 if (!username) return -2;
381 if (!cret) return -2;
383 aaa = (char *)malloc((size_t)(strlen(username) + 6));
386 sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
387 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
394 * Changes the user's password. Returns 200 if changed, errors otherwise.
396 int CtdlIPCChangePassword(CtdlIPC *ipc, const char *passwd, char *cret)
401 if (!passwd) return -2;
402 if (!cret) return -2;
404 aaa = (char *)malloc((size_t)(strlen(passwd) + 6));
407 sprintf(aaa, "SETP %s", passwd);
408 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
415 /* Caller must free the march list */
416 /* Room types are defined in enum RoomList; keep these in sync! */
417 /* floor is -1 for all, or floornum */
418 int CtdlIPCKnownRooms(CtdlIPC *ipc, enum RoomList which, int floor, struct march **listing, char *cret)
421 struct march *march = NULL;
422 static char *proto[] =
423 {"LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
428 if (!listing) return -2;
429 if (*listing) return -2; /* Free the listing first */
430 if (!cret) return -2;
431 /* if (which < 0 || which > 4) return -2; */
432 if (floor < -1) return -2; /* Can't validate upper bound, sorry */
434 sprintf(aaa, "%s %d", proto[which], floor);
435 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
436 if (ret / 100 == 1) {
439 while (bbb && strlen(bbb)) {
442 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
444 memmove(bbb, bbb + a + 1, strlen(bbb) - a);
445 mptr = (struct march *) malloc(sizeof (struct march));
448 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
449 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
450 mptr->march_floor = (char) extract_int(aaa, 2);
451 mptr->march_order = (char) extract_int(aaa, 3);
452 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
453 mptr->march_access = (char) extract_int(aaa, 5);
460 while (mptr2->next != NULL)
474 /* Caller must free the struct ctdluser; caller may pass an existing one */
475 int CtdlIPCGetConfig(CtdlIPC *ipc, struct ctdluser **uret, char *cret)
479 if (!cret) return -2;
480 if (!uret) return -2;
481 if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof (struct ctdluser));
482 if (!*uret) return -1;
484 ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
485 if (ret / 100 == 2) {
486 uret[0]->flags = extract_int(cret, 2);
493 int CtdlIPCSetConfig(CtdlIPC *ipc, struct ctdluser *uret, char *cret)
497 if (!uret) return -2;
498 if (!cret) return -2;
504 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
509 int CtdlIPCRenameUser(CtdlIPC *ipc, char *oldname, char *newname, char *cret)
514 if (!oldname) return -2;
515 if (!newname) return -2;
516 if (!cret) return -2;
518 snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
519 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
525 int CtdlIPCGotoRoom(CtdlIPC *ipc, const char *room, const char *passwd,
526 struct ctdlipcroom **rret, char *cret)
531 if (!cret) return -2;
532 if (!rret) return -2;
533 if (!*rret) *rret = (struct ctdlipcroom *)calloc(1, sizeof (struct ctdlipcroom));
534 if (!*rret) return -1;
537 aaa = (char *)malloc(strlen(room) + strlen(passwd) + 7);
542 sprintf(aaa, "GOTO %s|%s", room, passwd);
544 aaa = (char *)malloc(strlen(room) + 6);
549 sprintf(aaa, "GOTO %s", room);
551 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
552 if (ret / 100 == 2) {
553 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
554 rret[0]->RRunread = extract_long(cret, 1);
555 rret[0]->RRtotal = extract_long(cret, 2);
556 rret[0]->RRinfoupdated = extract_int(cret, 3);
557 rret[0]->RRflags = extract_int(cret, 4);
558 rret[0]->RRhighest = extract_long(cret, 5);
559 rret[0]->RRlastread = extract_long(cret, 6);
560 rret[0]->RRismailbox = extract_int(cret, 7);
561 rret[0]->RRaide = extract_int(cret, 8);
562 rret[0]->RRnewmail = extract_long(cret, 9);
563 rret[0]->RRfloor = extract_int(cret, 10);
564 rret[0]->RRcurrentview = extract_int(cret, 11);
565 rret[0]->RRdefaultview = extract_int(cret, 12);
566 /* position 13 is a trash folder flag ... irrelevant in this client */
567 rret[0]->RRflags2 = extract_int(cret, 14);
578 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
579 /* whicharg is number of messages, applies to last, first, gt, lt */
580 int CtdlIPCGetMessages(CtdlIPC *ipc, enum MessageList which, int whicharg,
581 const char *mtemplate, unsigned long **mret, char *cret)
584 register unsigned long count = 0;
585 static char *proto[] =
586 { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
591 if (!cret) return -2;
592 if (!mret) return -2;
593 if (*mret) return -2;
594 if (which < 0 || which > 6) return -2;
597 sprintf(aaa, "MSGS %s||%d", proto[which],
598 (mtemplate) ? 1 : 0);
600 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg,
601 (mtemplate) ? 1 : 0);
602 if (mtemplate) count = strlen(mtemplate);
603 ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
607 *mret = (unsigned long *)calloc(1, sizeof(unsigned long));
610 while (bbb && strlen(bbb)) {
611 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
612 remove_token(bbb, 0, '\n');
613 *mret = (unsigned long *)realloc(*mret, (size_t)((count + 2) *
614 sizeof (unsigned long)));
616 (*mret)[count++] = atol(aaa);
628 int CtdlIPCGetSingleMessage(CtdlIPC *ipc, long msgnum, int headers, int as_mime,
629 struct ctdlipcmessage **mret, char *cret)
635 int multipart_hunting = 0;
636 char multipart_prefix[128];
639 if (!cret) return -1;
640 if (!mret) return -1;
641 if (!*mret) *mret = (struct ctdlipcmessage *)calloc(1, sizeof (struct ctdlipcmessage));
642 if (!*mret) return -1;
643 if (!msgnum) return -1;
645 strcpy(encoding, "");
646 strcpy(mret[0]->content_type, "");
647 sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
648 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
649 if (ret / 100 == 1) {
651 strcpy(mret[0]->mime_chosen, "1"); /* Default chosen-part is "1" */
652 while (strlen(bbb) > 4 && bbb[4] == '=') {
653 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
654 remove_token(bbb, 0, '\n');
656 if (!strncasecmp(aaa, "nhdr=yes", 8))
658 else if (!strncasecmp(aaa, "from=", 5))
659 safestrncpy(mret[0]->author, &aaa[5], SIZ);
660 else if (!strncasecmp(aaa, "type=", 5))
661 mret[0]->type = atoi(&aaa[5]);
662 else if (!strncasecmp(aaa, "msgn=", 5))
663 safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
664 else if (!strncasecmp(aaa, "subj=", 5))
665 safestrncpy(mret[0]->subject, &aaa[5], SIZ);
666 else if (!strncasecmp(aaa, "rfca=", 5))
667 safestrncpy(mret[0]->email, &aaa[5], SIZ);
668 else if (!strncasecmp(aaa, "hnod=", 5))
669 safestrncpy(mret[0]->hnod, &aaa[5], SIZ);
670 else if (!strncasecmp(aaa, "room=", 5))
671 safestrncpy(mret[0]->room, &aaa[5], SIZ);
672 else if (!strncasecmp(aaa, "node=", 5))
673 safestrncpy(mret[0]->node, &aaa[5], SIZ);
674 else if (!strncasecmp(aaa, "rcpt=", 5))
675 safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
676 else if (!strncasecmp(aaa, "wefw=", 5))
677 safestrncpy(mret[0]->references, &aaa[5], SIZ);
678 else if (!strncasecmp(aaa, "time=", 5))
679 mret[0]->time = atol(&aaa[5]);
681 /* Multipart/alternative prefix & suffix strings help
682 * us to determine which part we want to download.
684 else if (!strncasecmp(aaa, "pref=", 5)) {
685 extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
686 if (!strcasecmp(multipart_prefix,
687 "multipart/alternative")) {
691 else if (!strncasecmp(aaa, "suff=", 5)) {
692 extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
693 if (!strcasecmp(multipart_prefix,
694 "multipart/alternative")) {
699 else if (!strncasecmp(aaa, "part=", 5)) {
700 struct parts *ptr, *chain;
702 ptr = (struct parts *)calloc(1, sizeof (struct parts));
705 /* Fill the buffers for the caller */
706 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
707 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
708 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
709 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
710 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
711 ptr->length = extract_long(&aaa[5], 5);
712 if (!mret[0]->attachments)
713 mret[0]->attachments = ptr;
715 chain = mret[0]->attachments;
721 /* Now handle multipart/alternative */
722 if (multipart_hunting > 0) {
723 if ( (!strcasecmp(ptr->mimetype,
725 || (!strcasecmp(ptr->mimetype,
727 strcpy(mret[0]->mime_chosen,
735 /* Eliminate "text\n" */
736 remove_token(bbb, 0, '\n');
738 /* If doing a MIME thing, pull out the extra headers */
741 if (!strncasecmp(bbb, "Content-type:", 13)) {
742 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
743 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
744 striplt(mret[0]->content_type);
746 /* strip out ";charset=" portion. FIXME do something with
747 * the charset (like... convert it) instead of just throwing
750 if (strstr(mret[0]->content_type, ";") != NULL) {
751 strcpy(strstr(mret[0]->content_type, ";"), "");
755 if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
756 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
757 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
758 striplt(mret[0]->mime_chosen);
760 if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
761 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
762 strcpy(encoding, &encoding[26]);
765 remove_token(bbb, 0, '\n');
766 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
767 remove_token(bbb, 0, '\n');
774 if ( (!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable")) ) {
776 int bytes_decoded = 0;
777 ccc = malloc(strlen(bbb) + 32768);
778 if (!strcasecmp(encoding, "base64")) {
779 bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
781 else if (!strcasecmp(encoding, "quoted-printable")) {
782 bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
784 ccc[bytes_decoded] = 0;
789 /* FIXME: Strip trailing whitespace */
790 bbb = (char *)realloc(bbb, (size_t)(strlen(bbb) + 1));
793 bbb = (char *)realloc(bbb, 1);
803 int CtdlIPCWhoKnowsRoom(CtdlIPC *ipc, char **listing, char *cret)
808 if (!cret) return -2;
809 if (!listing) return -2;
810 if (*listing) return -2;
812 ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
818 int CtdlIPCServerInfo(CtdlIPC *ipc, char *cret)
822 char *listing = NULL;
825 if (!cret) return -2;
827 ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
828 if (ret / 100 == 1) {
831 while (*listing && strlen(listing)) {
832 extract_token(buf, listing, 0, '\n', sizeof buf);
833 remove_token(listing, 0, '\n');
835 case 0: ipc->ServInfo.pid = atoi(buf);
837 case 1: strcpy(ipc->ServInfo.nodename,buf);
839 case 2: strcpy(ipc->ServInfo.humannode,buf);
841 case 3: strcpy(ipc->ServInfo.fqdn,buf);
843 case 4: strcpy(ipc->ServInfo.software,buf);
845 case 5: ipc->ServInfo.rev_level = atoi(buf);
847 case 6: strcpy(ipc->ServInfo.site_location,buf);
849 case 7: strcpy(ipc->ServInfo.sysadm,buf);
851 case 9: strcpy(ipc->ServInfo.moreprompt,buf);
853 case 10: ipc->ServInfo.ok_floors = atoi(buf);
855 case 11: ipc->ServInfo.paging_level = atoi(buf);
857 case 13: ipc->ServInfo.supports_qnop = atoi(buf);
859 case 14: ipc->ServInfo.supports_ldap = atoi(buf);
861 case 15: ipc->ServInfo.newuser_disabled = atoi(buf);
863 case 16: strcpy(ipc->ServInfo.default_cal_zone, buf);
865 case 17: ipc->ServInfo.load_avg = atof(buf);
867 case 18: ipc->ServInfo.worker_avg = atof(buf);
869 case 19: ipc->ServInfo.thread_count = atoi(buf);
871 case 20: ipc->ServInfo.has_sieve = atoi(buf);
873 case 21: ipc->ServInfo.fulltext_enabled = atoi(buf);
875 case 22: strcpy(ipc->ServInfo.svn_revision, buf);
877 case 24: ipc->ServInfo.guest_logins = atoi(buf);
883 if (listing) free(listing);
889 int CtdlIPCReadDirectory(CtdlIPC *ipc, char **listing, char *cret)
894 if (!cret) return -2;
895 if (!listing) return -2;
896 if (*listing) return -2;
898 ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
904 * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
906 int CtdlIPCSetLastRead(CtdlIPC *ipc, long msgnum, char *cret)
911 if (!cret) return -2;
914 sprintf(aaa, "SLRP %ld", msgnum);
917 sprintf(aaa, "SLRP HIGHEST");
919 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
925 int CtdlIPCInviteUserToRoom(CtdlIPC *ipc, const char *username, char *cret)
930 if (!cret) return -2;
931 if (!username) return -2;
933 aaa = (char *)malloc(strlen(username) + 6);
936 sprintf(aaa, "INVT %s", username);
937 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
944 int CtdlIPCKickoutUserFromRoom(CtdlIPC *ipc, const char *username, char *cret)
949 if (!cret) return -1;
950 if (!username) return -1;
952 aaa = (char *)malloc(strlen(username) + 6);
954 sprintf(aaa, "KICK %s", username);
955 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
962 int CtdlIPCGetRoomAttributes(CtdlIPC *ipc, struct ctdlroom **qret, char *cret)
966 if (!cret) return -2;
967 if (!qret) return -2;
968 if (!*qret) *qret = (struct ctdlroom *)calloc(1, sizeof (struct ctdlroom));
969 if (!*qret) return -1;
971 ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
972 if (ret / 100 == 2) {
973 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
974 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
975 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
976 qret[0]->QRflags = extract_int(cret, 3);
977 qret[0]->QRfloor = extract_int(cret, 4);
978 qret[0]->QRorder = extract_int(cret, 5);
979 qret[0]->QRdefaultview = extract_int(cret, 6);
980 qret[0]->QRflags2 = extract_int(cret, 7);
987 /* set forget to kick all users out of room */
988 int CtdlIPCSetRoomAttributes(CtdlIPC *ipc, int forget, struct ctdlroom *qret, char *cret)
993 if (!cret) return -2;
994 if (!qret) return -2;
996 aaa = (char *)malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) +
997 strlen(qret->QRdirname) + 64);
1000 sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
1001 qret->QRname, qret->QRpasswd, qret->QRdirname,
1002 qret->QRflags, forget, qret->QRfloor, qret->QRorder,
1003 qret->QRdefaultview, qret->QRflags2);
1004 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1011 int CtdlIPCGetRoomAide(CtdlIPC *ipc, char *cret)
1013 if (!cret) return -1;
1015 return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1020 int CtdlIPCSetRoomAide(CtdlIPC *ipc, const char *username, char *cret)
1025 if (!cret) return -2;
1026 if (!username) return -2;
1028 aaa = (char *)malloc(strlen(username) + 6);
1029 if (!aaa) return -1;
1031 sprintf(aaa, "SETA %s", username);
1032 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1039 int CtdlIPCPostMessage(CtdlIPC *ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1045 if (!cret) return -2;
1048 if (mr->references) {
1049 for (ptr=mr->references; *ptr != 0; ++ptr) {
1050 if (*ptr == '|') *ptr = '!';
1054 snprintf(cmd, sizeof cmd,
1055 "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1056 mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1057 ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL,
1059 if ((flag == 0) && (subject_required != NULL)) {
1060 /* Is the server strongly recommending that the user enter a message subject? */
1061 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1062 *subject_required = extract_int(&cret[4], 1);
1072 int CtdlIPCRoomInfo(CtdlIPC *ipc, char **iret, char *cret)
1076 if (!cret) return -2;
1077 if (!iret) return -2;
1078 if (*iret) return -2;
1080 return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1085 int CtdlIPCDeleteMessage(CtdlIPC *ipc, long msgnum, char *cret)
1089 if (!cret) return -2;
1090 if (!msgnum) return -2;
1092 sprintf(aaa, "DELE %ld", msgnum);
1093 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1098 int CtdlIPCMoveMessage(CtdlIPC *ipc, int copy, long msgnum, const char *destroom, char *cret)
1103 if (!cret) return -2;
1104 if (!destroom) return -2;
1105 if (!msgnum) return -2;
1107 aaa = (char *)malloc(strlen(destroom) + 28);
1108 if (!aaa) return -1;
1110 sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1111 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1118 int CtdlIPCDeleteRoom(CtdlIPC *ipc, int for_real, char *cret)
1122 if (!cret) return -2;
1124 sprintf(aaa, "KILL %d", for_real);
1125 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1130 int CtdlIPCCreateRoom(CtdlIPC *ipc, int for_real, const char *roomname, int type,
1131 const char *password, int floor, char *cret)
1136 if (!cret) return -2;
1137 if (!roomname) return -2;
1140 aaa = (char *)malloc(strlen(roomname) + strlen(password) + 40);
1141 if (!aaa) return -1;
1142 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type,
1145 aaa = (char *)malloc(strlen(roomname) + 40);
1146 if (!aaa) return -1;
1147 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type,
1150 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1157 int CtdlIPCForgetRoom(CtdlIPC *ipc, char *cret)
1159 if (!cret) return -2;
1161 return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1166 int CtdlIPCSystemMessage(CtdlIPC *ipc, const char *message, char **mret, char *cret)
1172 if (!cret) return -2;
1173 if (!mret) return -2;
1174 if (*mret) return -2;
1175 if (!message) return -2;
1177 aaa = (char *)malloc(strlen(message) + 6);
1178 if (!aaa) return -1;
1180 sprintf(aaa, "MESG %s", message);
1181 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1188 int CtdlIPCNextUnvalidatedUser(CtdlIPC *ipc, char *cret)
1190 if (!cret) return -2;
1192 return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1197 int CtdlIPCGetUserRegistration(CtdlIPC *ipc, const char *username, char **rret, char *cret)
1203 if (!cret) return -2;
1204 if (!rret) return -2;
1205 if (*rret) return -2;
1208 aaa = (char *)malloc(strlen(username) + 6);
1210 aaa = (char *)malloc(12);
1211 if (!aaa) return -1;
1214 sprintf(aaa, "GREG %s", username);
1216 sprintf(aaa, "GREG _SELF_");
1217 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1224 int CtdlIPCValidateUser(CtdlIPC *ipc, const char *username, int axlevel, char *cret)
1229 if (!cret) return -2;
1230 if (!username) return -2;
1231 if (axlevel < AxDeleted || axlevel > AxAideU) return -2;
1233 aaa = (char *)malloc(strlen(username) + 17);
1234 if (!aaa) return -1;
1236 sprintf(aaa, "VALI %s|%d", username, axlevel);
1237 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1244 int CtdlIPCSetRoomInfo(CtdlIPC *ipc, int for_real, const char *info, char *cret)
1248 if (!cret) return -1;
1249 if (!info) return -1;
1251 sprintf(aaa, "EINF %d", for_real);
1252 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1257 int CtdlIPCUserListing(CtdlIPC *ipc, char *searchstring, char **listing, char *cret)
1263 if (!cret) return -1;
1264 if (!listing) return -1;
1265 if (*listing) return -1;
1266 if (!searchstring) return -1;
1268 cmd = malloc(strlen(searchstring) + 10);
1269 sprintf(cmd, "LIST %s", searchstring);
1271 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1278 int CtdlIPCSetRegistration(CtdlIPC *ipc, const char *info, char *cret)
1280 if (!cret) return -1;
1281 if (!info) return -1;
1283 return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info),
1289 int CtdlIPCMiscCheck(CtdlIPC *ipc, struct ctdlipcmisc *chek, char *cret)
1293 if (!cret) return -1;
1294 if (!chek) return -1;
1296 ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1297 if (ret / 100 == 2) {
1298 chek->newmail = extract_long(cret, 0);
1299 chek->needregis = extract_int(cret, 1);
1300 chek->needvalid = extract_int(cret, 2);
1307 int CtdlIPCDeleteFile(CtdlIPC *ipc, const char *filename, char *cret)
1312 if (!cret) return -2;
1313 if (!filename) return -2;
1315 aaa = (char *)malloc(strlen(filename) + 6);
1316 if (!aaa) return -1;
1318 sprintf(aaa, "DELF %s", filename);
1319 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1326 int CtdlIPCMoveFile(CtdlIPC *ipc, const char *filename, const char *destroom, char *cret)
1331 if (!cret) return -2;
1332 if (!filename) return -2;
1333 if (!destroom) return -2;
1335 aaa = (char *)malloc(strlen(filename) + strlen(destroom) + 7);
1336 if (!aaa) return -1;
1338 sprintf(aaa, "MOVF %s|%s", filename, destroom);
1339 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1346 int CtdlIPCOnlineUsers(CtdlIPC *ipc, char **listing, time_t *stamp, char *cret)
1351 if (!cret) return -1;
1352 if (!listing) return -1;
1353 if (*listing) return -1;
1355 *stamp = CtdlIPCServerTime(ipc, cret);
1357 *stamp = time(NULL);
1358 ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1364 int CtdlIPCFileDownload(CtdlIPC *ipc, const char *filename, void **buf,
1366 void (*progress_gauge_callback)
1367 (CtdlIPC*, unsigned long, unsigned long),
1376 if (!cret) return -2;
1377 if (!filename) return -2;
1378 if (!buf) return -2;
1379 if (*buf) return -2;
1380 if (ipc->downloading) return -2;
1382 aaa = (char *)malloc(strlen(filename) + 6);
1383 if (!aaa) return -1;
1385 sprintf(aaa, "OPEN %s", filename);
1386 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1388 if (ret / 100 == 2) {
1389 ipc->downloading = 1;
1390 bytes = extract_long(cret, 0);
1391 last_mod = extract_int(cret, 1);
1392 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1394 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume,
1395 progress_gauge_callback, cret);
1397 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1398 progress_gauge_callback, cret);
1401 ret = CtdlIPCEndDownload(ipc, cret);
1403 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1404 filename, mimetype);
1411 int CtdlIPCAttachmentDownload(CtdlIPC *ipc, long msgnum, const char *part,
1413 void (*progress_gauge_callback)
1414 (CtdlIPC*, unsigned long, unsigned long),
1424 if (!cret) return -2;
1425 if (!buf) return -2;
1426 if (*buf) return -2;
1427 if (!part) return -2;
1428 if (!msgnum) return -2;
1429 if (ipc->downloading) return -2;
1431 sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1432 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1433 if (ret / 100 == 2) {
1434 ipc->downloading = 1;
1435 bytes = extract_long(cret, 0);
1436 last_mod = extract_int(cret, 1);
1437 extract_token(filename, cret, 2, '|', sizeof filename);
1438 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1439 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1440 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1441 ret = CtdlIPCEndDownload(ipc, cret);
1443 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1444 filename, mimetype);
1451 int CtdlIPCImageDownload(CtdlIPC *ipc, const char *filename, void **buf,
1452 void (*progress_gauge_callback)
1453 (CtdlIPC*, unsigned long, unsigned long),
1462 if (!cret) return -1;
1463 if (!buf) return -1;
1464 if (*buf) return -1;
1465 if (!filename) return -1;
1466 if (ipc->downloading) return -1;
1468 aaa = (char *)malloc(strlen(filename) + 6);
1469 if (!aaa) return -1;
1471 sprintf(aaa, "OIMG %s", filename);
1472 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1474 if (ret / 100 == 2) {
1475 ipc->downloading = 1;
1476 bytes = extract_long(cret, 0);
1477 last_mod = extract_int(cret, 1);
1478 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1479 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1480 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1481 ret = CtdlIPCEndDownload(ipc, cret);
1483 sprintf(cret, "%d|%ld|%s|%s", (int)bytes, last_mod,
1484 filename, mimetype);
1491 int CtdlIPCFileUpload(CtdlIPC *ipc, const char *save_as, const char *comment,
1493 void (*progress_gauge_callback)
1494 (CtdlIPC*, unsigned long, unsigned long),
1500 char MimeTestBuf[64];
1501 const char *MimeType;
1504 if (!cret) return -1;
1505 if (!save_as) return -1;
1506 if (!comment) return -1;
1507 if (!path) return -1;
1508 if (!*path) return -1;
1509 if (ipc->uploading) return -1;
1511 uploadFP = fopen(path, "r");
1512 if (!uploadFP) return -2;
1514 len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1519 MimeType = GuessMimeType(&MimeTestBuf[0], len);
1520 aaa = (char *)malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1521 if (!aaa) return -1;
1523 sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1524 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1526 if (ret / 100 == 2) {
1528 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1529 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1537 int CtdlIPCImageUpload(CtdlIPC *ipc, int for_real, const char *path,
1538 const char *save_as,
1539 void (*progress_gauge_callback)
1540 (CtdlIPC*, unsigned long, unsigned long),
1546 char MimeTestBuf[64];
1547 const char *MimeType;
1550 if (!cret) return -1;
1551 if (!save_as) return -1;
1552 if (!path && for_real) return -1;
1553 if (!*path && for_real) return -1;
1554 if (ipc->uploading) return -1;
1556 aaa = (char *)malloc(strlen(save_as) + 17);
1557 if (!aaa) return -1;
1559 uploadFP = fopen(path, "r");
1560 if (!uploadFP) return -2;
1562 len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1566 MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1568 sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1569 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1571 if (ret / 100 == 2 && for_real) {
1573 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1574 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1582 int CtdlIPCQueryUsername(CtdlIPC *ipc, const char *username, char *cret)
1587 if (!cret) return -2;
1588 if (!username) return -2;
1590 aaa = (char *)malloc(strlen(username) + 6);
1591 if (!aaa) return -1;
1593 sprintf(aaa, "QUSR %s", username);
1594 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1601 int CtdlIPCFloorListing(CtdlIPC *ipc, char **listing, char *cret)
1605 if (!cret) return -2;
1606 if (!listing) return -2;
1607 if (*listing) return -2;
1609 return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1614 int CtdlIPCCreateFloor(CtdlIPC *ipc, int for_real, const char *name, char *cret)
1619 if (!cret) return -2;
1620 if (!name) return -2;
1622 sprintf(aaa, "CFLR %s|%d", name, for_real);
1623 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1629 int CtdlIPCDeleteFloor(CtdlIPC *ipc, int for_real, int floornum, char *cret)
1633 if (!cret) return -1;
1634 if (floornum < 0) return -1;
1636 sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1637 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1642 int CtdlIPCEditFloor(CtdlIPC *ipc, int floornum, const char *floorname, char *cret)
1647 if (!cret) return -2;
1648 if (!floorname) return -2;
1649 if (floornum < 0) return -2;
1651 sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1652 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1660 * You only need to fill out hostname, the defaults will be used if any of the
1661 * other fields are not set properly.
1663 int CtdlIPCIdentifySoftware(CtdlIPC *ipc, int developerid, int clientid,
1664 int revision, const char *software_name, const char *hostname,
1670 if (developerid < 0 || clientid < 0 || revision < 0 ||
1674 revision = CLIENT_VERSION - 600;
1675 software_name = "Citadel (libcitadel)";
1677 if (!hostname) return -2;
1679 aaa = (char *)malloc(strlen(software_name) + strlen(hostname) + 29);
1680 if (!aaa) return -1;
1682 sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid,
1683 revision, software_name, hostname);
1684 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1691 int CtdlIPCSendInstantMessage(CtdlIPC *ipc, const char *username, const char *text,
1697 if (!cret) return -2;
1698 if (!username) return -2;
1700 aaa = (char *)malloc(strlen(username) + 8);
1701 if (!aaa) return -1;
1704 sprintf(aaa, "SEXP %s|-", username);
1705 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text),
1708 sprintf(aaa, "SEXP %s||", username);
1709 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1717 int CtdlIPCGetInstantMessage(CtdlIPC *ipc, char **listing, char *cret)
1721 if (!cret) return -2;
1722 if (!listing) return -2;
1723 if (*listing) return -2;
1725 return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1730 /* mode is 0 = enable, 1 = disable, 2 = status */
1731 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC *ipc, int mode, char *cret)
1735 if (!cret) return -2;
1737 sprintf(aaa, "DEXP %d", mode);
1738 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1743 int CtdlIPCSetBio(CtdlIPC *ipc, char *bio, char *cret)
1745 if (!cret) return -2;
1746 if (!bio) return -2;
1748 return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio),
1754 int CtdlIPCGetBio(CtdlIPC *ipc, const char *username, char **listing, char *cret)
1760 if (!cret) return -2;
1761 if (!username) return -2;
1762 if (!listing) return -2;
1763 if (*listing) return -2;
1765 aaa = (char *)malloc(strlen(username) + 6);
1766 if (!aaa) return -1;
1768 sprintf(aaa, "RBIO %s", username);
1769 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1776 int CtdlIPCListUsersWithBios(CtdlIPC *ipc, char **listing, char *cret)
1780 if (!cret) return -2;
1781 if (!listing) return -2;
1782 if (*listing) return -2;
1784 return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1789 int CtdlIPCStealthMode(CtdlIPC *ipc, int mode, char *cret)
1793 if (!cret) return -1;
1795 sprintf(aaa, "STEL %d", mode ? 1 : 0);
1796 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1801 int CtdlIPCTerminateSession(CtdlIPC *ipc, int sid, char *cret)
1805 if (!cret) return -1;
1807 sprintf(aaa, "TERM %d", sid);
1808 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1813 int CtdlIPCTerminateServerNow(CtdlIPC *ipc, char *cret)
1815 if (!cret) return -1;
1817 return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1822 int CtdlIPCTerminateServerScheduled(CtdlIPC *ipc, int mode, char *cret)
1826 if (!cret) return -1;
1828 sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1829 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1834 int CtdlIPCEnterSystemMessage(CtdlIPC *ipc, const char *filename, const char *text,
1840 if (!cret) return -2;
1841 if (!text) return -2;
1842 if (!filename) return -2;
1844 aaa = (char *)malloc(strlen(filename) + 6);
1845 if (!aaa) return -1;
1847 sprintf(aaa, "EMSG %s", filename);
1848 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1855 int CtdlIPCChangeHostname(CtdlIPC *ipc, const char *hostname, char *cret)
1860 if (!cret) return -2;
1861 if (!hostname) return -2;
1863 aaa = (char *)malloc(strlen(hostname) + 6);
1864 if (!aaa) return -1;
1866 sprintf(aaa, "HCHG %s", hostname);
1867 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1874 int CtdlIPCChangeRoomname(CtdlIPC *ipc, const char *roomname, char *cret)
1879 if (!cret) return -2;
1880 if (!roomname) return -2;
1882 aaa = (char *)malloc(strlen(roomname) + 6);
1883 if (!aaa) return -1;
1885 sprintf(aaa, "RCHG %s", roomname);
1886 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1893 int CtdlIPCChangeUsername(CtdlIPC *ipc, const char *username, char *cret)
1898 if (!cret) return -2;
1899 if (!username) return -2;
1901 aaa = (char *)malloc(strlen(username) + 6);
1902 if (!aaa) return -1;
1904 sprintf(aaa, "UCHG %s", username);
1905 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1912 /* This function returns the actual server time reported, or 0 if error */
1913 time_t CtdlIPCServerTime(CtdlIPC *ipc, char *cret)
1915 register time_t tret;
1918 ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1919 if (ret / 100 == 2) {
1920 tret = extract_long(cret, 0);
1929 int CtdlIPCAideGetUserParameters(CtdlIPC *ipc, const char *who,
1930 struct ctdluser **uret, char *cret)
1935 if (!cret) return -2;
1936 if (!uret) return -2;
1937 if (!*uret) *uret = (struct ctdluser *)calloc(1, sizeof(struct ctdluser));
1938 if (!*uret) return -1;
1940 sprintf(aaa, "AGUP %s", who);
1941 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1943 if (ret / 100 == 2) {
1944 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1945 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1946 uret[0]->flags = extract_int(cret, 2);
1947 uret[0]->timescalled = extract_long(cret, 3);
1948 uret[0]->posted = extract_long(cret, 4);
1949 uret[0]->axlevel = extract_int(cret, 5);
1950 uret[0]->usernum = extract_long(cret, 6);
1951 uret[0]->lastcall = extract_long(cret, 7);
1952 uret[0]->USuserpurge = extract_int(cret, 8);
1959 int CtdlIPCAideSetUserParameters(CtdlIPC *ipc, const struct ctdluser *uret, char *cret)
1964 if (!cret) return -2;
1965 if (!uret) return -2;
1967 aaa = (char *)malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1968 if (!aaa) return -1;
1970 sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
1971 uret->fullname, uret->password, uret->flags,
1972 uret->timescalled, uret->posted, uret->axlevel,
1973 uret->usernum, uret->lastcall, uret->USuserpurge);
1974 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1981 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
1982 /* caller must free the struct ExpirePolicy */
1983 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC *ipc, GPEXWhichPolicy which,
1984 struct ExpirePolicy **policy, char *cret)
1986 static char *proto[] = {
1990 strof(mailboxespolicy)
1995 if (!cret) return -2;
1996 if (!policy) return -2;
1997 if (!*policy) *policy = (struct ExpirePolicy *)calloc(1, sizeof(struct ExpirePolicy));
1998 if (!*policy) return -1;
1999 if (which < 0 || which > 3) return -2;
2001 sprintf(cmd, "GPEX %s", proto[which]);
2002 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2003 if (ret / 100 == 2) {
2004 policy[0]->expire_mode = extract_int(cret, 0);
2005 policy[0]->expire_value = extract_int(cret, 1);
2012 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2013 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2014 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC *ipc, int which,
2015 struct ExpirePolicy *policy, char *cret)
2018 char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2020 if (!cret) return -2;
2021 if (which < 0 || which > 3) return -2;
2022 if (!policy) return -2;
2023 if (policy->expire_mode < 0 || policy->expire_mode > 3) return -2;
2024 if (policy->expire_mode >= 2 && policy->expire_value < 1) return -2;
2026 sprintf(aaa, "SPEX %s|%d|%d", whichvals[which],
2027 policy->expire_mode, policy->expire_value);
2028 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2033 int CtdlIPCGetSystemConfig(CtdlIPC *ipc, char **listing, char *cret)
2037 if (!cret) return -2;
2038 if (!listing) return -2;
2039 if (*listing) return -2;
2041 return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0,
2042 listing, &bytes, cret);
2047 int CtdlIPCSetSystemConfig(CtdlIPC *ipc, const char *listing, char *cret)
2049 if (!cret) return -2;
2050 if (!listing) return -2;
2052 return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing),
2058 int CtdlIPCGetSystemConfigByType(CtdlIPC *ipc, const char *mimetype,
2059 char **listing, char *cret)
2065 if (!cret) return -2;
2066 if (!mimetype) return -2;
2067 if (!listing) return -2;
2068 if (*listing) return -2;
2070 aaa = malloc(strlen(mimetype) + 13);
2071 if (!aaa) return -1;
2072 sprintf(aaa, "CONF GETSYS|%s", mimetype);
2073 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0,
2074 listing, &bytes, cret);
2081 int CtdlIPCSetSystemConfigByType(CtdlIPC *ipc, const char *mimetype,
2082 const char *listing, char *cret)
2087 if (!cret) return -2;
2088 if (!mimetype) return -2;
2089 if (!listing) return -2;
2091 aaa = malloc(strlen(mimetype) + 13);
2092 if (!aaa) return -1;
2093 sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2094 ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing),
2102 int CtdlIPCGetRoomNetworkConfig(CtdlIPC *ipc, char **listing, char *cret)
2106 if (!cret) return -2;
2107 if (!listing) return -2;
2108 if (*listing) return -2;
2110 return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0,
2111 listing, &bytes, cret);
2116 int CtdlIPCSetRoomNetworkConfig(CtdlIPC *ipc, const char *listing, char *cret)
2118 if (!cret) return -2;
2119 if (!listing) return -2;
2121 return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing),
2127 int CtdlIPCRequestClientLogout(CtdlIPC *ipc, int session, char *cret)
2131 if (!cret) return -2;
2132 if (session < 0) return -2;
2134 sprintf(aaa, "REQT %d", session);
2135 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2140 int CtdlIPCSetMessageSeen(CtdlIPC *ipc, long msgnum, int seen, char *cret)
2144 if (!cret) return -2;
2145 if (msgnum < 0) return -2;
2147 sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2148 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2153 int CtdlIPCStartEncryption(CtdlIPC *ipc, char *cret)
2162 /* New SSL object */
2163 temp_ssl = SSL_new(ssl_ctx);
2165 error_printf("SSL_new failed: %s\n",
2166 ERR_reason_error_string(ERR_get_error()));
2169 /* Pointless flag waving */
2170 #if SSLEAY_VERSION_NUMBER >= 0x0922
2171 SSL_set_session_id_context(temp_ssl, (const unsigned char*) "Citadel SID", 14);
2174 if (!access(EGD_POOL, F_OK))
2177 if (!RAND_status()) {
2178 error_printf("PRNG not properly seeded\n");
2182 /* Associate network connection with SSL object */
2183 if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2184 error_printf("SSL_set_fd failed: %s\n",
2185 ERR_reason_error_string(ERR_get_error()));
2189 if (status_hook != NULL)
2190 status_hook("Requesting encryption...\r");
2192 /* Ready to start SSL/TLS */
2194 CtdlIPC_putline(ipc, "STLS");
2195 CtdlIPC_getline(ipc, buf);
2196 if (buf[0] != '2') {
2197 error_printf("Server can't start TLS: %s\n", buf);
2201 r = CtdlIPCGenericCommand(ipc,
2202 "STLS", NULL, 0, NULL, NULL, cret);
2204 error_printf("Server can't start TLS: %s\n", buf);
2209 /* Do SSL/TLS handshake */
2210 if ((a = SSL_connect(temp_ssl)) < 1) {
2211 error_printf("SSL_connect failed: %s\n",
2212 ERR_reason_error_string(ERR_get_error()));
2216 ipc->ssl = temp_ssl;
2218 if (BIO_set_close(ipc->ssl->rbio, BIO_NOCLOSE))
2222 bits = SSL_CIPHER_get_bits(SSL_get_current_cipher(ipc->ssl), &alg_bits);
2223 error_printf("Encrypting with %s cipher %s (%d of %d bits)\n",
2224 SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)),
2225 SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl)),
2231 #endif /* HAVE_OPENSSL */
2236 static void endtls(SSL *ssl)
2247 int CtdlIPCDirectoryLookup(CtdlIPC *ipc, const char *address, char *cret)
2252 if (!address) return -2;
2253 if (!cret) return -2;
2255 aaa = (char *)malloc(strlen(address) + 6);
2256 if (!aaa) return -1;
2258 sprintf(aaa, "QDIR %s", address);
2259 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2266 int CtdlIPCInternalProgram(CtdlIPC *ipc, int secret, char *cret)
2270 if (!cret) return -2;
2271 sprintf(aaa, "IPGM %d", secret);
2272 return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2278 /* ************************************************************************** */
2279 /* Stuff below this line is not for public consumption */
2280 /* ************************************************************************** */
2283 /* Read a listing from the server up to 000. Append to dest if it exists */
2284 char *CtdlIPCReadListing(CtdlIPC *ipc, char *dest)
2293 length = strlen(ret);
2298 while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2299 linelength = strlen(aaa);
2300 ret = (char *)realloc(ret, (size_t)(length + linelength + 2));
2302 strcpy(&ret[length], aaa);
2303 length += linelength;
2304 strcpy(&ret[length++], "\n");
2312 /* Send a listing to the server; generate the ending 000. */
2313 int CtdlIPCSendListing(CtdlIPC *ipc, const char *listing)
2317 text = (char *)malloc(strlen(listing) + 6);
2319 strcpy(text, listing);
2320 while (text[strlen(text) - 1] == '\n')
2321 text[strlen(text) - 1] = '\0';
2322 strcat(text, "\n000");
2323 CtdlIPC_putline(ipc, text);
2327 /* Malloc failed but we are committed to send */
2328 /* This may result in extra blanks at the bottom */
2329 CtdlIPC_putline(ipc, text);
2330 CtdlIPC_putline(ipc, "000");
2336 /* Partial read of file from server */
2337 size_t CtdlIPCPartialRead(CtdlIPC *ipc, void **buf, size_t offset, size_t bytes, char *cret)
2339 register size_t len = 0;
2343 if (!cret) return 0;
2344 if (bytes < 1) return 0;
2347 sprintf(aaa, "READ %d|%d", (int)offset, (int)bytes);
2348 CtdlIPC_putline(ipc, aaa);
2349 CtdlIPC_getline(ipc, aaa);
2351 strcpy(cret, &aaa[4]);
2353 len = extract_long(&aaa[4], 0);
2354 *buf = (void *)realloc(*buf, (size_t)(offset + len));
2356 /* I know what I'm doing */
2357 serv_read(ipc, ((char *)(*buf) + offset), len);
2359 /* We have to read regardless */
2360 serv_read(ipc, aaa, len);
2364 CtdlIPC_unlock(ipc);
2370 int CtdlIPCEndDownload(CtdlIPC *ipc, char *cret)
2374 if (!cret) return -2;
2375 if (!ipc->downloading) return -2;
2377 ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2379 ipc->downloading = 0;
2385 int CtdlIPCSpecifyPreferredFormats(CtdlIPC *ipc, char *cret, char *formats) {
2389 snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2390 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2397 int CtdlIPCReadDownload(CtdlIPC *ipc, void **buf, size_t bytes, size_t resume,
2398 void (*progress_gauge_callback)
2399 (CtdlIPC*, unsigned long, unsigned long),
2402 register size_t len;
2404 if (!cret) return -1;
2405 if (!buf) return -1;
2406 if (*buf) return -1;
2407 if (!ipc->downloading) return -1;
2410 if (progress_gauge_callback)
2411 progress_gauge_callback(ipc, len, bytes);
2412 while (len < bytes) {
2413 register size_t block;
2415 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2421 if (progress_gauge_callback)
2422 progress_gauge_callback(ipc, len, bytes);
2427 /* READ - pipelined */
2428 int CtdlIPCHighSpeedReadDownload(CtdlIPC *ipc, void **buf, size_t bytes,
2430 void (*progress_gauge_callback)
2431 (CtdlIPC*, unsigned long, unsigned long),
2434 register size_t len;
2435 register int calls; /* How many calls in the pipeline */
2436 register int i; /* iterator */
2439 if (!cret) return -1;
2440 if (!buf) return -1;
2441 if (*buf) return -1;
2442 if (!ipc->downloading) return -1;
2444 *buf = (void *)realloc(*buf, bytes - resume);
2445 if (!*buf) return -1;
2449 if (progress_gauge_callback)
2450 progress_gauge_callback(ipc, len, bytes);
2452 /* How many calls will be in the pipeline? */
2453 calls = (bytes - resume) / 4096;
2454 if ((bytes - resume) % 4096) calls++;
2456 /* Send all requests at once */
2457 for (i = 0; i < calls; i++) {
2458 sprintf(aaa, "READ %d|4096", (int)(i * 4096 + resume) );
2459 CtdlIPC_putline(ipc, aaa);
2462 /* Receive all responses at once */
2463 for (i = 0; i < calls; i++) {
2464 CtdlIPC_getline(ipc, aaa);
2466 strcpy(cret, &aaa[4]);
2468 len = extract_long(&aaa[4], 0);
2469 /* I know what I'm doing */
2470 serv_read(ipc, ((char *)(*buf) + (i * 4096)), len);
2472 if (progress_gauge_callback)
2473 progress_gauge_callback(ipc, i * 4096 + len, bytes);
2475 CtdlIPC_unlock(ipc);
2481 int CtdlIPCEndUpload(CtdlIPC *ipc, int discard, char *cret)
2486 if (!cret) return -1;
2487 if (!ipc->uploading) return -1;
2489 sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2490 ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2497 int CtdlIPCWriteUpload(CtdlIPC *ipc, FILE *uploadFP,
2498 void (*progress_gauge_callback)
2499 (CtdlIPC*, unsigned long, unsigned long),
2502 register int ret = -1;
2503 register size_t offset = 0;
2507 FILE *fd = uploadFP;
2510 if (!cret) return -1;
2512 fseek(fd, 0L, SEEK_END);
2516 if (progress_gauge_callback)
2517 progress_gauge_callback(ipc, 0, bytes);
2519 while (offset < bytes) {
2520 register size_t to_write;
2522 /* Read some data in */
2523 to_write = fread(buf, 1, 4096, fd);
2525 if (feof(fd) || ferror(fd)) break;
2527 sprintf(aaa, "WRIT %d", (int)to_write);
2528 CtdlIPC_putline(ipc, aaa);
2529 CtdlIPC_getline(ipc, aaa);
2530 strcpy(cret, &aaa[4]);
2532 if (aaa[0] == '7') {
2533 to_write = extract_long(&aaa[4], 0);
2535 serv_write(ipc, buf, to_write);
2537 if (progress_gauge_callback)
2538 progress_gauge_callback(ipc, offset, bytes);
2539 /* Detect short reads and back up if needed */
2540 /* offset will never be negative anyway */
2541 fseek(fd, (signed)offset, SEEK_SET);
2546 if (progress_gauge_callback)
2547 progress_gauge_callback(ipc, 1, 1);
2550 return (!ferr ? ret : -2);
2555 * Generic command method. This method should handle any server command
2556 * except for CHAT. It takes the following arguments:
2558 * ipc The server to speak with
2559 * command Preformatted command to send to server
2560 * to_send A text or binary file to send to server
2561 * (only sent if server requests it)
2562 * bytes_to_send The number of bytes in to_send (required if
2563 * sending binary, optional if sending listing)
2564 * to_receive Pointer to a NULL pointer, if the server
2565 * sends text or binary we will allocate memory
2566 * for the file and stuff it here
2567 * bytes_to_receive If a file is received, we will store its
2569 * proto_response The protocol response. Caller must provide
2570 * this buffer and ensure that it is at least
2571 * 128 bytes in length.
2573 * This function returns a number equal to the protocol response number,
2574 * -1 if an internal error occurred, -2 if caller provided bad values,
2575 * or 0 - the protocol response number if bad values were found during
2576 * the protocol exchange.
2577 * It stores the protocol response string (minus the number) in
2578 * protocol_response as described above. Some commands send additional
2579 * data in this string.
2581 int CtdlIPCGenericCommand(CtdlIPC *ipc,
2582 const char *command, const char *to_send,
2583 size_t bytes_to_send, char **to_receive,
2584 size_t *bytes_to_receive, char *proto_response)
2589 if (!command) return -2;
2590 if (!proto_response) return -2;
2593 CtdlIPC_putline(ipc, command);
2595 CtdlIPC_getline(ipc, proto_response);
2596 if (proto_response[3] == '*')
2598 ret = atoi(proto_response);
2599 strcpy(proto_response, &proto_response[4]);
2600 switch (ret / 100) {
2601 default: /* Unknown, punt */
2603 case 3: /* MORE_DATA */
2605 /* Don't need to do anything */
2607 case 1: /* LISTING_FOLLOWS */
2608 if (to_receive && !*to_receive && bytes_to_receive) {
2609 *to_receive = CtdlIPCReadListing(ipc, NULL);
2610 } else { /* Drain */
2611 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000")) ;
2615 case 4: /* SEND_LISTING */
2617 CtdlIPCSendListing(ipc, to_send);
2619 /* No listing given, fake it */
2620 CtdlIPC_putline(ipc, "000");
2624 case 6: /* BINARY_FOLLOWS */
2625 if (to_receive && !*to_receive && bytes_to_receive) {
2627 extract_long(proto_response, 0);
2628 *to_receive = (char *)
2629 malloc((size_t)*bytes_to_receive);
2633 serv_read(ipc, *to_receive,
2640 drain = extract_long(proto_response, 0);
2641 while (drain > SIZ) {
2642 serv_read(ipc, buf, SIZ);
2645 serv_read(ipc, buf, drain);
2649 case 7: /* SEND_BINARY */
2650 if (to_send && bytes_to_send) {
2651 serv_write(ipc, to_send, bytes_to_send);
2652 } else if (bytes_to_send) {
2653 /* Fake it, send nulls */
2656 fake = bytes_to_send;
2657 memset(buf, '\0', SIZ);
2658 while (fake > SIZ) {
2659 serv_write(ipc, buf, SIZ);
2662 serv_write(ipc, buf, fake);
2664 } /* else who knows? DANGER WILL ROBINSON */
2666 case 8: /* START_CHAT_MODE */
2667 if (!strncasecmp(command, "CHAT", 4)) {
2668 /* Don't call chatmode with generic! */
2669 CtdlIPC_putline(ipc, "/quit");
2672 /* In this mode we send then receive listing */
2674 CtdlIPCSendListing(ipc, to_send);
2676 /* No listing given, fake it */
2677 CtdlIPC_putline(ipc, "000");
2680 if (to_receive && !*to_receive
2681 && bytes_to_receive) {
2682 *to_receive = CtdlIPCReadListing(ipc, NULL);
2683 } else { /* Drain */
2684 while (CtdlIPC_getline(ipc, buf),
2685 strcmp(buf, "000")) ;
2690 case 9: /* ASYNC_MSG */
2691 /* CtdlIPCDoAsync(ret, proto_response); */
2692 free(CtdlIPCReadListing(ipc, NULL)); /* STUB FIXME */
2698 CtdlIPC_unlock(ipc);
2704 * Connect to a Citadel on a remote host using a TCP/IP socket
2706 static int tcp_connectsock(char *host, char *service)
2708 struct in6_addr serveraddr;
2709 struct addrinfo hints;
2710 struct addrinfo *res = NULL;
2711 struct addrinfo *ai = NULL;
2715 if ((host == NULL) || IsEmptyStr(host)) {
2716 service = DEFAULT_HOST ;
2718 if ((service == NULL) || IsEmptyStr(service)) {
2719 service = DEFAULT_PORT ;
2722 memset(&hints, 0x00, sizeof(hints));
2723 hints.ai_flags = AI_NUMERICSERV;
2724 hints.ai_family = AF_UNSPEC;
2725 hints.ai_socktype = SOCK_STREAM;
2728 * Handle numeric IPv4 and IPv6 addresses
2730 rc = inet_pton(AF_INET, host, &serveraddr);
2731 if (rc == 1) { /* dotted quad */
2732 hints.ai_family = AF_INET;
2733 hints.ai_flags |= AI_NUMERICHOST;
2736 rc = inet_pton(AF_INET6, host, &serveraddr);
2737 if (rc == 1) { /* IPv6 address */
2738 hints.ai_family = AF_INET6;
2739 hints.ai_flags |= AI_NUMERICHOST;
2743 /* Begin the connection process */
2745 rc = getaddrinfo(host, service, &hints, &res);
2751 * Try all available addresses until we connect to one or until we run out.
2753 for (ai = res; ai != NULL; ai = ai->ai_next) {
2754 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2755 if (sock < 0) return(-1);
2757 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2759 return(sock); /* Connected! */
2762 close(sock); /* Failed. Close the socket to avoid fd leak! */
2774 * Connect to a Citadel on the local host using a unix domain socket
2776 static int uds_connectsock(int *isLocal, char *sockpath)
2778 struct sockaddr_un addr;
2781 memset(&addr, 0, sizeof(addr));
2782 addr.sun_family = AF_UNIX;
2783 safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2785 s = socket(AF_UNIX, SOCK_STREAM, 0);
2790 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2801 * input binary data from socket
2803 static void serv_read(CtdlIPC *ipc, char *buf, unsigned int bytes)
2805 unsigned int len, rlen;
2807 #if defined(HAVE_OPENSSL)
2809 serv_read_ssl(ipc, buf, bytes);
2814 while (len < bytes) {
2815 rlen = read(ipc->sock, &buf[len], bytes - len);
2817 connection_died(ipc, 0);
2826 * send binary to server
2828 void serv_write(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2830 unsigned int bytes_written = 0;
2833 #if defined(HAVE_OPENSSL)
2835 serv_write_ssl(ipc, buf, nbytes);
2839 while (bytes_written < nbytes) {
2840 retval = write(ipc->sock, &buf[bytes_written],
2841 nbytes - bytes_written);
2843 connection_died(ipc, 0);
2846 bytes_written += retval;
2853 * input binary data from encrypted connection
2855 static void serv_read_ssl(CtdlIPC* ipc, char *buf, unsigned int bytes)
2861 while (len < bytes) {
2862 if (SSL_want_read(ipc->ssl)) {
2863 if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2864 error_printf("SSL_write in serv_read:\n");
2865 ERR_print_errors_fp(stderr);
2868 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2872 errval = SSL_get_error(ipc->ssl, rlen);
2873 if (errval == SSL_ERROR_WANT_READ ||
2874 errval == SSL_ERROR_WANT_WRITE) {
2879 Not sure why we'd want to handle these error codes any differently,
2880 but this definitely isn't the way to handle them. Someone must have
2881 naively assumed that we could fall back to unencrypted communications,
2882 but all it does is just recursively blow the stack.
2883 if (errval == SSL_ERROR_ZERO_RETURN ||
2884 errval == SSL_ERROR_SSL) {
2885 serv_read(ipc, &buf[len], bytes - len);
2889 error_printf("SSL_read in serv_read: %s\n",
2890 ERR_reason_error_string(ERR_peek_error()));
2891 connection_died(ipc, 1);
2900 * send binary to server encrypted
2902 static void serv_write_ssl(CtdlIPC *ipc, const char *buf, unsigned int nbytes)
2904 unsigned int bytes_written = 0;
2908 while (bytes_written < nbytes) {
2909 if (SSL_want_write(ipc->ssl)) {
2910 if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2911 error_printf("SSL_read in serv_write:\n");
2912 ERR_print_errors_fp(stderr);
2915 retval = SSL_write(ipc->ssl, &buf[bytes_written],
2916 nbytes - bytes_written);
2920 errval = SSL_get_error(ipc->ssl, retval);
2921 if (errval == SSL_ERROR_WANT_READ ||
2922 errval == SSL_ERROR_WANT_WRITE) {
2926 if (errval == SSL_ERROR_ZERO_RETURN ||
2927 errval == SSL_ERROR_SSL) {
2928 serv_write(ipc, &buf[bytes_written],
2929 nbytes - bytes_written);
2932 error_printf("SSL_write in serv_write: %s\n",
2933 ERR_reason_error_string(ERR_peek_error()));
2934 connection_died(ipc, 1);
2937 bytes_written += retval;
2942 #ifdef THREADED_CLIENT
2943 static void ssl_lock(int mode, int n, const char *file, int line)
2945 if (mode & CRYPTO_LOCK)
2946 pthread_mutex_lock(Critters[n]);
2948 pthread_mutex_unlock(Critters[n]);
2950 #endif /* THREADED_CLIENT */
2953 static void CtdlIPC_init_OpenSSL(void)
2956 const SSL_METHOD *ssl_method;
2959 /* already done init */
2968 SSL_load_error_strings();
2969 SSLeay_add_ssl_algorithms();
2971 /* Set up the SSL context in which we will oeprate */
2972 ssl_method = SSLv23_client_method();
2973 ssl_ctx = SSL_CTX_new(ssl_method);
2975 error_printf("SSL_CTX_new failed: %s\n",
2976 ERR_reason_error_string(ERR_get_error()));
2979 /* Any reasonable cipher we can get */
2980 if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
2981 error_printf("No ciphers available for encryption\n");
2984 SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
2986 /* Load DH parameters into the context */
2989 error_printf("Can't allocate a DH object: %s\n",
2990 ERR_reason_error_string(ERR_get_error()));
2993 if (!(BN_hex2bn(&(dh->p), DH_P))) {
2994 error_printf("Can't assign DH_P: %s\n",
2995 ERR_reason_error_string(ERR_get_error()));
2999 if (!(BN_hex2bn(&(dh->g), DH_G))) {
3000 error_printf("Can't assign DH_G: %s\n",
3001 ERR_reason_error_string(ERR_get_error()));
3006 SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3009 #ifdef THREADED_CLIENT
3010 /* OpenSSL requires callbacks for threaded clients */
3011 CRYPTO_set_locking_callback(ssl_lock);
3012 CRYPTO_set_id_callback(id_callback);
3014 /* OpenSSL requires us to do semaphores for threaded clients */
3015 Critters = malloc(CRYPTO_num_locks() * sizeof (pthread_mutex_t *));
3017 perror("malloc failed");
3020 for (a = 0; a < CRYPTO_num_locks(); a++) {
3021 Critters[a] = malloc(sizeof (pthread_mutex_t));
3023 perror("malloc failed");
3026 pthread_mutex_init(Critters[a], NULL);
3029 #endif /* THREADED_CLIENT */
3034 #ifdef THREADED_CLIENT
3035 static unsigned long id_callback(void) {
3036 return (unsigned long)pthread_self();
3038 #endif /* THREADED_CLIENT */
3039 #endif /* HAVE_OPENSSL */
3043 ReadNetworkChunk(CtdlIPC* ipc)
3060 FD_SET(ipc->sock, &read_fd);
3061 ret = select(ipc->sock+1, &read_fd, NULL, NULL, &tv);
3063 // fprintf(stderr, "\nselect failed: %d %d %s\n", ret, err, strerror(err));
3067 *(ipc->BufPtr) = '\0';
3068 // n = read(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1);
3069 n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3071 ipc->BufPtr[n]='\0';
3079 if (!(errno == EINTR || errno == EAGAIN))
3080 error_printf( "\nselect failed: %d %s\n", err, strerror(err));
3086 n = read(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1);
3088 ipc->BufPtr[n]='\0';
3093 connection_died(ipc, 0);
3101 * input string from socket - implemented in terms of serv_read()
3105 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3108 char *aptr, *bptr, *aeptr, *beptr;
3110 // error_printf("---\n");
3113 #if defined(HAVE_OPENSSL)
3116 /* Read one character at a time. */
3118 serv_read(ipc, &buf[i], 1);
3119 if (buf[i] == '\n' || i == (SIZ-1))
3123 /* If we got a long line, discard characters until the newline. */
3125 while (buf[i] != '\n')
3126 serv_read(ipc, &buf[i], 1);
3128 /* Strip the trailing newline (and carriage return, if present) */
3129 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3130 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3135 if (ipc->Buf == NULL)
3138 ipc->Buf = (char*) malloc(ipc->BufSize + 10);
3140 ipc->BufPtr = ipc->Buf;
3144 // while ((ipc->BufUsed == 0)||(ntries++ > 10))
3145 if (ipc->BufUsed == 0)
3146 ReadNetworkChunk(ipc);
3148 //// if (ipc->BufUsed != 0) while (1)
3154 aeptr = ipc->Buf + ipc->BufSize;
3155 while ((aptr < aeptr) &&
3159 *(bptr++) = *(aptr++);
3160 if ((*aptr == '\n') && (aptr < aeptr))
3162 /* Terminate it right, remove the line breaks */
3163 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3165 while ((aptr < aeptr ) && (*(aptr + 1) == '\0') )
3168 // 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);
3169 if ((bptr > buf + 1) && (*(bptr-1) == '\r'))
3172 /* is there more in the buffer we need to read later? */
3173 if (ipc->Buf + ipc->BufUsed > aptr)
3180 ipc->BufPtr = ipc->Buf;
3182 // error_printf("----bla6\n");
3185 }/* should we move our read stuf to the bufferstart so we have more space at the end? */
3186 else if ((ipc->BufPtr != ipc->Buf) &&
3187 (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4))))
3189 size_t NewBufSize = ipc->BufSize * 2;
3190 int delta = (ipc->BufPtr - ipc->Buf);
3193 /* if the line would end after our buffer, we should use a bigger buffer. */
3194 NewBuf = (char *)malloc (NewBufSize + 10);
3195 memcpy (NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3197 ipc->Buf = ipc->BufPtr = NewBuf;
3198 ipc->BufUsed -= delta;
3199 ipc->BufSize = NewBufSize;
3201 if (ReadNetworkChunk(ipc) <0)
3203 // error_printf("----bla\n");
3207 /// error_printf("----bl45761%s\nipc->BufUsed");
3209 // error_printf("----bla1\n");
3212 #else /* CHUNKED_READ */
3214 static void CtdlIPC_getline(CtdlIPC* ipc, char *buf)
3218 /* Read one character at a time. */
3220 serv_read(ipc, &buf[i], 1);
3221 if (buf[i] == '\n' || i == (SIZ-1))
3225 /* If we got a long line, discard characters until the newline. */
3227 while (buf[i] != '\n')
3228 serv_read(ipc, &buf[i], 1);
3230 /* Strip the trailing newline (and carriage return, if present) */
3231 if (i>=0 && buf[i] == 10) buf[i--] = 0;
3232 if (i>=0 && buf[i] == 13) buf[i--] = 0;
3236 #endif /* CHUNKED_READ */
3239 void CtdlIPC_chat_recv(CtdlIPC* ipc, char* buf)
3241 CtdlIPC_getline(ipc, buf);
3245 * send line to server - implemented in terms of serv_write()
3247 static void CtdlIPC_putline(CtdlIPC *ipc, const char *buf)
3253 cmd = malloc(len + 2);
3255 /* This requires no extra memory */
3256 serv_write(ipc, buf, len);
3257 serv_write(ipc, "\n", 1);
3259 /* This is network-optimized */
3260 strncpy(cmd, buf, len);
3261 strcpy(cmd + len, "\n");
3262 serv_write(ipc, cmd, len + 1);
3266 ipc->last_command_sent = time(NULL);
3269 void CtdlIPC_chat_send(CtdlIPC* ipc, const char* buf)
3271 CtdlIPC_putline(ipc, buf);
3278 CtdlIPC* CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3286 ipc = ialloc(CtdlIPC);
3290 #if defined(HAVE_OPENSSL)
3292 CtdlIPC_init_OpenSSL();
3294 #if defined(HAVE_PTHREAD_H)
3295 pthread_mutex_init(&(ipc->mutex), NULL); /* Default fast mutex */
3297 ipc->sock = -1; /* Not connected */
3298 ipc->isLocal = 0; /* Not local, of course! */
3299 ipc->downloading = 0;
3301 ipc->last_command_sent = 0L;
3302 ipc->network_status_cb = NULL;
3307 strcpy(cithost, DEFAULT_HOST); /* default host */
3308 strcpy(citport, DEFAULT_PORT); /* default port */
3310 /* Allow caller to supply our values (Windows) */
3311 if (hostbuf && strlen(hostbuf) > 0)
3312 strcpy(cithost, hostbuf);
3313 if (portbuf && strlen(portbuf) > 0)
3314 strcpy(citport, portbuf);
3316 /* Read host/port from command line if present */
3317 for (a = 0; a < argc; ++a) {
3320 } else if (a == 1) {
3321 strcpy(cithost, argv[a]);
3322 } else if (a == 2) {
3323 strcpy(citport, argv[a]);
3325 error_printf("%s: usage: ",argv[0]);
3326 error_printf("%s [host] [port] ",argv[0]);
3333 if ((!strcmp(cithost, "localhost"))
3334 || (!strcmp(cithost, "127.0.0.1"))) {
3338 /* If we're using a unix domain socket we can do a bunch of stuff */
3339 if (!strcmp(cithost, UDS)) {
3340 if (!strcasecmp(citport, DEFAULT_PORT)) {
3341 snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3344 snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3346 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3347 if (ipc->sock == -1) {
3351 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3352 if (portbuf != NULL) strcpy(portbuf, sockpath);
3353 strcpy(ipc->ip_hostname, "");
3354 strcpy(ipc->ip_address, "");
3358 ipc->sock = tcp_connectsock(cithost, citport);
3359 if (ipc->sock == -1) {
3365 /* Learn the actual network identity of the host to which we are connected */
3367 struct sockaddr_in6 clientaddr;
3368 unsigned int addrlen = sizeof(clientaddr);
3370 ipc->ip_hostname[0] = 0;
3371 ipc->ip_address[0] = 0;
3373 getpeername(ipc->sock, (struct sockaddr *)&clientaddr, &addrlen);
3374 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3375 ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0
3377 getnameinfo((struct sockaddr *)&clientaddr, addrlen,
3378 ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST
3381 /* stuff other things elsewhere */
3383 if (hostbuf != NULL) strcpy(hostbuf, cithost);
3384 if (portbuf != NULL) strcpy(portbuf, citport);
3390 * Disconnect and delete the IPC class (destructor)
3392 void CtdlIPC_delete(CtdlIPC* ipc)
3396 SSL_shutdown(ipc->ssl);
3401 if (ipc->sock > -1) {
3402 shutdown(ipc->sock, 2); /* Close it up */
3405 if (ipc->Buf != NULL)
3414 * Disconnect and delete the IPC class (destructor)
3415 * Also NULLs out the pointer
3417 void CtdlIPC_delete_ptr(CtdlIPC** pipc)
3419 CtdlIPC_delete(*pipc);
3425 * return the file descriptor of the server socket so we can select() on it.
3427 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3430 int CtdlIPC_getsockfd(CtdlIPC* ipc)
3437 * return one character
3439 * FIXME: This is only used in chat mode; eliminate it when chat mode gets
3442 char CtdlIPC_get(CtdlIPC* ipc)
3447 serv_read(ipc, buf, 1);