the crufty code, it stings...
[citadel.git] / textclient / citadel_ipc.c
1 // Copyright (c) 1987-2022 by the citadel.org team
2 //
3 // This program is open source software.  Use, duplication, and/or
4 // disclosure are subject to the GNU General Purpose License version 3.
5 //
6 // This program is distributed in the hope that it will be useful,
7 // but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9 // GNU General Public License for more details.
10
11 #include "textclient.h"
12
13 #ifdef HAVE_OPENSSL
14 static SSL_CTX *ssl_ctx;
15 char arg_encrypt;
16 char rc_encrypt;
17 #endif                          /* HAVE_OPENSSL */
18
19 #ifndef INADDR_NONE
20 #define INADDR_NONE 0xffffffff
21 #endif
22
23 static void (*status_hook) (char *s) = NULL;
24 char ctdl_autoetc_dir[PATH_MAX] = "";
25 char file_citadel_rc[PATH_MAX] = "";
26 char ctdl_run_dir[PATH_MAX] = "";
27 char ctdl_etc_dir[PATH_MAX] = "";
28 char ctdl_home_directory[PATH_MAX] = "";
29 char file_citadel_socket[PATH_MAX] = "";
30
31 char *viewdefs[] = {
32         "Messages",
33         "Summary",
34         "Address book",
35         "Calendar",
36         "Tasks"
37 };
38
39 char *axdefs[] = {
40         "Deleted",
41         "New User",
42         "Problem User",
43         "Local User",
44         "Normal User",
45         "Preferred User",
46         "Admin",
47         "Admin"
48 };
49
50
51 void CtdlIPC_lock(CtdlIPC * ipc) {
52         if (ipc->network_status_cb)
53                 ipc->network_status_cb(1);
54 }
55
56
57 void CtdlIPC_unlock(CtdlIPC * ipc) {
58         if (ipc->network_status_cb)
59                 ipc->network_status_cb(0);
60 }
61
62
63 char *libcitadelclient_version_string(void) {
64         return "libcitadelclient(unnumbered)";
65 }
66
67
68 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
69         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
70                          (home&!relh)?ctdl_home_directory:basedir, \
71              ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
72              ((basedir!=ctdldir)&(home&!relh))?"/":"", \
73                          relhome, \
74              (relhome[0]!='\0')?"/":"",\
75                          dirbuffer,\
76                          (dirbuffer[0]!='\0')?"/":"");
77
78 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
79
80
81 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg) {
82         const char *basedir = "";
83         char dirbuffer[PATH_MAX] = "";
84
85         StripSlashes(ctdldir, 1);
86
87 #ifndef HAVE_RUN_DIR
88         basedir = ctdldir;
89 #else
90         basedir = RUN_DIR;
91 #endif
92         COMPUTE_DIRECTORY(ctdl_run_dir);
93         StripSlashes(ctdl_run_dir, 1);
94
95
96 #ifndef HAVE_AUTO_ETC_DIR
97         basedir = ctdldir;
98 #else
99         basedir = AUTO_ETC_DIR;
100 #endif
101         COMPUTE_DIRECTORY(ctdl_autoetc_dir);
102         StripSlashes(ctdl_autoetc_dir, 1);
103
104
105 #ifndef HAVE_ETC_DIR
106         basedir = ctdldir;
107 #else
108         basedir = ETC_DIR;
109 #endif
110         COMPUTE_DIRECTORY(ctdl_etc_dir);
111         StripSlashes(ctdl_etc_dir, 1);
112
113
114
115         snprintf(file_citadel_rc, sizeof file_citadel_rc, "%scitadel.rc", ctdl_etc_dir);
116         StripSlashes(file_citadel_rc, 0);
117
118         snprintf(file_citadel_socket, sizeof file_citadel_socket, "%scitadel.socket", ctdl_run_dir);
119         StripSlashes(file_citadel_socket, 0);
120
121         DBG_PRINT(ctdl_run_dir);
122         DBG_PRINT(file_citadel_socket);
123         DBG_PRINT(ctdl_etc_dir);
124         DBG_PRINT(file_citadel_rc);
125 }
126
127 void setCryptoStatusHook(void (*hook) (char *s)) {
128         status_hook = hook;
129 }
130
131 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC * ipc, void (*hook) (int state)) {
132         ipc->network_status_cb = hook;
133 }
134
135
136 char instant_msgs = 0;
137
138
139 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes);
140 static void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
141 #ifdef HAVE_OPENSSL
142 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes);
143 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
144 static void endtls(SSL * ssl);
145 #endif                          /* HAVE_OPENSSL */
146 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf);
147 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf);
148
149
150
151 const char *svn_revision(void);
152
153 /*
154  * Does nothing.  The server should always return 200.
155  */
156 int CtdlIPCNoop(CtdlIPC * ipc)
157 {
158         char aaa[128];
159
160         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
161 }
162
163
164 /*
165  * Does nothing interesting.  The server should always return 200
166  * along with your string.
167  */
168 int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret)
169 {
170         int ret;
171         char *aaa;
172
173         if (!arg)
174                 return -2;
175         if (!cret)
176                 return -2;
177
178         aaa = (char *) malloc((size_t) (strlen(arg) + 6));
179         if (!aaa)
180                 return -1;
181
182         sprintf(aaa, "ECHO %s", arg);
183         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
184         free(aaa);
185         return ret;
186 }
187
188
189 /*
190  * Asks the server to close the connecction.
191  * Should always return 200.
192  */
193 int CtdlIPCQuit(CtdlIPC * ipc) {
194         int ret = 221;          /* Default to successful quit */
195         char aaa[SIZ];
196
197         CtdlIPC_lock(ipc);
198         if (ipc->sock > -1) {
199                 CtdlIPC_putline(ipc, "QUIT");
200                 CtdlIPC_getline(ipc, aaa);
201                 ret = atoi(aaa);
202         }
203 #ifdef HAVE_OPENSSL
204         if (ipc->ssl)
205                 SSL_shutdown(ipc->ssl);
206         ipc->ssl = NULL;
207 #endif
208         if (ipc->sock)
209                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
210         ipc->sock = -1;
211         CtdlIPC_unlock(ipc);
212         return ret;
213 }
214
215
216 /*
217  * Asks the server to log out.  Should always return 200, even if no user
218  * was logged in.  The user will not be logged in after this!
219  */
220 int CtdlIPCLogout(CtdlIPC * ipc)
221 {
222         int ret;
223         char aaa[SIZ];
224
225         CtdlIPC_lock(ipc);
226         CtdlIPC_putline(ipc, "LOUT");
227         CtdlIPC_getline(ipc, aaa);
228         ret = atoi(aaa);
229         CtdlIPC_unlock(ipc);
230         return ret;
231 }
232
233
234 /*
235  * First stage of authentication - pass the username.  Returns 300 if the
236  * username is able to log in, with the username correctly spelled in cret.
237  * Returns various 500 error codes if the user doesn't exist, etc.
238  */
239 int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret)
240 {
241         int ret;
242         char *aaa;
243
244         if (!username)
245                 return -2;
246         if (!cret)
247                 return -2;
248
249         aaa = (char *) malloc((size_t) (strlen(username) + 6));
250         if (!aaa)
251                 return -1;
252
253         sprintf(aaa, "USER %s", username);
254         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
255         free(aaa);
256         return ret;
257 }
258
259
260 /*
261  * Second stage of authentication - provide password.  The server returns
262  * 200 and several arguments in cret relating to the user's account.
263  */
264 int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret)
265 {
266         int ret;
267         char *aaa;
268
269         if (!passwd)
270                 return -2;
271         if (!cret)
272                 return -2;
273
274         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
275         if (!aaa)
276                 return -1;
277
278         sprintf(aaa, "PASS %s", passwd);
279         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
280         free(aaa);
281         return ret;
282 }
283
284
285 /*
286  * Second stage of authentication - provide password.  The server returns
287  * 200 and several arguments in cret relating to the user's account.
288  */
289 int CtdlIPCTryApopPassword(CtdlIPC * ipc, const char *response, char *cret)
290 {
291         int ret;
292         char *aaa;
293
294         if (!response)
295                 return -2;
296         if (!cret)
297                 return -2;
298
299         aaa = (char *) malloc((size_t) (strlen(response) + 6));
300         if (!aaa)
301                 return -1;
302
303         sprintf(aaa, "PAS2 %s", response);
304         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
305         free(aaa);
306         return ret;
307 }
308
309
310 /*
311  * Create a new user.  This returns 200 plus the same arguments as TryPassword
312  * if selfservice is nonzero, unless there was a problem creating the account.
313  * If selfservice is zero, creates a new user but does not log out the existing
314  * user - intended for use by system administrators to create accounts on
315  * behalf of other users.
316  */
317 int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char *cret)
318 {
319         int ret;
320         char *aaa;
321
322         if (!username)
323                 return -2;
324         if (!cret)
325                 return -2;
326
327         aaa = (char *) malloc((size_t) (strlen(username) + 6));
328         if (!aaa)
329                 return -1;
330
331         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
332         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
333         free(aaa);
334         return ret;
335 }
336
337
338 /*
339  * Changes the user's password.  Returns 200 if changed, errors otherwise.
340  */
341 int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret)
342 {
343         int ret;
344         char *aaa;
345
346         if (!passwd)
347                 return -2;
348         if (!cret)
349                 return -2;
350
351         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
352         if (!aaa)
353                 return -1;
354
355         sprintf(aaa, "SETP %s", passwd);
356         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
357         free(aaa);
358         return ret;
359 }
360
361
362 /* LKRN */
363 /* Caller must free the march list */
364 /* Room types are defined in enum RoomList; keep these in sync! */
365 /* floor is -1 for all, or floornum */
366 int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret)
367 {
368         int ret;
369         struct march *march = NULL;
370         static char *proto[] = { "LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
371         char aaa[SIZ];
372         char *bbb = NULL;
373         size_t bbb_len;
374
375         if (!listing)
376                 return -2;
377         if (*listing)
378                 return -2;      /* Free the listing first */
379         if (!cret)
380                 return -2;
381         /* if (which < 0 || which > 4) return -2; */
382         if (floor < -1)
383                 return -2;      /* Can't validate upper bound, sorry */
384
385         sprintf(aaa, "%s %d", proto[which], floor);
386         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
387         if (ret / 100 == 1) {
388                 struct march *mptr;
389
390                 while (bbb && strlen(bbb)) {
391                         int a;
392
393                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
394                         a = strlen(aaa);
395                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
396                         mptr = (struct march *) malloc(sizeof(struct march));
397                         if (mptr) {
398                                 mptr->next = NULL;
399                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
400                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
401                                 mptr->march_floor = (char) extract_int(aaa, 2);
402                                 mptr->march_order = (char) extract_int(aaa, 3);
403                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
404                                 mptr->march_access = (char) extract_int(aaa, 5);
405                                 if (march == NULL)
406                                         march = mptr;
407                                 else {
408                                         struct march *mptr2;
409
410                                         mptr2 = march;
411                                         while (mptr2->next != NULL)
412                                                 mptr2 = mptr2->next;
413                                         mptr2->next = mptr;
414                                 }
415                         }
416                 }
417         }
418         *listing = march;
419         if (bbb)
420                 free(bbb);
421         return ret;
422 }
423
424
425 /* GETU */
426 /* Caller must free the struct ctdluser; caller may pass an existing one */
427 int CtdlIPCGetConfig(CtdlIPC * ipc, struct ctdluser **uret, char *cret)
428 {
429         int ret;
430
431         if (!cret)
432                 return -2;
433         if (!uret)
434                 return -2;
435         if (!*uret)
436                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
437         if (!*uret)
438                 return -1;
439
440         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
441         if (ret / 100 == 2) {
442                 uret[0]->flags = extract_int(cret, 2);
443         }
444         return ret;
445 }
446
447
448 /* SETU */
449 int CtdlIPCSetConfig(CtdlIPC * ipc, struct ctdluser *uret, char *cret)
450 {
451         char aaa[48];
452
453         if (!uret)
454                 return -2;
455         if (!cret)
456                 return -2;
457
458         sprintf(aaa, "SETU 80|24|%d", uret->flags);
459         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
460 }
461
462
463 /* RENU */
464 int CtdlIPCRenameUser(CtdlIPC * ipc, char *oldname, char *newname, char *cret)
465 {
466         int ret;
467         char cmd[256];
468
469         if (!oldname)
470                 return -2;
471         if (!newname)
472                 return -2;
473         if (!cret)
474                 return -2;
475
476         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
477         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
478         return ret;
479 }
480
481
482 /* GOTO */
483 int CtdlIPCGotoRoom(CtdlIPC * ipc, const char *room, const char *passwd, struct ctdlipcroom **rret, char *cret)
484 {
485         int ret;
486         char *aaa;
487
488         if (!cret)
489                 return -2;
490         if (!rret)
491                 return -2;
492         if (!*rret)
493                 *rret = (struct ctdlipcroom *) calloc(1, sizeof(struct ctdlipcroom));
494         if (!*rret)
495                 return -1;
496
497         if (passwd) {
498                 aaa = (char *) malloc(strlen(room) + strlen(passwd) + 7);
499                 if (!aaa) {
500                         free(*rret);
501                         return -1;
502                 }
503                 sprintf(aaa, "GOTO %s|%s", room, passwd);
504         } else {
505                 aaa = (char *) malloc(strlen(room) + 6);
506                 if (!aaa) {
507                         free(*rret);
508                         return -1;
509                 }
510                 sprintf(aaa, "GOTO %s", room);
511         }
512         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
513         if (ret / 100 == 2) {
514                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
515                 rret[0]->RRunread = extract_long(cret, 1);
516                 rret[0]->RRtotal = extract_long(cret, 2);
517                 rret[0]->RRinfoupdated = extract_int(cret, 3);
518                 rret[0]->RRflags = extract_int(cret, 4);
519                 rret[0]->RRhighest = extract_long(cret, 5);
520                 rret[0]->RRlastread = extract_long(cret, 6);
521                 rret[0]->RRismailbox = extract_int(cret, 7);
522                 rret[0]->RRaide = extract_int(cret, 8);
523                 rret[0]->RRnewmail = extract_long(cret, 9);
524                 rret[0]->RRfloor = extract_int(cret, 10);
525                 rret[0]->RRcurrentview = extract_int(cret, 11);
526                 rret[0]->RRdefaultview = extract_int(cret, 12);
527                 /* position 13 is a trash folder flag ... irrelevant in this client */
528                 rret[0]->RRflags2 = extract_int(cret, 14);
529         } else {
530                 free(*rret);
531                 *rret = NULL;
532         }
533         free(aaa);
534         return ret;
535 }
536
537
538 /* MSGS */
539 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
540 /* whicharg is number of messages, applies to last, first, gt, lt */
541 int CtdlIPCGetMessages(CtdlIPC * ipc, enum MessageList which, int whicharg, const char *mtemplate, unsigned long **mret, char *cret)
542 {
543         int ret;
544         unsigned long count = 0;
545         static char *proto[] = { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
546         char aaa[33];
547         char *bbb = NULL;
548         size_t bbb_len;
549
550         if (!cret)
551                 return -2;
552         if (!mret)
553                 return -2;
554         if (*mret)
555                 return -2;
556         if (which < 0 || which > 6)
557                 return -2;
558
559         if (which <= 2)
560                 sprintf(aaa, "MSGS %s||%d", proto[which], (mtemplate) ? 1 : 0);
561         else
562                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg, (mtemplate) ? 1 : 0);
563         if (mtemplate)
564                 count = strlen(mtemplate);
565         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
566         if (ret / 100 != 1)
567                 return ret;
568         count = 0;
569         *mret = (unsigned long *) calloc(1, sizeof(unsigned long));
570         if (!*mret)
571                 return -1;
572         while (bbb && strlen(bbb)) {
573                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
574                 remove_token(bbb, 0, '\n');
575                 *mret = (unsigned long *) realloc(*mret, (size_t) ((count + 2) * sizeof(unsigned long)));
576                 if (*mret) {
577                         (*mret)[count++] = atol(aaa);
578                         (*mret)[count] = 0L;
579                 } else {
580                         break;
581                 }
582         }
583         if (bbb)
584                 free(bbb);
585         return ret;
586 }
587
588
589 /* MSG0, MSG2 */
590 int CtdlIPCGetSingleMessage(CtdlIPC * ipc, long msgnum, int headers, int as_mime, struct ctdlipcmessage **mret, char *cret)
591 {
592         int ret;
593         char aaa[SIZ];
594         char *bbb = NULL;
595         size_t bbb_len;
596         int multipart_hunting = 0;
597         char multipart_prefix[128];
598         char encoding[256];
599
600         if (!cret)
601                 return -1;
602         if (!mret)
603                 return -1;
604         if (!*mret)
605                 *mret = (struct ctdlipcmessage *) calloc(1, sizeof(struct ctdlipcmessage));
606         if (!*mret)
607                 return -1;
608         if (!msgnum)
609                 return -1;
610
611         strcpy(encoding, "");
612         strcpy(mret[0]->content_type, "");
613         mret[0]->is_local = 0;
614         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
615         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
616         if (ret / 100 == 1) {
617                 if (as_mime != 2) {
618                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
619                         while (strlen(bbb) > 4 && bbb[4] == '=') {
620                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
621                                 remove_token(bbb, 0, '\n');
622
623                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
624                                         mret[0]->nhdr = 1;
625                                 else if (!strncasecmp(aaa, "from=", 5))
626                                         strncpy(mret[0]->author, &aaa[5], SIZ);
627                                 else if (!strncasecmp(aaa, "type=", 5))
628                                         mret[0]->type = atoi(&aaa[5]);
629                                 else if (!strncasecmp(aaa, "msgn=", 5))
630                                         strncpy(mret[0]->msgid, &aaa[5], SIZ);
631                                 else if (!strncasecmp(aaa, "subj=", 5))
632                                         strncpy(mret[0]->subject, &aaa[5], SIZ);
633                                 else if (!strncasecmp(aaa, "rfca=", 5))
634                                         strncpy(mret[0]->email, &aaa[5], SIZ);
635                                 else if (!strncasecmp(aaa, "room=", 5))
636                                         strncpy(mret[0]->room, &aaa[5], SIZ);
637                                 else if (!strncasecmp(aaa, "rcpt=", 5))
638                                         strncpy(mret[0]->recipient, &aaa[5], SIZ);
639                                 else if (!strncasecmp(aaa, "wefw=", 5))
640                                         strncpy(mret[0]->references, &aaa[5], SIZ);
641                                 else if (!strncasecmp(aaa, "time=", 5))
642                                         mret[0]->time = atol(&aaa[5]);
643                                 else if (!strncasecmp(aaa, "locl", 4))
644                                         mret[0]->is_local = 1;
645
646                                 /* Multipart/alternative prefix & suffix strings help
647                                  * us to determine which part we want to download.
648                                  */
649                                 else if (!strncasecmp(aaa, "pref=", 5)) {
650                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
651                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
652                                                 ++multipart_hunting;
653                                         }
654                                 } else if (!strncasecmp(aaa, "suff=", 5)) {
655                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
656                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
657                                                 ++multipart_hunting;
658                                         }
659                                 }
660
661                                 else if (!strncasecmp(aaa, "part=", 5)) {
662                                         struct parts *ptr, *chain;
663
664                                         ptr = (struct parts *) calloc(1, sizeof(struct parts));
665                                         if (ptr) {
666
667                                                 /* Fill the buffers for the caller */
668                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
669                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
670                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
671                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
672                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
673                                                 ptr->length = extract_long(&aaa[5], 5);
674                                                 if (!mret[0]->attachments)
675                                                         mret[0]->attachments = ptr;
676                                                 else {
677                                                         chain = mret[0]->attachments;
678                                                         while (chain->next)
679                                                                 chain = chain->next;
680                                                         chain->next = ptr;
681                                                 }
682
683                                                 /* Now handle multipart/alternative */
684                                                 if (multipart_hunting > 0) {
685                                                         if ((!strcasecmp(ptr->mimetype, "text/plain"))
686                                                             || (!strcasecmp(ptr->mimetype, "text/html"))) {
687                                                                 strcpy(mret[0]->mime_chosen, ptr->number);
688                                                         }
689                                                 }
690
691                                         }
692                                 }
693                         }
694                         /* Eliminate "text\n" */
695                         remove_token(bbb, 0, '\n');
696
697                         /* If doing a MIME thing, pull out the extra headers */
698                         if (as_mime == 4) {
699                                 do {
700                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
701                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
702                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
703                                                 striplt(mret[0]->content_type);
704
705                                                 /* strip out ";charset=" portion.  FIXME do something with
706                                                  * the charset (like... convert it) instead of just throwing
707                                                  * it away
708                                                  */
709                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
710                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
711                                                 }
712
713                                         }
714                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
715                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
716                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
717                                                 striplt(mret[0]->mime_chosen);
718                                         }
719                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
720                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
721                                                 strcpy(encoding, &encoding[26]);
722                                                 striplt(encoding);
723                                         }
724                                         remove_token(bbb, 0, '\n');
725                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
726                                 remove_token(bbb, 0, '\n');
727                         }
728
729
730                 }
731                 if (strlen(bbb)) {
732
733                         if ((!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable"))) {
734                                 char *ccc = NULL;
735                                 int bytes_decoded = 0;
736                                 ccc = malloc(strlen(bbb) + 32768);
737                                 if (!strcasecmp(encoding, "base64")) {
738                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
739                                 } else if (!strcasecmp(encoding, "quoted-printable")) {
740                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
741                                 }
742                                 ccc[bytes_decoded] = 0;
743                                 free(bbb);
744                                 bbb = ccc;
745                         }
746
747                         /* FIXME: Strip trailing whitespace */
748                         bbb = (char *) realloc(bbb, (size_t) (strlen(bbb) + 1));
749
750                 } else {
751                         bbb = (char *) realloc(bbb, 1);
752                         *bbb = '\0';
753                 }
754                 mret[0]->text = bbb;
755         }
756         return ret;
757 }
758
759
760 /* WHOK */
761 int CtdlIPCWhoKnowsRoom(CtdlIPC * ipc, char **listing, char *cret)
762 {
763         int ret;
764         size_t bytes;
765
766         if (!cret)
767                 return -2;
768         if (!listing)
769                 return -2;
770         if (*listing)
771                 return -2;
772
773         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
774         return ret;
775 }
776
777
778 /* INFO */
779 int CtdlIPCServerInfo(CtdlIPC * ipc, char *cret)
780 {
781         int ret;
782         size_t bytes;
783         char *listing = NULL;
784         char buf[SIZ];
785
786         if (!cret)
787                 return -2;
788
789         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
790         if (ret / 100 == 1) {
791                 int line = 0;
792
793                 while (*listing && strlen(listing)) {
794                         extract_token(buf, listing, 0, '\n', sizeof buf);
795                         remove_token(listing, 0, '\n');
796                         switch (line++) {
797                         case 0:
798                                 ipc->ServInfo.pid = atoi(buf);
799                                 break;
800                         case 1:
801                                 strcpy(ipc->ServInfo.nodename, buf);
802                                 break;
803                         case 2:
804                                 strcpy(ipc->ServInfo.humannode, buf);
805                                 break;
806                         case 3:
807                                 strcpy(ipc->ServInfo.fqdn, buf);
808                                 break;
809                         case 4:
810                                 strcpy(ipc->ServInfo.software, buf);
811                                 break;
812                         case 5:
813                                 ipc->ServInfo.rev_level = atoi(buf);
814                                 break;
815                         case 6:
816                                 strcpy(ipc->ServInfo.site_location, buf);
817                                 break;
818                         case 7:
819                                 strcpy(ipc->ServInfo.sysadm, buf);
820                                 break;
821                         case 9:
822                                 strcpy(ipc->ServInfo.moreprompt, buf);
823                                 break;
824                         case 10:
825                                 ipc->ServInfo.ok_floors = atoi(buf);
826                                 break;
827                         case 14:
828                                 ipc->ServInfo.supports_ldap = atoi(buf);
829                                 break;
830                         case 15:
831                                 ipc->ServInfo.newuser_disabled = atoi(buf);
832                                 break;
833                         case 16:
834                                 strcpy(ipc->ServInfo.default_cal_zone, buf);
835                                 break;
836                         case 17:
837                                 ipc->ServInfo.load_avg = atof(buf);
838                                 break;
839                         case 18:
840                                 ipc->ServInfo.worker_avg = atof(buf);
841                                 break;
842                         case 19:
843                                 ipc->ServInfo.thread_count = atoi(buf);
844                                 break;
845                         case 20:
846                                 ipc->ServInfo.has_sieve = atoi(buf);
847                                 break;
848                         case 21:
849                                 ipc->ServInfo.fulltext_enabled = atoi(buf);
850                                 break;
851                         case 22:
852                                 strcpy(ipc->ServInfo.svn_revision, buf);
853                                 break;
854                         case 24:
855                                 ipc->ServInfo.guest_logins = atoi(buf);
856                                 break;
857                         }
858                 }
859
860         }
861         if (listing)
862                 free(listing);
863         return ret;
864 }
865
866
867 /* RDIR */
868 int CtdlIPCReadDirectory(CtdlIPC * ipc, char **listing, char *cret)
869 {
870         int ret;
871         size_t bytes;
872
873         if (!cret)
874                 return -2;
875         if (!listing)
876                 return -2;
877         if (*listing)
878                 return -2;
879
880         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
881         return ret;
882 }
883
884
885 /*
886  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
887  */
888 int CtdlIPCSetLastRead(CtdlIPC * ipc, long msgnum, char *cret)
889 {
890         int ret;
891         char aaa[64];
892
893         if (!cret)
894                 return -2;
895
896         if (msgnum) {
897                 sprintf(aaa, "SLRP %ld", msgnum);
898         } else {
899                 sprintf(aaa, "SLRP HIGHEST");
900         }
901         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
902         return ret;
903 }
904
905
906 /* INVT */
907 int CtdlIPCInviteUserToRoom(CtdlIPC * ipc, const char *username, char *cret)
908 {
909         int ret;
910         char *aaa;
911
912         if (!cret)
913                 return -2;
914         if (!username)
915                 return -2;
916
917         aaa = (char *) malloc(strlen(username) + 6);
918         if (!aaa)
919                 return -1;
920
921         sprintf(aaa, "INVT %s", username);
922         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
923         free(aaa);
924         return ret;
925 }
926
927
928 /* KICK */
929 int CtdlIPCKickoutUserFromRoom(CtdlIPC * ipc, const char *username, char *cret)
930 {
931         int ret;
932         char *aaa;
933
934         if (!cret)
935                 return -1;
936         if (!username)
937                 return -1;
938
939         aaa = (char *) malloc(strlen(username) + 6);
940
941         sprintf(aaa, "KICK %s", username);
942         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
943         free(aaa);
944         return ret;
945 }
946
947
948 /* GETR */
949 int CtdlIPCGetRoomAttributes(CtdlIPC * ipc, struct ctdlroom **qret, char *cret)
950 {
951         int ret;
952
953         if (!cret)
954                 return -2;
955         if (!qret)
956                 return -2;
957         if (!*qret)
958                 *qret = (struct ctdlroom *) calloc(1, sizeof(struct ctdlroom));
959         if (!*qret)
960                 return -1;
961
962         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
963         if (ret / 100 == 2) {
964                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
965                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
966                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
967                 qret[0]->QRflags = extract_int(cret, 3);
968                 qret[0]->QRfloor = extract_int(cret, 4);
969                 qret[0]->QRorder = extract_int(cret, 5);
970                 qret[0]->QRdefaultview = extract_int(cret, 6);
971                 qret[0]->QRflags2 = extract_int(cret, 7);
972         }
973         return ret;
974 }
975
976
977 /* SETR */
978 int CtdlIPCSetRoomAttributes(CtdlIPC *ipc,
979         int forget,                             // if nonzero, kick all users out of the room
980         struct ctdlroom *qret,
981         char *cret
982 ) {
983         int ret;
984         char *cmd;
985
986         if (!cret)
987                 return -2;
988         if (!qret)
989                 return -2;
990
991         cmd = (char *) malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) + strlen(qret->QRdirname) + 64);
992         if (!cmd)
993                 return -1;
994
995         sprintf(cmd, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
996                 qret->QRname, qret->QRpasswd, qret->QRdirname,
997                 qret->QRflags, forget, qret->QRfloor, qret->QRorder, qret->QRdefaultview, qret->QRflags2);
998         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
999         free(cmd);
1000         return ret;
1001 }
1002
1003
1004 /* GETA */
1005 int CtdlIPCGetRoomAide(CtdlIPC *ipc, char *cret)
1006 {
1007         if (!cret)
1008                 return -1;
1009
1010         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1011 }
1012
1013
1014 /* SETA */
1015 int CtdlIPCSetRoomAide(CtdlIPC *ipc, const char *username, char *cret) {
1016         int ret;
1017         char *aaa;
1018
1019         if (!cret)
1020                 return -2;
1021         if (!username)
1022                 return -2;
1023
1024         aaa = (char *) malloc(strlen(username) + 6);
1025         if (!aaa)
1026                 return -1;
1027
1028         sprintf(aaa, "SETA %s", username);
1029         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1030         free(aaa);
1031         return ret;
1032 }
1033
1034
1035 /* ENT0 */
1036 int CtdlIPCPostMessage(CtdlIPC * ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1037 {
1038         int ret;
1039         char cmd[SIZ];
1040         char *ptr;
1041
1042         if (!cret)
1043                 return -2;
1044         if (!mr)
1045                 return -2;
1046
1047         if (mr->references) {
1048                 for (ptr = mr->references; *ptr != 0; ++ptr) {
1049                         if (*ptr == '|')
1050                                 *ptr = '!';
1051                 }
1052         }
1053
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, NULL, cret);
1058         if ((flag == 0) && (subject_required != NULL)) {
1059                 /* Is the server strongly recommending that the user enter a message subject? */
1060                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1061                         *subject_required = extract_int(&cret[4], 1);
1062                 }
1063
1064
1065         }
1066         return ret;
1067 }
1068
1069
1070 /* RINF */
1071 int CtdlIPCRoomInfo(CtdlIPC * ipc, char **iret, char *cret) {
1072         size_t bytes;
1073
1074         if (!cret)
1075                 return -2;
1076         if (!iret)
1077                 return -2;
1078         if (*iret)
1079                 return -2;
1080
1081         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1082 }
1083
1084
1085 /* DELE */
1086 int CtdlIPCDeleteMessage(CtdlIPC * ipc, long msgnum, char *cret) {
1087         char aaa[64];
1088
1089         if (!cret)
1090                 return -2;
1091         if (!msgnum)
1092                 return -2;
1093
1094         sprintf(aaa, "DELE %ld", msgnum);
1095         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1096 }
1097
1098
1099 /* MOVE */
1100 int CtdlIPCMoveMessage(CtdlIPC * ipc, int copy, long msgnum, const char *destroom, char *cret) {
1101         int ret;
1102         char *aaa;
1103
1104         if (!cret)
1105                 return -2;
1106         if (!destroom)
1107                 return -2;
1108         if (!msgnum)
1109                 return -2;
1110
1111         aaa = (char *) malloc(strlen(destroom) + 28);
1112         if (!aaa)
1113                 return -1;
1114
1115         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1116         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1117         free(aaa);
1118         return ret;
1119 }
1120
1121
1122 /* KILL */
1123 int CtdlIPCDeleteRoom(CtdlIPC * ipc, int for_real, char *cret) {
1124         char aaa[64];
1125
1126         if (!cret)
1127                 return -2;
1128
1129         sprintf(aaa, "KILL %d", for_real);
1130         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1131 }
1132
1133
1134 /* CRE8 */
1135 int CtdlIPCCreateRoom(CtdlIPC * ipc, int for_real, const char *roomname, int type, const char *password, int floor, char *cret)
1136 {
1137         int ret;
1138         char *aaa;
1139
1140         if (!cret)
1141                 return -2;
1142         if (!roomname)
1143                 return -2;
1144
1145         if (password) {
1146                 aaa = (char *) malloc(strlen(roomname) + strlen(password) + 40);
1147                 if (!aaa)
1148                         return -1;
1149                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type, password, floor);
1150         }
1151         else {
1152                 aaa = (char *) malloc(strlen(roomname) + 40);
1153                 if (!aaa)
1154                         return -1;
1155                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type, floor);
1156         }
1157         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1158         free(aaa);
1159         return ret;
1160 }
1161
1162
1163 /* FORG */
1164 int CtdlIPCForgetRoom(CtdlIPC * ipc, char *cret) {
1165         if (!cret)
1166                 return -2;
1167
1168         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1169 }
1170
1171
1172 /* MESG */
1173 int CtdlIPCSystemMessage(CtdlIPC * ipc, const char *message, char **mret, char *cret) {
1174         int ret;
1175         char *aaa;
1176         size_t bytes;
1177
1178         if (!cret)
1179                 return -2;
1180         if (!mret)
1181                 return -2;
1182         if (*mret)
1183                 return -2;
1184         if (!message)
1185                 return -2;
1186
1187         aaa = (char *) malloc(strlen(message) + 6);
1188         if (!aaa)
1189                 return -1;
1190
1191         sprintf(aaa, "MESG %s", message);
1192         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1193         free(aaa);
1194         return ret;
1195 }
1196
1197
1198 /* GNUR */
1199 int CtdlIPCNextUnvalidatedUser(CtdlIPC *ipc, char *cret) {
1200         if (!cret)
1201                 return -2;
1202
1203         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1204 }
1205
1206
1207 /* GREG */
1208 int CtdlIPCGetUserRegistration(CtdlIPC * ipc, const char *username, char **rret, char *cret) {
1209         int ret;
1210         char *aaa;
1211         size_t bytes;
1212
1213         if (!cret)
1214                 return -2;
1215         if (!rret)
1216                 return -2;
1217         if (*rret)
1218                 return -2;
1219
1220         if (username)
1221                 aaa = (char *) malloc(strlen(username) + 6);
1222         else
1223                 aaa = (char *) malloc(12);
1224         if (!aaa)
1225                 return -1;
1226
1227         if (username)
1228                 sprintf(aaa, "GREG %s", username);
1229         else
1230                 sprintf(aaa, "GREG _SELF_");
1231         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1232         free(aaa);
1233         return ret;
1234 }
1235
1236
1237 /* VALI */
1238 int CtdlIPCValidateUser(CtdlIPC * ipc, const char *username, int axlevel, char *cret) {
1239         int ret;
1240         char *aaa;
1241
1242         if (!cret)
1243                 return -2;
1244         if (!username)
1245                 return -2;
1246         if (axlevel < AxDeleted || axlevel > AxAideU)
1247                 return -2;
1248
1249         aaa = (char *) malloc(strlen(username) + 17);
1250         if (!aaa)
1251                 return -1;
1252
1253         sprintf(aaa, "VALI %s|%d", username, axlevel);
1254         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1255         free(aaa);
1256         return ret;
1257 }
1258
1259
1260 /* EINF */
1261 int CtdlIPCSetRoomInfo(CtdlIPC * ipc, int for_real, const char *info, char *cret)
1262 {
1263         char aaa[64];
1264
1265         if (!cret)
1266                 return -1;
1267         if (!info)
1268                 return -1;
1269
1270         sprintf(aaa, "EINF %d", for_real);
1271         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1272 }
1273
1274
1275 /* LIST */
1276 int CtdlIPCUserListing(CtdlIPC * ipc, char *searchstring, char **listing, char *cret)
1277 {
1278         size_t bytes;
1279         char *cmd;
1280         int ret;
1281
1282         if (!cret)
1283                 return -1;
1284         if (!listing)
1285                 return -1;
1286         if (*listing)
1287                 return -1;
1288         if (!searchstring)
1289                 return -1;
1290
1291         cmd = malloc(strlen(searchstring) + 10);
1292         sprintf(cmd, "LIST %s", searchstring);
1293
1294         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1295         free(cmd);
1296         return (ret);
1297 }
1298
1299
1300 /* REGI */
1301 int CtdlIPCSetRegistration(CtdlIPC * ipc, const char *info, char *cret)
1302 {
1303         if (!cret)
1304                 return -1;
1305         if (!info)
1306                 return -1;
1307
1308         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info), NULL, NULL, cret);
1309 }
1310
1311
1312 /* CHEK */
1313 int CtdlIPCMiscCheck(CtdlIPC * ipc, struct ctdlipcmisc *chek, char *cret)
1314 {
1315         int ret;
1316
1317         if (!cret)
1318                 return -1;
1319         if (!chek)
1320                 return -1;
1321
1322         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1323         if (ret / 100 == 2) {
1324                 chek->newmail = extract_long(cret, 0);
1325                 chek->needregis = extract_int(cret, 1);
1326                 chek->needvalid = extract_int(cret, 2);
1327         }
1328         return ret;
1329 }
1330
1331
1332 /* DELF */
1333 int CtdlIPCDeleteFile(CtdlIPC * ipc, const char *filename, char *cret)
1334 {
1335         int ret;
1336         char *aaa;
1337
1338         if (!cret)
1339                 return -2;
1340         if (!filename)
1341                 return -2;
1342
1343         aaa = (char *) malloc(strlen(filename) + 6);
1344         if (!aaa)
1345                 return -1;
1346
1347         sprintf(aaa, "DELF %s", filename);
1348         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1349         free(aaa);
1350         return ret;
1351 }
1352
1353
1354 /* MOVF */
1355 int CtdlIPCMoveFile(CtdlIPC * ipc, const char *filename, const char *destroom, char *cret)
1356 {
1357         int ret;
1358         char *aaa;
1359
1360         if (!cret)
1361                 return -2;
1362         if (!filename)
1363                 return -2;
1364         if (!destroom)
1365                 return -2;
1366
1367         aaa = (char *) malloc(strlen(filename) + strlen(destroom) + 7);
1368         if (!aaa)
1369                 return -1;
1370
1371         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1372         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1373         free(aaa);
1374         return ret;
1375 }
1376
1377
1378 /* RWHO */
1379 int CtdlIPCOnlineUsers(CtdlIPC * ipc, char **listing, time_t * stamp, char *cret)
1380 {
1381         int ret;
1382         size_t bytes;
1383
1384         if (!cret)
1385                 return -1;
1386         if (!listing)
1387                 return -1;
1388         if (*listing)
1389                 return -1;
1390
1391         *stamp = CtdlIPCServerTime(ipc, cret);
1392         if (!*stamp)
1393                 *stamp = time(NULL);
1394         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1395         return ret;
1396 }
1397
1398
1399 /* OPEN */
1400 int CtdlIPCFileDownload(CtdlIPC * ipc, const char *filename, void **buf, size_t resume, void (*progress_gauge_callback)
1401                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
1402 {
1403         int ret;
1404         size_t bytes;
1405         time_t last_mod;
1406         char mimetype[SIZ];
1407         char *aaa;
1408
1409         if (!cret)
1410                 return -2;
1411         if (!filename)
1412                 return -2;
1413         if (!buf)
1414                 return -2;
1415         if (*buf)
1416                 return -2;
1417         if (ipc->downloading)
1418                 return -2;
1419
1420         aaa = (char *) malloc(strlen(filename) + 6);
1421         if (!aaa)
1422                 return -1;
1423
1424         sprintf(aaa, "OPEN %s", filename);
1425         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1426         free(aaa);
1427         if (ret / 100 == 2) {
1428                 ipc->downloading = 1;
1429                 bytes = extract_long(cret, 0);
1430                 last_mod = extract_int(cret, 1);
1431                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1432                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume, progress_gauge_callback, cret);
1433                 ret = CtdlIPCEndDownload(ipc, cret);
1434                 if (ret / 100 == 2)
1435                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1436         }
1437         return ret;
1438 }
1439
1440
1441 /* OPNA */
1442 int CtdlIPCAttachmentDownload(CtdlIPC * ipc, long msgnum, const char *part, void **buf, void (*progress_gauge_callback)
1443                                (CtdlIPC *, unsigned long, unsigned long), char *cret)
1444 {
1445         int ret;
1446         size_t bytes;
1447         time_t last_mod;
1448         char filename[SIZ];
1449         char mimetype[SIZ];
1450         char aaa[SIZ];
1451
1452         if (!cret)
1453                 return -2;
1454         if (!buf)
1455                 return -2;
1456         if (*buf)
1457                 return -2;
1458         if (!part)
1459                 return -2;
1460         if (!msgnum)
1461                 return -2;
1462         if (ipc->downloading)
1463                 return -2;
1464
1465         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1466         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1467         if (ret / 100 == 2) {
1468                 ipc->downloading = 1;
1469                 bytes = extract_long(cret, 0);
1470                 last_mod = extract_int(cret, 1);
1471                 extract_token(filename, cret, 2, '|', sizeof filename);
1472                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1473                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1474                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1475                 ret = CtdlIPCEndDownload(ipc, cret);
1476                 if (ret / 100 == 2)
1477                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1478         }
1479         return ret;
1480 }
1481
1482
1483 /* OIMG */
1484 int CtdlIPCImageDownload(CtdlIPC * ipc, const char *filename, void **buf, void (*progress_gauge_callback)
1485                           (CtdlIPC *, unsigned long, unsigned long), char *cret)
1486 {
1487         int ret;
1488         size_t bytes;
1489         time_t last_mod;
1490         char mimetype[SIZ];
1491         char *aaa;
1492
1493         if (!cret)
1494                 return -1;
1495         if (!buf)
1496                 return -1;
1497         if (*buf)
1498                 return -1;
1499         if (!filename)
1500                 return -1;
1501         if (ipc->downloading)
1502                 return -1;
1503
1504         aaa = (char *) malloc(strlen(filename) + 6);
1505         if (!aaa)
1506                 return -1;
1507
1508         sprintf(aaa, "OIMG %s", filename);
1509         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1510         free(aaa);
1511         if (ret / 100 == 2) {
1512                 ipc->downloading = 1;
1513                 bytes = extract_long(cret, 0);
1514                 last_mod = extract_int(cret, 1);
1515                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1516 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1517                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1518                 ret = CtdlIPCEndDownload(ipc, cret);
1519                 if (ret / 100 == 2)
1520                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1521         }
1522         return ret;
1523 }
1524
1525
1526 /* UOPN */
1527 int CtdlIPCFileUpload(CtdlIPC * ipc, const char *save_as, const char *comment, const char *path, void (*progress_gauge_callback)
1528                        (CtdlIPC *, unsigned long, unsigned long), char *cret)
1529 {
1530         int ret;
1531         char *aaa;
1532         FILE *uploadFP;
1533         char MimeTestBuf[64];
1534         const char *MimeType;
1535         long len;
1536
1537         if (!cret)
1538                 return -1;
1539         if (!save_as)
1540                 return -1;
1541         if (!comment)
1542                 return -1;
1543         if (!path)
1544                 return -1;
1545         if (!*path)
1546                 return -1;
1547         if (ipc->uploading)
1548                 return -1;
1549
1550         uploadFP = fopen(path, "r");
1551         if (!uploadFP)
1552                 return -2;
1553
1554         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1555         rewind(uploadFP);
1556         if (len < 0)
1557                 return -3;
1558
1559         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1560         aaa = (char *) malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1561         if (!aaa)
1562                 return -1;
1563
1564         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1565         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1566         free(aaa);
1567         if (ret / 100 == 2) {
1568                 ipc->uploading = 1;
1569                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1570                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1571                 ipc->uploading = 0;
1572         }
1573         return ret;
1574 }
1575
1576
1577 /* UIMG */
1578 int CtdlIPCImageUpload(CtdlIPC * ipc, int for_real, const char *path, const char *save_as, void (*progress_gauge_callback)
1579                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
1580 {
1581         int ret;
1582         FILE *uploadFP;
1583         char *aaa;
1584         char MimeTestBuf[64];
1585         const char *MimeType;
1586         long len;
1587
1588         if (!cret)
1589                 return -1;
1590         if (!save_as)
1591                 return -1;
1592         if (!path && for_real)
1593                 return -1;
1594         if (!*path && for_real)
1595                 return -1;
1596         if (ipc->uploading)
1597                 return -1;
1598
1599         aaa = (char *) malloc(strlen(save_as) + 17);
1600         if (!aaa)
1601                 return -1;
1602
1603         uploadFP = fopen(path, "r");
1604         if (!uploadFP)
1605                 return -2;
1606
1607         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1608         rewind(uploadFP);
1609         if (len < 0)
1610                 return -3;
1611         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1612
1613         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1614         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1615         free(aaa);
1616         if (ret / 100 == 2 && for_real) {
1617                 ipc->uploading = 1;
1618                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1619                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1620                 ipc->uploading = 0;
1621         }
1622         return ret;
1623 }
1624
1625
1626 /* QUSR */
1627 int CtdlIPCQueryUsername(CtdlIPC * ipc, const char *username, char *cret)
1628 {
1629         int ret;
1630         char *aaa;
1631
1632         if (!cret)
1633                 return -2;
1634         if (!username)
1635                 return -2;
1636
1637         aaa = (char *) malloc(strlen(username) + 6);
1638         if (!aaa)
1639                 return -1;
1640
1641         sprintf(aaa, "QUSR %s", username);
1642         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1643         free(aaa);
1644         return ret;
1645 }
1646
1647
1648 /* LFLR */
1649 int CtdlIPCFloorListing(CtdlIPC * ipc, char **listing, char *cret)
1650 {
1651         size_t bytes;
1652
1653         if (!cret)
1654                 return -2;
1655         if (!listing)
1656                 return -2;
1657         if (*listing)
1658                 return -2;
1659
1660         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1661 }
1662
1663
1664 /* CFLR */
1665 int CtdlIPCCreateFloor(CtdlIPC * ipc, int for_real, const char *name, char *cret)
1666 {
1667         int ret;
1668         char aaa[SIZ];
1669
1670         if (!cret)
1671                 return -2;
1672         if (!name)
1673                 return -2;
1674
1675         sprintf(aaa, "CFLR %s|%d", name, for_real);
1676         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1677         return ret;
1678 }
1679
1680
1681 /* KFLR */
1682 int CtdlIPCDeleteFloor(CtdlIPC * ipc, int for_real, int floornum, char *cret)
1683 {
1684         char aaa[SIZ];
1685
1686         if (!cret)
1687                 return -1;
1688         if (floornum < 0)
1689                 return -1;
1690
1691         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1692         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1693 }
1694
1695
1696 /* EFLR */
1697 int CtdlIPCEditFloor(CtdlIPC * ipc, int floornum, const char *floorname, char *cret)
1698 {
1699         int ret;
1700         char aaa[SIZ];
1701
1702         if (!cret)
1703                 return -2;
1704         if (!floorname)
1705                 return -2;
1706         if (floornum < 0)
1707                 return -2;
1708
1709         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1710         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1711         return ret;
1712 }
1713
1714
1715 /*
1716  * IDEN 
1717  *
1718  * You only need to fill out hostname, the defaults will be used if any of the
1719  * other fields are not set properly.
1720  */
1721 int CtdlIPCIdentifySoftware(CtdlIPC * ipc, int developerid, int clientid,
1722                             int revision, const char *software_name, const char *hostname, char *cret)
1723 {
1724         int ret;
1725         char *aaa;
1726
1727         if (developerid < 0 || clientid < 0 || revision < 0 || !software_name) {
1728                 developerid = 8;
1729                 clientid = 0;
1730                 revision = CLIENT_VERSION - 600;
1731                 software_name = "Citadel (libcitadel)";
1732         }
1733         if (!hostname)
1734                 return -2;
1735
1736         aaa = (char *) malloc(strlen(software_name) + strlen(hostname) + 29);
1737         if (!aaa)
1738                 return -1;
1739
1740         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid, revision, software_name, hostname);
1741         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1742         free(aaa);
1743         return ret;
1744 }
1745
1746
1747 /* SEXP */
1748 int CtdlIPCSendInstantMessage(CtdlIPC * ipc, const char *username, const char *text, char *cret)
1749 {
1750         int ret;
1751         char *aaa;
1752
1753         if (!cret)
1754                 return -2;
1755         if (!username)
1756                 return -2;
1757
1758         aaa = (char *) malloc(strlen(username) + 8);
1759         if (!aaa)
1760                 return -1;
1761
1762         if (text) {
1763                 sprintf(aaa, "SEXP %s|-", username);
1764                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1765         } else {
1766                 sprintf(aaa, "SEXP %s||", username);
1767                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1768         }
1769         free(aaa);
1770         return ret;
1771 }
1772
1773
1774 /* GEXP */
1775 int CtdlIPCGetInstantMessage(CtdlIPC * ipc, char **listing, char *cret)
1776 {
1777         size_t bytes;
1778
1779         if (!cret)
1780                 return -2;
1781         if (!listing)
1782                 return -2;
1783         if (*listing)
1784                 return -2;
1785
1786         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1787 }
1788
1789
1790 /* DEXP */
1791 /* mode is 0 = enable, 1 = disable, 2 = status */
1792 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC * ipc, int mode, char *cret)
1793 {
1794         char aaa[64];
1795
1796         if (!cret)
1797                 return -2;
1798
1799         sprintf(aaa, "DEXP %d", mode);
1800         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1801 }
1802
1803
1804 /* EBIO */
1805 int CtdlIPCSetBio(CtdlIPC * ipc, char *bio, char *cret)
1806 {
1807         if (!cret)
1808                 return -2;
1809         if (!bio)
1810                 return -2;
1811
1812         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio), NULL, NULL, cret);
1813 }
1814
1815
1816 /* RBIO */
1817 int CtdlIPCGetBio(CtdlIPC * ipc, const char *username, char **listing, char *cret)
1818 {
1819         int ret;
1820         size_t bytes;
1821         char *aaa;
1822
1823         if (!cret)
1824                 return -2;
1825         if (!username)
1826                 return -2;
1827         if (!listing)
1828                 return -2;
1829         if (*listing)
1830                 return -2;
1831
1832         aaa = (char *) malloc(strlen(username) + 6);
1833         if (!aaa)
1834                 return -1;
1835
1836         sprintf(aaa, "RBIO %s", username);
1837         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1838         free(aaa);
1839         return ret;
1840 }
1841
1842
1843 /* LBIO */
1844 int CtdlIPCListUsersWithBios(CtdlIPC * ipc, char **listing, char *cret)
1845 {
1846         size_t bytes;
1847
1848         if (!cret)
1849                 return -2;
1850         if (!listing)
1851                 return -2;
1852         if (*listing)
1853                 return -2;
1854
1855         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1856 }
1857
1858
1859 /* STEL */
1860 int CtdlIPCStealthMode(CtdlIPC * ipc, int mode, char *cret)
1861 {
1862         char aaa[64];
1863
1864         if (!cret)
1865                 return -1;
1866
1867         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1868         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1869 }
1870
1871
1872 /* TERM */
1873 int CtdlIPCTerminateSession(CtdlIPC * ipc, int sid, char *cret)
1874 {
1875         char aaa[64];
1876
1877         if (!cret)
1878                 return -1;
1879
1880         sprintf(aaa, "TERM %d", sid);
1881         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1882 }
1883
1884
1885 /* DOWN */
1886 int CtdlIPCTerminateServerNow(CtdlIPC * ipc, char *cret)
1887 {
1888         if (!cret)
1889                 return -1;
1890
1891         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1892 }
1893
1894
1895 /* SCDN */
1896 int CtdlIPCTerminateServerScheduled(CtdlIPC * ipc, int mode, char *cret)
1897 {
1898         char aaa[16];
1899
1900         if (!cret)
1901                 return -1;
1902
1903         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1904         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1905 }
1906
1907
1908 /* EMSG */
1909 int CtdlIPCEnterSystemMessage(CtdlIPC * ipc, const char *filename, const char *text, char *cret)
1910 {
1911         int ret;
1912         char *aaa;
1913
1914         if (!cret)
1915                 return -2;
1916         if (!text)
1917                 return -2;
1918         if (!filename)
1919                 return -2;
1920
1921         aaa = (char *) malloc(strlen(filename) + 6);
1922         if (!aaa)
1923                 return -1;
1924
1925         sprintf(aaa, "EMSG %s", filename);
1926         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1927         free(aaa);
1928         return ret;
1929 }
1930
1931
1932 /* TIME */
1933 /* This function returns the actual server time reported, or 0 if error */
1934 time_t CtdlIPCServerTime(CtdlIPC * ipc, char *cret)
1935 {
1936         time_t tret;
1937         int ret;
1938
1939         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
1940         if (ret / 100 == 2) {
1941                 tret = extract_long(cret, 0);
1942         } else {
1943                 tret = 0L;
1944         }
1945         return tret;
1946 }
1947
1948
1949 /* AGUP */
1950 int CtdlIPCAideGetUserParameters(CtdlIPC * ipc, const char *who, struct ctdluser **uret, char *cret)
1951 {
1952         int ret;
1953         char aaa[SIZ];
1954
1955         if (!cret)
1956                 return -2;
1957         if (!uret)
1958                 return -2;
1959         if (!*uret)
1960                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
1961         if (!*uret)
1962                 return -1;
1963
1964         sprintf(aaa, "AGUP %s", who);
1965         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1966
1967         if (ret / 100 == 2) {
1968                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
1969                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
1970                 uret[0]->flags = extract_int(cret, 2);
1971                 uret[0]->timescalled = extract_long(cret, 3);
1972                 uret[0]->posted = extract_long(cret, 4);
1973                 uret[0]->axlevel = extract_int(cret, 5);
1974                 uret[0]->usernum = extract_long(cret, 6);
1975                 uret[0]->lastcall = extract_long(cret, 7);
1976                 uret[0]->USuserpurge = extract_int(cret, 8);
1977         }
1978         return ret;
1979 }
1980
1981
1982 /* ASUP */
1983 int CtdlIPCAideSetUserParameters(CtdlIPC * ipc, const struct ctdluser *uret, char *cret)
1984 {
1985         int ret;
1986         char *aaa;
1987
1988         if (!cret)
1989                 return -2;
1990         if (!uret)
1991                 return -2;
1992
1993         aaa = (char *) malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
1994         if (!aaa)
1995                 return -1;
1996
1997         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
1998                 uret->fullname, uret->password, uret->flags, uret->timescalled,
1999                 uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge);
2000         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2001         free(aaa);
2002         return ret;
2003 }
2004
2005
2006 /* AGEA */
2007 int CtdlIPCAideGetEmailAddresses(CtdlIPC * ipc, const char *who, char *target_buf, char *cret)
2008 {
2009         int ret;
2010         char aaa[SIZ];
2011         char *emailaddrs = NULL;
2012         size_t emailaddrs_len = 0;
2013
2014         sprintf(aaa, "AGEA %s", who);
2015         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
2016
2017         if (ret / 100 == 1) {
2018                 strcpy(target_buf, emailaddrs);
2019         }
2020
2021         if (emailaddrs != NULL) {
2022                 free(emailaddrs);
2023         }
2024
2025         return ret;
2026 }
2027
2028
2029 /* ASEA */
2030 int CtdlIPCAideSetEmailAddresses(CtdlIPC * ipc, const char *who, char *emailaddrs, char *cret)
2031 {
2032         char aaa[SIZ];
2033         int ret;
2034
2035         if (!who)
2036                 return -2;
2037         if (!emailaddrs)
2038                 return -2;
2039         if (!cret)
2040                 return -2;
2041
2042         sprintf(aaa, "ASEA %s", who);
2043         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
2044         return ret;
2045 }
2046
2047
2048 /* GPEX */
2049 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2050 /* caller must free the struct ExpirePolicy */
2051 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC * ipc, GPEXWhichPolicy which, struct ExpirePolicy **policy, char *cret)
2052 {
2053         static char *proto[] = {
2054                 strof(roompolicy),
2055                 strof(floorpolicy),
2056                 strof(sitepolicy),
2057                 strof(mailboxespolicy)
2058         };
2059         char cmd[256];
2060         int ret;
2061
2062         if (!cret)
2063                 return -2;
2064         if (!policy)
2065                 return -2;
2066         if (!*policy)
2067                 *policy = (struct ExpirePolicy *) calloc(1, sizeof(struct ExpirePolicy));
2068         if (!*policy)
2069                 return -1;
2070         if (which < 0 || which > 3)
2071                 return -2;
2072
2073         sprintf(cmd, "GPEX %s", proto[which]);
2074         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2075         if (ret / 100 == 2) {
2076                 policy[0]->expire_mode = extract_int(cret, 0);
2077                 policy[0]->expire_value = extract_int(cret, 1);
2078         }
2079         return ret;
2080 }
2081
2082
2083 /* SPEX */
2084 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2085 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2086 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC * ipc, int which, struct ExpirePolicy *policy, char *cret)
2087 {
2088         char aaa[38];
2089         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2090
2091         if (!cret)
2092                 return -2;
2093         if (which < 0 || which > 3)
2094                 return -2;
2095         if (!policy)
2096                 return -2;
2097         if (policy->expire_mode < 0 || policy->expire_mode > 3)
2098                 return -2;
2099         if (policy->expire_mode >= 2 && policy->expire_value < 1)
2100                 return -2;
2101
2102         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which], policy->expire_mode, policy->expire_value);
2103         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2104 }
2105
2106
2107 /* CONF GET */
2108 int CtdlIPCGetSystemConfig(CtdlIPC * ipc, char **listing, char *cret)
2109 {
2110         size_t bytes;
2111
2112         if (!cret)
2113                 return -2;
2114         if (!listing)
2115                 return -2;
2116         if (*listing)
2117                 return -2;
2118
2119         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0, listing, &bytes, cret);
2120 }
2121
2122
2123 /* CONF SET */
2124 int CtdlIPCSetSystemConfig(CtdlIPC * ipc, const char *listing, char *cret)
2125 {
2126         if (!cret)
2127                 return -2;
2128         if (!listing)
2129                 return -2;
2130
2131         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing), NULL, NULL, cret);
2132 }
2133
2134
2135 /* CONF GETSYS */
2136 int CtdlIPCGetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, char **listing, char *cret)
2137 {
2138         int ret;
2139         char *aaa;
2140         size_t bytes;
2141
2142         if (!cret)
2143                 return -2;
2144         if (!mimetype)
2145                 return -2;
2146         if (!listing)
2147                 return -2;
2148         if (*listing)
2149                 return -2;
2150
2151         aaa = malloc(strlen(mimetype) + 13);
2152         if (!aaa)
2153                 return -1;
2154         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2155         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2156         free(aaa);
2157         return ret;
2158 }
2159
2160
2161 /* CONF PUTSYS */
2162 int CtdlIPCSetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, const char *listing, char *cret)
2163 {
2164         int ret;
2165         char *aaa;
2166
2167         if (!cret)
2168                 return -2;
2169         if (!mimetype)
2170                 return -2;
2171         if (!listing)
2172                 return -2;
2173
2174         aaa = malloc(strlen(mimetype) + 13);
2175         if (!aaa)
2176                 return -1;
2177         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2178         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2179         free(aaa);
2180         return ret;
2181 }
2182
2183
2184 /* GNET */
2185 int CtdlIPCGetRoomNetworkConfig(CtdlIPC * ipc, char **listing, char *cret)
2186 {
2187         size_t bytes;
2188
2189         if (!cret)
2190                 return -2;
2191         if (!listing)
2192                 return -2;
2193         if (*listing)
2194                 return -2;
2195
2196         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2197 }
2198
2199
2200 /* SNET */
2201 int CtdlIPCSetRoomNetworkConfig(CtdlIPC * ipc, const char *listing, char *cret)
2202 {
2203         if (!cret)
2204                 return -2;
2205         if (!listing)
2206                 return -2;
2207
2208         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2209 }
2210
2211
2212 /* REQT */
2213 int CtdlIPCRequestClientLogout(CtdlIPC * ipc, int session, char *cret)
2214 {
2215         char aaa[64];
2216
2217         if (!cret)
2218                 return -2;
2219         if (session < 0)
2220                 return -2;
2221
2222         sprintf(aaa, "REQT %d", session);
2223         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2224 }
2225
2226
2227 /* SEEN */
2228 int CtdlIPCSetMessageSeen(CtdlIPC * ipc, long msgnum, int seen, char *cret)
2229 {
2230         char aaa[27];
2231
2232         if (!cret)
2233                 return -2;
2234         if (msgnum < 0)
2235                 return -2;
2236
2237         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2238         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2239 }
2240
2241
2242 /* STLS */
2243 int CtdlIPCStartEncryption(CtdlIPC * ipc, char *cret)
2244 {
2245         int a;
2246         int r;
2247         char buf[SIZ];
2248
2249 #ifdef HAVE_OPENSSL
2250         SSL *temp_ssl;
2251
2252         /* New SSL object */
2253         temp_ssl = SSL_new(ssl_ctx);
2254         if (!temp_ssl) {
2255                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2256                 return -2;
2257         }
2258         /* Pointless flag waving */
2259 #if SSLEAY_VERSION_NUMBER >= 0x0922
2260         SSL_set_session_id_context(temp_ssl, (const unsigned char *) "Citadel SID", 14);
2261 #endif
2262
2263         /* Associate network connection with SSL object */
2264         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2265                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2266                 return -2;
2267         }
2268
2269         if (status_hook != NULL) {
2270                 status_hook("Requesting encryption...\r");
2271         }
2272
2273         /* Ready to start SSL/TLS */
2274         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2275         if (r / 100 != 2) {
2276                 error_printf("Server can't start TLS: %s\n", buf);
2277                 endtls(temp_ssl);
2278                 return r;
2279         }
2280
2281         /* Do SSL/TLS handshake */
2282         if ((a = SSL_connect(temp_ssl)) < 1) {
2283                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2284                 endtls(temp_ssl);
2285                 return -2;
2286         }
2287         ipc->ssl = temp_ssl;
2288
2289         error_printf("Encrypting with %s cipher %s\n",
2290                      SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2291             );
2292         return r;
2293 #else
2294         return 0;
2295 #endif                          /* HAVE_OPENSSL */
2296 }
2297
2298
2299 #ifdef HAVE_OPENSSL
2300 static void endtls(SSL * ssl)
2301 {
2302         if (ssl) {
2303                 SSL_shutdown(ssl);
2304                 SSL_free(ssl);
2305         }
2306 }
2307 #endif
2308
2309
2310 /* QDIR */
2311 int CtdlIPCDirectoryLookup(CtdlIPC * ipc, const char *address, char *cret)
2312 {
2313         int ret;
2314         char *aaa;
2315
2316         if (!address)
2317                 return -2;
2318         if (!cret)
2319                 return -2;
2320
2321         aaa = (char *) malloc(strlen(address) + 6);
2322         if (!aaa)
2323                 return -1;
2324
2325         sprintf(aaa, "QDIR %s", address);
2326         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2327         free(aaa);
2328         return ret;
2329 }
2330
2331
2332 /* IPGM */
2333 int CtdlIPCInternalProgram(CtdlIPC * ipc, int secret, char *cret)
2334 {
2335         char aaa[30];
2336
2337         if (!cret)
2338                 return -2;
2339         sprintf(aaa, "IPGM %d", secret);
2340         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2341 }
2342
2343
2344
2345
2346 /* ************************************************************************** */
2347 /*           Stuff below this line is not for public consumption            */
2348 /* ************************************************************************** */
2349
2350
2351 /* Read a listing from the server up to 000.  Append to dest if it exists */
2352 char *CtdlIPCReadListing(CtdlIPC * ipc, char *dest)
2353 {
2354         size_t length = 0;
2355         size_t linelength;
2356         char *ret = NULL;
2357         char aaa[SIZ];
2358
2359         ret = dest;
2360         if (ret != NULL) {
2361                 length = strlen(ret);
2362         } else {
2363                 length = 0;
2364         }
2365
2366         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2367                 linelength = strlen(aaa);
2368                 ret = (char *) realloc(ret, (size_t) (length + linelength + 2));
2369                 if (ret) {
2370                         strcpy(&ret[length], aaa);
2371                         length += linelength;
2372                         strcpy(&ret[length++], "\n");
2373                 }
2374         }
2375
2376         return (ret);
2377 }
2378
2379
2380 /* Send a listing to the server; generate the ending 000. */
2381 int CtdlIPCSendListing(CtdlIPC * ipc, const char *listing)
2382 {
2383         char *text;
2384
2385         text = (char *) malloc(strlen(listing) + 6);
2386         if (text) {
2387                 strcpy(text, listing);
2388                 while (text[strlen(text) - 1] == '\n')
2389                         text[strlen(text) - 1] = '\0';
2390                 strcat(text, "\n000");
2391                 CtdlIPC_putline(ipc, text);
2392                 free(text);
2393                 text = NULL;
2394         } else {
2395                 /* Malloc failed but we are committed to send */
2396                 /* This may result in extra blanks at the bottom */
2397                 CtdlIPC_putline(ipc, text);
2398                 CtdlIPC_putline(ipc, "000");
2399         }
2400         return 0;
2401 }
2402
2403
2404 /* Partial read of file from server */
2405 size_t CtdlIPCPartialRead(CtdlIPC * ipc, void **buf, size_t offset, size_t bytes, char *cret)
2406 {
2407         size_t len = 0;
2408         char aaa[SIZ];
2409
2410         if (!buf)
2411                 return 0;
2412         if (!cret)
2413                 return 0;
2414         if (bytes < 1)
2415                 return 0;
2416
2417         CtdlIPC_lock(ipc);
2418         sprintf(aaa, "READ %d|%d", (int) offset, (int) bytes);
2419         CtdlIPC_putline(ipc, aaa);
2420         CtdlIPC_getline(ipc, aaa);
2421         if (aaa[0] != '6')
2422                 strcpy(cret, &aaa[4]);
2423         else {
2424                 len = extract_long(&aaa[4], 0);
2425                 *buf = (void *) realloc(*buf, (size_t) (offset + len));
2426                 if (*buf) {
2427                         /* I know what I'm doing */
2428                         serv_read(ipc, ((char *) (*buf) + offset), len);
2429                 } else {
2430                         /* We have to read regardless */
2431                         serv_read(ipc, aaa, len);
2432                         len = 0;
2433                 }
2434         }
2435         CtdlIPC_unlock(ipc);
2436         return len;
2437 }
2438
2439
2440 /* CLOS */
2441 int CtdlIPCEndDownload(CtdlIPC * ipc, char *cret)
2442 {
2443         int ret;
2444
2445         if (!cret)
2446                 return -2;
2447         if (!ipc->downloading)
2448                 return -2;
2449
2450         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2451         if (ret / 100 == 2)
2452                 ipc->downloading = 0;
2453         return ret;
2454 }
2455
2456
2457 /* MSGP */
2458 int CtdlIPCSpecifyPreferredFormats(CtdlIPC * ipc, char *cret, char *formats)
2459 {
2460         int ret;
2461         char cmd[SIZ];
2462
2463         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2464         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2465         return ret;
2466 }
2467
2468
2469
2470 /* READ */
2471 int CtdlIPCReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2472                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
2473 {
2474         size_t len;
2475
2476         if (!cret)
2477                 return -1;
2478         if (!buf)
2479                 return -1;
2480         if (*buf)
2481                 return -1;
2482         if (!ipc->downloading)
2483                 return -1;
2484
2485         len = resume;
2486         if (progress_gauge_callback)
2487                 progress_gauge_callback(ipc, len, bytes);
2488         while (len < bytes) {
2489                 size_t block;
2490
2491                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2492                 if (block == 0) {
2493                         free(*buf);
2494                         return 0;
2495                 }
2496                 len += block;
2497                 if (progress_gauge_callback)
2498                         progress_gauge_callback(ipc, len, bytes);
2499         }
2500         return len;
2501 }
2502
2503 /* READ - pipelined */
2504 int CtdlIPCHighSpeedReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2505                                   (CtdlIPC *, unsigned long, unsigned long), char *cret)
2506 {
2507         size_t len;
2508         int calls;              /* How many calls in the pipeline */
2509         int i;                  /* iterator */
2510         char aaa[4096];
2511
2512         if (!cret)
2513                 return -1;
2514         if (!buf)
2515                 return -1;
2516         if (*buf)
2517                 return -1;
2518         if (!ipc->downloading)
2519                 return -1;
2520
2521         *buf = (void *) realloc(*buf, bytes - resume);
2522         if (!*buf)
2523                 return -1;
2524
2525         len = 0;
2526         CtdlIPC_lock(ipc);
2527         if (progress_gauge_callback)
2528                 progress_gauge_callback(ipc, len, bytes);
2529
2530         /* How many calls will be in the pipeline? */
2531         calls = (bytes - resume) / 4096;
2532         if ((bytes - resume) % 4096)
2533                 calls++;
2534
2535         /* Send all requests at once */
2536         for (i = 0; i < calls; i++) {
2537                 sprintf(aaa, "READ %d|4096", (int) (i * 4096 + resume));
2538                 CtdlIPC_putline(ipc, aaa);
2539         }
2540
2541         /* Receive all responses at once */
2542         for (i = 0; i < calls; i++) {
2543                 CtdlIPC_getline(ipc, aaa);
2544                 if (aaa[0] != '6')
2545                         strcpy(cret, &aaa[4]);
2546                 else {
2547                         len = extract_long(&aaa[4], 0);
2548                         /* I know what I'm doing */
2549                         serv_read(ipc, ((char *) (*buf) + (i * 4096)), len);
2550                 }
2551                 if (progress_gauge_callback)
2552                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2553         }
2554         CtdlIPC_unlock(ipc);
2555         return len;
2556 }
2557
2558
2559 /* UCLS */
2560 int CtdlIPCEndUpload(CtdlIPC * ipc, int discard, char *cret)
2561 {
2562         int ret;
2563         char cmd[8];
2564
2565         if (!cret)
2566                 return -1;
2567         if (!ipc->uploading)
2568                 return -1;
2569
2570         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2571         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2572         ipc->uploading = 0;
2573         return ret;
2574 }
2575
2576
2577 /* WRIT */
2578 int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_callback)
2579                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
2580 {
2581         int ret = -1;
2582         size_t offset = 0;
2583         size_t bytes;
2584         char aaa[SIZ];
2585         char buf[4096];
2586         FILE *fd = uploadFP;
2587         int ferr;
2588
2589         if (!cret)
2590                 return -1;
2591
2592         fseek(fd, 0L, SEEK_END);
2593         bytes = ftell(fd);
2594         rewind(fd);
2595
2596         if (progress_gauge_callback)
2597                 progress_gauge_callback(ipc, 0, bytes);
2598
2599         while (offset < bytes) {
2600                 size_t to_write;
2601
2602                 /* Read some data in */
2603                 to_write = fread(buf, 1, 4096, fd);
2604                 if (!to_write) {
2605                         if (feof(fd) || ferror(fd))
2606                                 break;
2607                 }
2608                 sprintf(aaa, "WRIT %d", (int) to_write);
2609                 CtdlIPC_putline(ipc, aaa);
2610                 CtdlIPC_getline(ipc, aaa);
2611                 strcpy(cret, &aaa[4]);
2612                 ret = atoi(aaa);
2613                 if (aaa[0] == '7') {
2614                         to_write = extract_long(&aaa[4], 0);
2615
2616                         serv_write(ipc, buf, to_write);
2617                         offset += to_write;
2618                         if (progress_gauge_callback)
2619                                 progress_gauge_callback(ipc, offset, bytes);
2620                         /* Detect short reads and back up if needed */
2621                         /* offset will never be negative anyway */
2622                         fseek(fd, (signed) offset, SEEK_SET);
2623                 } else {
2624                         break;
2625                 }
2626         }
2627         if (progress_gauge_callback)
2628                 progress_gauge_callback(ipc, 1, 1);
2629         ferr = ferror(fd);
2630         fclose(fd);
2631         return (!ferr ? ret : -2);
2632 }
2633
2634
2635 /*
2636  * Generic command method.  This method should handle any server command
2637  * except for CHAT.  It takes the following arguments:
2638  *
2639  * ipc                  The server to speak with
2640  * command              Preformatted command to send to server
2641  * to_send              A text or binary file to send to server
2642  *                      (only sent if server requests it)
2643  * bytes_to_send        The number of bytes in to_send (required if
2644  *                      sending binary, optional if sending listing)
2645  * to_receive           Pointer to a NULL pointer, if the server
2646  *                      sends text or binary we will allocate memory
2647  *                      for the file and stuff it here
2648  * bytes_to_receive     If a file is received, we will store its
2649  *                      byte count here
2650  * proto_response       The protocol response.  Caller must provide
2651  *                      this buffer and ensure that it is at least
2652  *                      128 bytes in length.
2653  *
2654  * This function returns a number equal to the protocol response number,
2655  * -1 if an internal error occurred, -2 if caller provided bad values,
2656  * or 0 - the protocol response number if bad values were found during
2657  * the protocol exchange.
2658  * 
2659  * It stores the protocol response string (minus the number) in 
2660  * protocol_response as described above.  Some commands send additional
2661  * data in this string.
2662  */
2663 int CtdlIPCGenericCommand(CtdlIPC *ipc,
2664                         const char *command,
2665                         const char *to_send,
2666                         size_t bytes_to_send,
2667                         char **to_receive,
2668                         size_t *bytes_to_receive,
2669                         char *proto_response
2670 ) {
2671         char buf[SIZ];
2672         int ret;
2673
2674         if (!command) {
2675                 return -2;
2676         }
2677         if (!proto_response) {
2678                 return -2;
2679         }
2680
2681         CtdlIPC_lock(ipc);
2682         CtdlIPC_putline(ipc, command);
2683         while (1) {
2684                 CtdlIPC_getline(ipc, proto_response);
2685                 if (proto_response[3] == '*')
2686                         instant_msgs = 1;
2687                 ret = atoi(proto_response);
2688                 strcpy(proto_response, &proto_response[4]);
2689                 switch (ret / 100) {
2690                 default:        /* Unknown, punt */
2691                 case 2: /* OK */
2692                 case 3: /* MORE_DATA */
2693                 case 5: /* ERROR */
2694                         /* Don't need to do anything */
2695                         break;
2696                 case 1: /* LISTING_FOLLOWS */
2697                         if (to_receive && !*to_receive && bytes_to_receive) {
2698                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2699                         } else {        /* Drain */
2700                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2701                                 ret = -ret;
2702                         }
2703                         break;
2704                 case 4: /* SEND_LISTING */
2705                         if (to_send) {
2706                                 CtdlIPCSendListing(ipc, to_send);
2707                         } else {
2708                                 /* No listing given, fake it */
2709                                 CtdlIPC_putline(ipc, "000");
2710                                 ret = -ret;
2711                         }
2712                         break;
2713                 case 6: /* BINARY_FOLLOWS */
2714                         if (to_receive && !*to_receive && bytes_to_receive) {
2715                                 *bytes_to_receive = extract_long(proto_response, 0);
2716                                 *to_receive = (char *)
2717                                     malloc((size_t) * bytes_to_receive);
2718                                 if (!*to_receive) {
2719                                         ret = -1;
2720                                 } else {
2721                                         serv_read(ipc, *to_receive, *bytes_to_receive);
2722                                 }
2723                         } else {
2724                                 /* Drain */
2725                                 size_t drain;
2726
2727                                 drain = extract_long(proto_response, 0);
2728                                 while (drain > SIZ) {
2729                                         serv_read(ipc, buf, SIZ);
2730                                         drain -= SIZ;
2731                                 }
2732                                 serv_read(ipc, buf, drain);
2733                                 ret = -ret;
2734                         }
2735                         break;
2736                 case 7: /* SEND_BINARY */
2737                         if (to_send && bytes_to_send) {
2738                                 serv_write(ipc, to_send, bytes_to_send);
2739                         } else if (bytes_to_send) {
2740                                 /* Fake it, send nulls */
2741                                 size_t fake;
2742
2743                                 fake = bytes_to_send;
2744                                 memset(buf, '\0', SIZ);
2745                                 while (fake > SIZ) {
2746                                         serv_write(ipc, buf, SIZ);
2747                                         fake -= SIZ;
2748                                 }
2749                                 serv_write(ipc, buf, fake);
2750                                 ret = -ret;
2751                         }       /* else who knows?  DANGER WILL ROBINSON */
2752                         break;
2753                 case 8: /* START_CHAT_MODE */
2754                         if (!strncasecmp(command, "CHAT", 4)) {
2755                                 /* Don't call chatmode with generic! */
2756                                 CtdlIPC_putline(ipc, "/quit");
2757                                 ret = -ret;
2758                         }
2759                         else {
2760                                 /* In this mode we send then receive listing */
2761                                 if (to_send) {
2762                                         CtdlIPCSendListing(ipc, to_send);
2763                                 }
2764                                 else {
2765                                         /* No listing given, fake it */
2766                                         CtdlIPC_putline(ipc, "000");
2767                                         ret = -ret;
2768                                 }
2769                                 if (to_receive && !*to_receive && bytes_to_receive) {
2770                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2771                                 }
2772                                 else {  /* Drain */
2773                                         while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2774                                         ret = -ret;
2775                                 }
2776                         }
2777                         break;
2778                 case 9: /* ASYNC_MSG */
2779                         /* CtdlIPCDoAsync(ret, proto_response); */
2780                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2781                         break;
2782                 }
2783                 if (ret / 100 != 9)
2784                         break;
2785         }
2786         CtdlIPC_unlock(ipc);
2787         return ret;
2788 }
2789
2790
2791 /*
2792  * Connect to a Citadel on a remote host using a TCP/IP socket
2793  */
2794 static int tcp_connectsock(char *host, char *service) {
2795         struct in6_addr serveraddr;
2796         struct addrinfo hints;
2797         struct addrinfo *res = NULL;
2798         struct addrinfo *ai = NULL;
2799         int rc = (-1);
2800         int sock = (-1);
2801
2802         if ((host == NULL) || IsEmptyStr(host)) {
2803                 service = DEFAULT_HOST;
2804         }
2805         if ((service == NULL) || IsEmptyStr(service)) {
2806                 service = DEFAULT_PORT;
2807         }
2808
2809         memset(&hints, 0x00, sizeof(hints));
2810         hints.ai_flags = AI_NUMERICSERV;
2811         hints.ai_family = AF_UNSPEC;
2812         hints.ai_socktype = SOCK_STREAM;
2813
2814         /*
2815          * Handle numeric IPv4 and IPv6 addresses
2816          */
2817         rc = inet_pton(AF_INET, host, &serveraddr);
2818         if (rc == 1) {          /* dotted quad */
2819                 hints.ai_family = AF_INET;
2820                 hints.ai_flags |= AI_NUMERICHOST;
2821         }
2822         else {
2823                 rc = inet_pton(AF_INET6, host, &serveraddr);
2824                 if (rc == 1) {  /* IPv6 address */
2825                         hints.ai_family = AF_INET6;
2826                         hints.ai_flags |= AI_NUMERICHOST;
2827                 }
2828         }
2829
2830         /* Begin the connection process */
2831
2832         rc = getaddrinfo(host, service, &hints, &res);
2833         if (rc != 0) {
2834                 return (-1);
2835         }
2836
2837         /*
2838          * Try all available addresses until we connect to one or until we run out.
2839          */
2840         for (ai = res; ai != NULL; ai = ai->ai_next) {
2841                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2842                 if (sock < 0)
2843                         return (-1);
2844
2845                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2846                 if (rc >= 0) {
2847                         return (sock);  /* Connected! */
2848                 }
2849                 else {
2850                         close(sock);    /* Failed.  Close the socket to avoid fd leak! */
2851                 }
2852         }
2853         return (-1);
2854 }
2855
2856
2857 /*
2858  * Connect to a Citadel on the local host using a unix domain socket
2859  */
2860 static int uds_connectsock(int *isLocal, char *sockpath) {
2861         struct sockaddr_un addr;
2862         int s;
2863
2864         memset(&addr, 0, sizeof(addr));
2865         addr.sun_family = AF_UNIX;
2866         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2867
2868         s = socket(AF_UNIX, SOCK_STREAM, 0);
2869         if (s < 0) {
2870                 return -1;
2871         }
2872
2873         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2874                 close(s);
2875                 return -1;
2876         }
2877
2878         *isLocal = 1;
2879         return s;
2880 }
2881
2882
2883 /*
2884  * input binary data from socket
2885  */
2886 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes) {
2887         unsigned int len, rlen;
2888
2889 #if defined(HAVE_OPENSSL)
2890         if (ipc->ssl) {
2891                 serv_read_ssl(ipc, buf, bytes);
2892                 return;
2893         }
2894 #endif
2895         len = 0;
2896         while (len < bytes) {
2897                 rlen = read(ipc->sock, &buf[len], bytes - len);
2898                 if (rlen < 1) {
2899                         connection_died(ipc, 0);
2900                         return;
2901                 }
2902                 len += rlen;
2903         }
2904 }
2905
2906
2907 /*
2908  * send binary to server
2909  */
2910 void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes) {
2911         unsigned int bytes_written = 0;
2912         int retval;
2913
2914 #if defined(HAVE_OPENSSL)
2915         if (ipc->ssl) {
2916                 serv_write_ssl(ipc, buf, nbytes);
2917                 return;
2918         }
2919 #endif
2920         while (bytes_written < nbytes) {
2921                 retval = write(ipc->sock, &buf[bytes_written], nbytes - bytes_written);
2922                 if (retval < 1) {
2923                         connection_died(ipc, 0);
2924                         return;
2925                 }
2926                 bytes_written += retval;
2927         }
2928 }
2929
2930
2931 #ifdef HAVE_OPENSSL
2932 /*
2933  * input binary data from encrypted connection
2934  */
2935 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes) {
2936         int len, rlen;
2937         char junk[1];
2938
2939         len = 0;
2940         while (len < bytes) {
2941                 if (SSL_want_read(ipc->ssl)) {
2942                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
2943                                 error_printf("SSL_write in serv_read:\n");
2944                                 ERR_print_errors_fp(stderr);
2945                         }
2946                 }
2947                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
2948                 if (rlen < 1) {
2949                         long errval;
2950
2951                         errval = SSL_get_error(ipc->ssl, rlen);
2952                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2953                                 sleep(1);
2954                                 continue;
2955                         }
2956                         error_printf("SSL_read in serv_read: %s\n", ERR_reason_error_string(ERR_peek_error()));
2957                         connection_died(ipc, 1);
2958                         return;
2959                 }
2960                 len += rlen;
2961         }
2962 }
2963
2964
2965 /*
2966  * send binary to server encrypted
2967  */
2968 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes) {
2969         unsigned int bytes_written = 0;
2970         int retval;
2971         char junk[1];
2972
2973         while (bytes_written < nbytes) {
2974                 if (SSL_want_write(ipc->ssl)) {
2975                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
2976                                 error_printf("SSL_read in serv_write:\n");
2977                                 ERR_print_errors_fp(stderr);
2978                         }
2979                 }
2980                 retval = SSL_write(ipc->ssl, &buf[bytes_written], nbytes - bytes_written);
2981                 if (retval < 1) {
2982                         long errval;
2983
2984                         errval = SSL_get_error(ipc->ssl, retval);
2985                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
2986                                 sleep(1);
2987                                 continue;
2988                         }
2989                         if (errval == SSL_ERROR_ZERO_RETURN || errval == SSL_ERROR_SSL) {
2990                                 serv_write(ipc, &buf[bytes_written], nbytes - bytes_written);
2991                                 return;
2992                         }
2993                         error_printf("SSL_write in serv_write: %s\n", ERR_reason_error_string(ERR_peek_error()));
2994                         connection_died(ipc, 1);
2995                         return;
2996                 }
2997                 bytes_written += retval;
2998         }
2999 }
3000
3001
3002 static void CtdlIPC_init_OpenSSL(void) {
3003         int a;
3004         const SSL_METHOD *ssl_method;
3005         DH *dh;
3006
3007         /* already done init */
3008         if (ssl_ctx) {
3009                 return;
3010         }
3011
3012         /* Get started */
3013         a = 0;
3014         ssl_ctx = NULL;
3015         dh = NULL;
3016         SSL_load_error_strings();
3017         SSLeay_add_ssl_algorithms();
3018
3019         /* Set up the SSL context in which we will oeprate */
3020         ssl_method = SSLv23_client_method();
3021         ssl_ctx = SSL_CTX_new(ssl_method);
3022         if (!ssl_ctx) {
3023                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
3024                 return;
3025         }
3026         /* Any reasonable cipher we can get */
3027         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
3028                 error_printf("No ciphers available for encryption\n");
3029                 return;
3030         }
3031         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
3032
3033         /* Load DH parameters into the context */
3034         dh = DH_new();
3035         if (!dh) {
3036                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
3037                 return;
3038         }
3039
3040         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
3041                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
3042                 DH_free(dh);
3043                 return;
3044         }
3045
3046         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3047         DH_free(dh);
3048 }
3049
3050 #endif                          /* HAVE_OPENSSL */
3051
3052
3053 int ReadNetworkChunk(CtdlIPC * ipc) {
3054         fd_set read_fd;
3055         int ret = 0;
3056         int err = 0;
3057         struct timeval tv;
3058         size_t n;
3059
3060         tv.tv_sec = 1;
3061         tv.tv_usec = 1000;
3062         /*tries = 0; */
3063         n = 0;
3064         while (1) {
3065                 errno = 0;
3066                 FD_ZERO(&read_fd);
3067                 FD_SET(ipc->sock, &read_fd);
3068                 ret = select(ipc->sock + 1, &read_fd, NULL, NULL, &tv);
3069
3070                 if (ret > 0) {
3071
3072                         *(ipc->BufPtr) = '\0';
3073                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3074                         if (n > 0) {
3075                                 ipc->BufPtr[n] = '\0';
3076                                 ipc->BufUsed += n;
3077                                 return n;
3078                         }
3079                         else {
3080                                 return n;
3081                         }
3082                 }
3083                 else if (ret < 0) {
3084                         if (!(errno == EINTR || errno == EAGAIN))
3085                                 error_printf("\nselect failed: %d %s\n", err, strerror(err));
3086                         return -1;
3087                 }
3088         }
3089 }
3090
3091
3092 /*
3093  * input string from socket - implemented in terms of serv_read()
3094  */
3095 #ifdef CHUNKED_READ
3096
3097 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf) {
3098         int i, ntries;
3099         char *aptr, *bptr, *aeptr, *beptr;
3100
3101
3102         beptr = buf + SIZ;
3103 #if defined(HAVE_OPENSSL)
3104         if (ipc->ssl) {
3105
3106                 /* Read one character at a time. */
3107                 for (i = 0;; i++) {
3108                         serv_read(ipc, &buf[i], 1);
3109                         if (buf[i] == '\n' || i == (SIZ - 1))
3110                                 break;
3111                 }
3112
3113                 /* If we got a long line, discard characters until the newline. */
3114                 if (i == (SIZ - 1))
3115                         while (buf[i] != '\n')
3116                                 serv_read(ipc, &buf[i], 1);
3117
3118                 /* Strip the trailing newline (and carriage return, if present) */
3119                 if (i >= 0 && buf[i] == 10)
3120                         buf[i--] = 0;
3121                 if (i >= 0 && buf[i] == 13)
3122                         buf[i--] = 0;
3123         } else
3124 #endif
3125         {
3126                 if (ipc->Buf == NULL) {
3127                         ipc->BufSize = SIZ;
3128                         ipc->Buf = (char *) malloc(ipc->BufSize + 10);
3129                         *(ipc->Buf) = '\0';
3130                         ipc->BufPtr = ipc->Buf;
3131                 }
3132
3133                 ntries = 0;
3134                 if (ipc->BufUsed == 0)
3135                         ReadNetworkChunk(ipc);
3136
3137                 bptr = buf;
3138                 while (1) {
3139                         aptr = ipc->BufPtr;
3140                         aeptr = ipc->Buf + ipc->BufSize;
3141                         while ((aptr < aeptr) && (bptr < beptr) && (*aptr != '\0') && (*aptr != '\n'))
3142                                 *(bptr++) = *(aptr++);
3143                         if ((*aptr == '\n') && (aptr < aeptr)) {
3144                                 /* Terminate it right, remove the line breaks */
3145                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3146                                         aptr++;
3147                                 while ((aptr < aeptr) && (*(aptr + 1) == '\0'))
3148                                         aptr++;
3149                                 *(bptr++) = '\0';
3150                                 if ((bptr > buf + 1) && (*(bptr - 1) == '\r'))
3151                                         *(--bptr) = '\0';
3152
3153                                 /* is there more in the buffer we need to read later? */
3154                                 if (ipc->Buf + ipc->BufUsed > aptr) {
3155                                         ipc->BufPtr = aptr;
3156                                 } else {
3157                                         ipc->BufUsed = 0;
3158                                         ipc->BufPtr = ipc->Buf;
3159                                 }
3160                                 return;
3161
3162                         }
3163                         // should we move our read stuf to the bufferstart so we have more space at the end?
3164                         else if ((ipc->BufPtr != ipc->Buf) && (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4)))) {
3165                                 size_t NewBufSize = ipc->BufSize * 2;
3166                                 int delta = (ipc->BufPtr - ipc->Buf);
3167                                 char *NewBuf;
3168
3169                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3170                                 NewBuf = (char *) malloc(NewBufSize + 10);
3171                                 memcpy(NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3172                                 free(ipc->Buf);
3173                                 ipc->Buf = ipc->BufPtr = NewBuf;
3174                                 ipc->BufUsed -= delta;
3175                                 ipc->BufSize = NewBufSize;
3176                         }
3177                         if (ReadNetworkChunk(ipc) < 0) {
3178                                 return;
3179                         }
3180                 }
3181         }
3182 }
3183
3184 #else                           /* CHUNKED_READ */
3185
3186 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf) {
3187         int i;
3188
3189         /* Read one character at a time. */
3190         for (i = 0;; i++) {
3191                 serv_read(ipc, &buf[i], 1);
3192                 if (buf[i] == '\n' || i == (SIZ - 1))
3193                         break;
3194         }
3195
3196         /* If we got a long line, discard characters until the newline. */
3197         if (i == (SIZ - 1))
3198                 while (buf[i] != '\n')
3199                         serv_read(ipc, &buf[i], 1);
3200
3201         /* Strip the trailing newline (and carriage return, if present) */
3202         if (i >= 0 && buf[i] == 10)
3203                 buf[i--] = 0;
3204         if (i >= 0 && buf[i] == 13)
3205                 buf[i--] = 0;
3206 }
3207
3208
3209 #endif                          /* CHUNKED_READ */
3210
3211
3212 void CtdlIPC_chat_recv(CtdlIPC * ipc, char *buf) {
3213         CtdlIPC_getline(ipc, buf);
3214 }
3215
3216 /*
3217  * send line to server - implemented in terms of serv_write()
3218  */
3219 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf) {
3220         char *cmd = NULL;
3221         int len;
3222
3223         len = strlen(buf);
3224         cmd = malloc(len + 2);
3225         if (!cmd) {
3226                 /* This requires no extra memory */
3227                 serv_write(ipc, buf, len);
3228                 serv_write(ipc, "\n", 1);
3229         }
3230         else {
3231                 /* This is network-optimized */
3232                 strncpy(cmd, buf, len);
3233                 strcpy(cmd + len, "\n");
3234                 serv_write(ipc, cmd, len + 1);
3235                 free(cmd);
3236         }
3237
3238         ipc->last_command_sent = time(NULL);
3239 }
3240
3241
3242 void CtdlIPC_chat_send(CtdlIPC * ipc, const char *buf) {
3243         CtdlIPC_putline(ipc, buf);
3244 }
3245
3246
3247 /*
3248  * attach to server
3249  */
3250 CtdlIPC *CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf) {
3251         int a;
3252         char cithost[SIZ];
3253         char citport[SIZ];
3254         char sockpath[SIZ];
3255         CtdlIPC *ipc;
3256
3257         ipc = malloc(sizeof(struct _CtdlIPC));
3258         if (!ipc) {
3259                 return 0;
3260         }
3261 #if defined(HAVE_OPENSSL)
3262         ipc->ssl = NULL;
3263         CtdlIPC_init_OpenSSL();
3264 #endif
3265         ipc->sock = -1;         /* Not connected */
3266         ipc->isLocal = 0;       /* Not local, of course! */
3267         ipc->downloading = 0;
3268         ipc->uploading = 0;
3269         ipc->last_command_sent = 0L;
3270         ipc->network_status_cb = NULL;
3271         ipc->Buf = NULL;
3272         ipc->BufUsed = 0;
3273         ipc->BufPtr = NULL;
3274
3275         strcpy(cithost, DEFAULT_HOST);  /* default host */
3276         strcpy(citport, DEFAULT_PORT);  /* default port */
3277
3278         /* Allow caller to supply our values */
3279         if (hostbuf && strlen(hostbuf) > 0) {
3280                 strcpy(cithost, hostbuf);
3281         }
3282         if (portbuf && strlen(portbuf) > 0) {
3283                 strcpy(citport, portbuf);
3284         }
3285
3286         /* Read host/port from command line if present */
3287         for (a = 0; a < argc; ++a) {
3288                 if (a == 0) {
3289                         /* do nothing */
3290                 }
3291                 else if (a == 1) {
3292                         strcpy(cithost, argv[a]);
3293                 }
3294                 else if (a == 2) {
3295                         strcpy(citport, argv[a]);
3296                 }
3297                 else {
3298                         error_printf("%s: usage: ", argv[0]);
3299                         error_printf("%s [host] [port] ", argv[0]);
3300                         free(ipc);
3301                         errno = EINVAL;
3302                         return 0;
3303                 }
3304         }
3305
3306         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3307                 ipc->isLocal = 1;
3308         }
3309
3310         /* If we're using a unix domain socket we can do a bunch of stuff */
3311         if (!strcmp(cithost, UDS)) {
3312                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3313                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3314                 }
3315                 else {
3316                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3317                 }
3318                 printf("[%s]\n", sockpath);
3319                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3320                 if (ipc->sock == -1) {
3321                         free(ipc);
3322                         return 0;
3323                 }
3324                 if (hostbuf != NULL)
3325                         strcpy(hostbuf, cithost);
3326                 if (portbuf != NULL)
3327                         strcpy(portbuf, sockpath);
3328                 strcpy(ipc->ip_hostname, "");
3329                 strcpy(ipc->ip_address, "");
3330                 return ipc;
3331         }
3332
3333         printf("[%s:%s]\n", cithost, citport);
3334         ipc->sock = tcp_connectsock(cithost, citport);
3335         if (ipc->sock == -1) {
3336                 free(ipc);
3337                 return 0;
3338         }
3339
3340
3341         /* Learn the actual network identity of the host to which we are connected */
3342
3343         struct sockaddr_in6 clientaddr;
3344         unsigned int addrlen = sizeof(clientaddr);
3345
3346         ipc->ip_hostname[0] = 0;
3347         ipc->ip_address[0] = 0;
3348
3349         getpeername(ipc->sock, (struct sockaddr *) &clientaddr, &addrlen);
3350         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0);
3351         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST);
3352
3353         /* stuff other things elsewhere */
3354
3355         if (hostbuf != NULL)
3356                 strcpy(hostbuf, cithost);
3357         if (portbuf != NULL)
3358                 strcpy(portbuf, citport);
3359         return ipc;
3360 }
3361
3362
3363 /*
3364  * Disconnect and delete the IPC class (destructor)
3365  */
3366 void CtdlIPC_delete(CtdlIPC *ipc) {
3367 #ifdef HAVE_OPENSSL
3368         if (ipc->ssl) {
3369                 SSL_shutdown(ipc->ssl);
3370                 SSL_free(ipc->ssl);
3371                 ipc->ssl = NULL;
3372         }
3373 #endif
3374         if (ipc->sock > -1) {
3375                 shutdown(ipc->sock, 2); /* Close it up */
3376                 ipc->sock = -1;
3377         }
3378         if (ipc->Buf != NULL)
3379                 free(ipc->Buf);
3380         ipc->Buf = NULL;
3381         ipc->BufPtr = NULL;
3382         free(ipc);
3383 }
3384
3385
3386 /*
3387  * Disconnect and delete the IPC class (destructor)
3388  * Also NULLs out the pointer
3389  */
3390 void CtdlIPC_delete_ptr(CtdlIPC **pipc) {
3391         CtdlIPC_delete(*pipc);
3392         *pipc = NULL;
3393 }